# SurrealDB documentation - full corpus

> The complete SurrealDB documentation as a single markdown document. Each page starts with a "Source:" line naming its canonical URL. The page index is at https://surrealdb.com/docs/llms.txt, and every page is also available individually by appending `.md` to its URL. In code examples, a fenced block whose title contains "output" holds the result of the block above it rather than runnable code, and a qualifier such as "Sample output" or "Possible output" means the value shown is one of several possible ones. Inside a block, a "//-" comment marks a value the statement above it returned, while "--" is an ordinary comment.

---

Source: https://surrealdb.com/docs/build/ai-agents

# AI agents

MCP servers, Agent Skills, and framework integrations for SurrealDB. The ways SurrealDB fits into AI tooling, and what it gives an agent.

SurrealDB connects to AI tooling in three places: the assistant you write code with, the framework your application is built on, and the database your agent stores its knowledge in. This page covers each one and where to go next.

## Ways to integrate

**[MCP](/docs/build/ai-agents/mcp.md)** - Give an AI tool a set of tools to call. The hosted [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) connects Claude, Cursor, and other clients to your SurrealDB Cloud account with one URL, so an assistant can deploy instances, query their data, and read logs while you work. A database you run yourself publishes the same data tools through [Embedded MCP](/docs/build/ai-agents/mcp/embedded.md), from SurrealDB 3.1.

**[Agent Skills](/docs/build/ai-agents/agent-skills.md)** - Install packaged knowledge into a coding agent. The official skills cover SurrealQL, vector search, and the Python SDK, so generated queries match how SurrealDB actually behaves instead of guessing at an API.

**[AI frameworks](/docs/build/integrations/ai-frameworks/overview.md)** - Use SurrealDB from the agent library you already have. LangChain, LlamaIndex, CrewAI, PydanticAI, Agno, and others connect through maintained integrations for vector stores, memory, and retrieval.

**[SurrealDB Agent Memory](/docs/agent-memory.md)** - A memory and knowledge layer for agents, running in front of SurrealDB. Use it when an agent needs to remember across sessions, and to keep straight what was said, what is true now, and what used to be true, without designing that model yourself.

## What the database gives an agent

An agent needs somewhere to keep state, a way to query it that fits the question being asked, and retrieval fast enough to answer in the loop. SurrealDB does all three in one engine, so documents, graph relationships and vector indexes live in one store instead of three that have to be kept in sync.

**Memory** - Store conversation context, artefacts, and entity records as documents, and model the relationships between users, tasks, tools, and outcomes as a graph. Agents can then traverse those connections rather than re-reading everything. Partition memory per tenant or per session while keeping one query model.

**Retrieval** - Combine structured filters with [vector indexes and similarity search](/docs/learn/data-models/vector-search/vector-indexes.md) so a lookup can be semantic, exact, or both. That covers RAG and hybrid retrieval without a second system.

**Knowledge graphs** - Follow links between concepts, permissions, and resources inside the database, so a question can reach supporting facts in a few hops instead of several round-trips through application code.

**Tools the agent can call** - Express logic in SurrealQL, including [database functions](/docs/reference/query-language/functions/database-functions.md) for reusable server-side behaviour and [HTTP functions](/docs/reference/query-language/functions/database-functions/http.md) for reaching external APIs from the query layer.

Because schemas and indexes can change as your workflows change, the same deployment serves low-latency reads for live agents and batch jobs for backfills or evaluation.

## Next steps

- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - connect your AI tool to SurrealDB Cloud
- [Agent Skills](/docs/build/ai-agents/agent-skills.md) - install SurrealQL and SDK knowledge into a coding agent
- [AI frameworks overview](/docs/build/integrations/ai-frameworks/overview.md) - compare the framework integrations
- [Build an AI agent](/docs/explore/tutorials/tutorials/build-an-ai-agent.md) - a worked example, end to end

---

Source: https://surrealdb.com/docs/build/ai-agents/agent-skills

# Agent Skills

Official SurrealDB agent skills for use in agentic coding workflows.

AI coding agents perform best when they have the right context for the job. Each Agent Skill is a self-contained package of instructions, reference material, and resources that an agent can pick up and apply whenever the task calls for it.

SurrealDB publishes a set of official skills that give your agent deep knowledge of SurrealQL, vector search, the Python SDK, and more. Built on the [Agent Skills](https://agentskills.io/) open standard, they work across all major coding agents including Claude Code, GitHub Copilot, Cursor, Cline, and many others.

## Installation

### Using the Skills CLI

The quickest way to get started is to install every SurrealDB skill in one go:

```bash
npx skills add surrealdb/agent-skills
```

If you only need a subset, pick the ones relevant to your project:

```bash
npx skills add surrealdb/agent-skills --skill surrealql
```

### Manual setup

Alternatively, clone the repository and copy the skills into your agent's context directory:

```bash
git clone https://github.com/surrealdb/agent-skills.git
```

After cloning, move the `skills/` folder to wherever your coding agent looks for context files. Check your agent's documentation for the exact path.

## Available skills

### SurrealQL

A comprehensive reference for the SurrealQL query language, including syntax, schema design, graph traversals, and idiomatic patterns.

```bash
npx skills add surrealdb/agent-skills --skill surrealql
```

> [!NOTE]
> Useful for:
> · Authoring SurrealQL queries
> · Defining and maintaining schemas
> · Navigating graph relationships and working with record IDs
> · Transitioning from traditional SQL to SurrealQL
> · Configuring live queries for real-time data

### SurrealDB Vector

Covers vector search in SurrealDB, including creating HNSW indexes, running KNN queries, and applying similarity scoring.

```bash
npx skills add surrealdb/agent-skills --skill surrealdb-vector
```

> [!NOTE]
> Useful for:
> · Setting up HNSW vector indexes on tables
> · Running KNN queries with distance operators
> · Implementing semantic search, RAG pipelines, or recommendation engines
> · Fine-tuning HNSW parameters (EFC, M, M0, distance function, type)

### SurrealDB Python

Guidance for using the SurrealDB Python SDK in both client/server mode (WebSocket) and embedded mode (in-memory or file-based).

```bash
npx skills add surrealdb/agent-skills --skill surrealdb-python
```

> [!NOTE]
> Useful for:
> · Connecting to SurrealDB from Python applications
> · Working with the `surrealdb` package (synchronous and asynchronous)
> · Running SurrealDB embedded in Python without a separate server
> · Performing CRUD operations from Python code

## Discovering community skills

The [skills.sh directory](https://skills.sh) catalogues skills published by the wider community. You can also search directly from the command line:

```bash
npx skills find QUERY
```

## Further reading

- [SurrealDB Agent Skills](https://github.com/surrealdb/agent-skills)
- [Agent Skills documentation](https://agentskills.io/home)

---

Source: https://surrealdb.com/docs/build/ai-agents/mcp

# SurrealDB MCP Server

Connect Claude, Cursor, and other AI tools to SurrealDB Cloud. One URL is all the configuration needs.

The SurrealDB MCP Server connects AI tools to your SurrealDB Cloud account. Add one URL to Claude, Cursor, or any other MCP client, sign in with your Surreal ID, and your assistant can work on your databases alongside you: deploy an instance, query the data inside it, look into why something is slow, and keep track of what it all costs.

```text
https://mcp.surrealdb.com
```

The server is hosted for you, so there is nothing to install and nothing to keep running. It acts as you: it sees only the organisations you belong to, respects the role you hold in each one, and asks you to confirm anything that cannot be undone.

> [!NOTE]
> This page covers the hosted server for SurrealDB Cloud. To give an assistant tools against a database you run yourself, see [Embedded MCP](/docs/build/ai-agents/mcp/embedded.md).

## What is MCP?

[Model Context Protocol](https://modelcontextprotocol.io) is an open standard for connecting AI assistants to outside tools and data. A server publishes a set of tools, and a client such as Claude or Cursor calls them on your behalf. Instead of copying a connection string into a chat window and pasting query results back out, your assistant works with SurrealDB directly.

## What you can do

- **Deploy and manage instances** - create an instance, pause one you are not using, resize it as traffic grows, or upgrade it to a newer version of SurrealDB.
- **Work with your data** - ask a question in plain language and let the assistant write and run the SurrealQL, or have it create tables and seed records while you build.
- **Look into problems** - pull up instance status, metrics, and logs next to the query you were just writing.
- **Manage your team** - invite people to an organisation, change what they can do, and withdraw invitations.
- **Keep an eye on cost** - check usage and invoices, and estimate what an instance will cost before you create it.
- **Give agents long-term memory** - create and manage [SurrealDB Agent Memory](/docs/agent-memory.md) contexts, then store and recall memories from them.

## Connect your AI tool

Most clients need nothing but the URL. Follow the guide for your tool:

- [MCP in Claude](/docs/build/ai-agents/mcp/claude.md)
- [MCP in Cursor](/docs/build/ai-agents/mcp/cursor.md)

For anything else, add `https://mcp.surrealdb.com` as a remote MCP server. The usual shape is:

```json
{
  "mcpServers": {
    "surrealdb": {
      "url": "https://mcp.surrealdb.com"
    }
  }
}
```

Some clients differ: VS Code uses a `servers` object with `"type": "http"`, and Windsurf and Antigravity use `serverUrl` in place of `url`. Once the server is connected, ask your assistant to list your organisations. If it comes back with them, you are set up.

## Signing in

### With your Surreal ID

If your client supports sign-in, this is all it takes: the client opens a browser window, you sign in with the same Surreal ID you use for SurrealDB Cloud, and you approve the connection. No credentials go into a config file, and you can disconnect from the client whenever you like.

A signed-in connection can use every tool the server offers, limited only by your role in each organisation.

### With a personal access token

Use a token when your client cannot open a browser, or when something runs unattended. Create one in the [account portal](https://account.surrealdb.com/tokens), tick the permissions it should carry, and pass it to your client as a header:

```json
{
  "mcpServers": {
    "surrealdb": {
      "url": "https://mcp.surrealdb.com",
      "headers": {
        "Authorization": "Bearer <your-token>"
      }
    }
  }
}
```

A token carries only the permissions you selected when you created it:

| Permission | Lets your assistant |
| --- | --- |
| `read:cloud` | See your organisations, instances, SurrealDB Agent Memory contexts, usage, and logs |
| `write:cloud-instances` | Deploy, resize, pause, upgrade, and delete instances |
| `query:cloud-instances` | Read and write the data inside your instances |
| `write:cloud-organization` | Create organisations, and manage members, roles, and invitations |
| `write:cloud-billing` | Update billing details and manage SurrealDB Agent Memory plans |
| `write:cloud-spectron` | Create SurrealDB Agent Memory contexts and manage who may use them |
| `query:spectron-contexts` | Store and recall agent memory in a SurrealDB Agent Memory context |

Include `read:cloud` alongside any of the others. Without it your assistant cannot look up which organisation or instance to act on, so the write permissions have nothing to work with.

> [!IMPORTANT]
> A personal access token stands in for your whole account and does not expire on its own. Give it only the permissions the job needs, keep it out of files you commit, and delete it in the account portal when you are finished with it.

## Available tools

You never call the tools by hand. Your assistant picks what it needs, and most clients show you the call before it runs. Read-only tools are marked as safe, so a client can approve them without asking you every time.

| Group | What your assistant can do |
| --- | --- |
| **Profile and organisations** | Check who you are signed in as, list the organisations you can work in, and create or rename one |
| **Members and invitations** | See who has access to an organisation, invite someone, change their role, or withdraw an invitation |
| **Instances** | List instances and their details, deploy a new one, pause, resume, resize, upgrade, adjust backups, or delete |
| **Instance data** | Run SurrealQL and record operations inside a running instance |
| **Monitoring and usage** | Read an instance's status, metrics, logs, and usage |
| **Billing** | Fill in billing details, check whether an organisation can deploy yet, and read usage and invoices |
| **Catalogue** | Look up the regions, instance types, and SurrealDB versions available to you |
| **Terms** | Fetch the SurrealDB Cloud terms and record your acceptance |
| **SurrealDB Agent Memory contexts** | Create and configure memory contexts, and control who may use them |
| **SurrealDB Agent Memory** | Store, search, recall, and reflect on what an agent has learned |

The regions, instance types, and versions are also offered as MCP resources, for clients that prefer them to tool calls. Clients with a prompt menu get a **Deploy a Cloud Instance** wizard, which walks through the choices, prices the result, and waits for your go-ahead.

To see the exact tool surface your client has, ask your assistant which SurrealDB tools it can use.

## Working with your data

The same connection that manages an instance can also query it. Ask a question, and your assistant chooses the namespace and database, writes the SurrealQL, and shows you what came back. [Example usages](/docs/build/ai-agents/mcp/examples.md) has prompts to try.

Two things have to be true first. The instance must be running, so resume it if you paused it. And it must be on **SurrealDB 3.1 or later**, the version that began answering these calls. If it is older, ask your assistant to upgrade it.

Inside an instance, these are the tools your assistant works with:

| Tool | What it does |
| --- | --- |
| `use` | Choose the namespace and database to work in |
| `query` | Run SurrealQL |
| `select`, `create`, `insert`, `upsert`, `update`, `delete`, `relate` | Read and change records without writing a full statement |
| `run` | Call a database function |
| `list` | List namespaces, databases, tables, indexes, and users |
| `info` | Describe the schema, or the engine itself |

A database you run yourself publishes the same set. See [Embedded MCP](/docs/build/ai-agents/mcp/embedded.md) for how they behave there.

SurrealDB Agent Memory contexts work the same way. Your assistant can `remember`, `recall`, `reflect`, `forget`, `upload`, and `inspect` against a context you own, described in the [SurrealDB Agent Memory MCP tools reference](/docs/agent-memory/integrations/mcp-server/tools-reference.md).

## Deploying an instance

Creating an instance costs money, so the server never quietly does it. Your assistant first checks whether the organisation is ready to deploy, and reports anything standing in the way: no free instances left on the plan, billing details missing, or no payment method on file.

Clearing those blockers is a conversation. The assistant asks for the billing contact and address it needs, then hands you a secure checkout link so you can add a card yourself. Card details never pass through the assistant. Once the payment goes through, it can check readiness again and deploy.

Ask for an estimate before it creates anything. The assistant can price a configuration, show you the monthly cost, and wait for you to agree.

## What the server will not do

- **Delete an instance on a guess.** Deletion is permanent and takes the instance's backups with it, so the assistant has to name the instance exactly as it stands right now. A stale or mistaken name is refused.
- **Handle your card.** Payment details are only ever entered by you, on a secure checkout page. No tool accepts a card number.
- **Accept terms for you.** Your assistant can fetch the documents and link you to them; the agreement is yours to give.
- **Show a secret twice.** New SurrealDB Agent Memory keys and access tokens appear once, when they are created. Store them somewhere safe there and then.

Clients often let you auto-approve tools to save clicks. Reserve that for the read-only ones, and keep the confirmation step on anything that changes or removes something.

## Troubleshooting

| What you see | What to do |
| --- | --- |
| Your client says the server needs authentication | Sign in again from the client, or check the personal access token still exists in the [account portal](https://account.surrealdb.com/tokens) |
| A tool reports a missing permission | Your token was created without it. Permissions are fixed once a token exists, so create a new token with that box ticked |
| Sign-in is refused because your email is not verified | Verify the email address on your Surreal ID, then sign in again |
| Your assistant loses the connection after a while | Idle connections are dropped. Reconnect in the client and carry on |
| An instance cannot be queried | It is paused or still starting. Wait until it reports as ready, or ask your assistant to resume it |
| An instance says it needs a newer version | Data tools need SurrealDB 3.1 or later. Ask your assistant to upgrade the instance |
| Your assistant cannot find an organisation you named | It is looking for the organisation's identifier rather than its display name. Ask it to list your organisations first |

## Next steps

- [MCP in Claude](/docs/build/ai-agents/mcp/claude.md) - set it up in Claude Code, Claude Desktop, or the Claude app
- [MCP in Cursor](/docs/build/ai-agents/mcp/cursor.md) - set it up in Cursor
- [Example usages](/docs/build/ai-agents/mcp/examples.md) - prompts that show what the server can do
- [Embedded MCP](/docs/build/ai-agents/mcp/embedded.md) - the MCP server inside SurrealDB, for databases you run yourself
- [Agent Skills](/docs/build/ai-agents/agent-skills.md) - teach your assistant SurrealQL and SDK patterns to go with these tools

---

Source: https://surrealdb.com/docs/build/ai-agents/mcp/claude

# MCP in Claude

Add the SurrealDB MCP Server to Claude Code, Claude Desktop, or the Claude app.

Claude can reach your SurrealDB Cloud account through the [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md). Setup takes a minute, and you sign in with your Surreal ID in the browser, so there are no credentials to paste.

## Claude Code

Add the server:

```bash
claude mcp add --transport http surrealdb https://mcp.surrealdb.com
```

Run `/mcp` inside Claude Code, choose **surrealdb**, and authenticate. A browser window opens for you to sign in and approve the connection.

Check it worked:

```bash
claude mcp list
```

**surrealdb** should be listed as connected. Ask Claude which SurrealDB organisations it can see, and it should answer with yours.

To connect it for one project rather than everywhere, add `--scope project`. The entry is written to `.mcp.json` in the repository, which you can commit for your team. Everyone still signs in as themselves.

## Claude Desktop and the Claude app

Open **Settings → Connectors → Add custom connector** and fill in:

| Field | Value |
| --- | --- |
| Name | `SurrealDB` |
| URL | `https://mcp.surrealdb.com` |

Claude opens a browser window for you to sign in. Once it closes, start a new conversation and open the tools menu in the composer. The SurrealDB tools appear there.

## Using a personal access token instead

If you would rather not sign in through the browser, or you are running Claude somewhere without one, create a token in the [account portal](https://account.surrealdb.com/tokens) and pass it as a header:

```bash
claude mcp add --transport http surrealdb https://mcp.surrealdb.com \
  --header "Authorization: Bearer <your-token>"
```

Tick only the permissions the work needs. [Signing in](/docs/build/ai-agents/mcp.md#signing-in) lists what each one allows.

## Try it

> Show me the SurrealDB instances in my organisation, and tell me which of them are paused.

Claude lists your organisations, picks the one you meant, and reports each instance with its state. [Example usages](/docs/build/ai-agents/mcp/examples.md) has more to try, including deploying an instance and querying its data.

## Removing it

```bash
claude mcp remove surrealdb
```

In Claude Desktop and the Claude app, open **Settings → Connectors** and remove the SurrealDB connector.

## Troubleshooting

| What you see | What to do |
| --- | --- |
| `claude mcp list` shows the server as failed | Run `/mcp` and authenticate. Until you sign in, only the sign-in tool works |
| The browser window never opens | Sign in from `/mcp` again, and check nothing is blocking pop-ups |
| Claude cannot see the tools in Claude Desktop | Quit and reopen the app completely, then start a new conversation |
| A tool reports a missing permission | You are using a personal access token created without it. Create a new token with that box ticked |

---

Source: https://surrealdb.com/docs/build/ai-agents/mcp/cursor

# MCP in Cursor

Add the SurrealDB MCP Server to Cursor and sign in with your Surreal ID.

With the [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) connected, Cursor's agent can reach your SurrealDB Cloud account while you work: check what is deployed, query a database, and read logs without leaving the editor.

## Add the server

Cursor reads `~/.cursor/mcp.json` for every project, or `.cursor/mcp.json` for one project. Add a `surrealdb` entry:

```json
{
  "mcpServers": {
    "surrealdb": {
      "url": "https://mcp.surrealdb.com"
    }
  }
}
```

If the file already lists other servers, add this entry alongside them.

## Sign in

Open **Settings → MCP**, find **surrealdb**, and click **Connect**. Cursor opens a browser window where you sign in with your Surreal ID and approve the connection. The indicator beside the server turns green when it is ready.

Nothing about your account is stored in `mcp.json`, so a project-level file is safe to commit. Everyone who opens the project signs in as themselves.

## Check it worked

In the chat panel, ask:

> Which SurrealDB Cloud organisations can you see?

Cursor should answer with your organisations. To see the whole tool surface, ask which SurrealDB tools it has available.

## Using a personal access token instead

For a machine that cannot open a browser, create a token in the [account portal](https://account.surrealdb.com/tokens) and add it as a header:

```json
{
  "mcpServers": {
    "surrealdb": {
      "url": "https://mcp.surrealdb.com",
      "headers": {
        "Authorization": "Bearer <your-token>"
      }
    }
  }
}
```

Give the token only the permissions you want the agent to have. [Signing in](/docs/build/ai-agents/mcp.md#signing-in) lists what each one allows.

> [!WARNING]
> A token in a project-level `.cursor/mcp.json` will be committed with the repository. Keep tokens in the global `~/.cursor/mcp.json` instead.

## Try it

> Create a `task` table in my dev instance with a title and a done flag, insert two rows, then show me the ones that are not done.

Cursor picks the instance, sets the namespace and database, and runs the SurrealQL, showing you each call as it goes. [Example usages](/docs/build/ai-agents/mcp/examples.md) has more, including deploying an instance and investigating a slow query.

## Removing it

Delete the `surrealdb` entry from `mcp.json`. Cursor stops offering the tools in new sessions.

## Troubleshooting

| What you see | What to do |
| --- | --- |
| The indicator stays red | Click **Connect** in **Settings → MCP** and complete the sign-in. Check the URL has no trailing path |
| Tools do not appear in chat | Reload the window after editing `mcp.json` |
| The agent asks you to authenticate on every request | Sign in through **Settings → MCP** rather than answering in chat, so Cursor stores the connection |
| A tool reports a missing permission | You are using a personal access token created without it. Create a new token with that box ticked |

---

Source: https://surrealdb.com/docs/build/ai-agents/mcp/embedded

# Embedded MCP

Connect AI agents and editors to a SurrealDB server you run yourself, through the built-in MCP server over HTTP or stdio.

_(since v3.1.0)_

SurrealDB ships a built-in [Model Context Protocol](https://modelcontextprotocol.io) server, so agents and editors can list schema, run SurrealQL, and change records through a standard set of tools. The same access control applies as on [`/sql`](/docs/reference/rest-api/http-protocol.md#sql) and RPC: `DEFINE USER` permissions, table `PERMISSIONS`, and server capability flags all decide what a tool can do.

This page covers the server inside the SurrealDB binary, for databases you run yourself. For SurrealDB Cloud, the hosted [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) reaches the same data tools remotely and adds organisation, instance, and billing management on top.

Both publish the **same data tools**. What differs is how your editor connects and who shares the database.

## When to use `surreal db mcp` vs `surreal db start`

| | **`surreal db mcp`** (stdio) | **`surreal db start`** + **`/mcp`** (HTTP) |
| --- | --- | --- |
| **How it works** | Your editor spawns SurrealDB as a child process; MCP runs over stdin and stdout | You run a server; the editor connects to `http://…/mcp` |
| **Database** | Embedded in the MCP process (default `memory`, or a local file path) | The same instance your app, CLI, or SurrealDB Studio uses |
| **Authentication** | Owner-level access on every tool call - no login step | Normal SurrealDB auth (Bearer JWT, HTTP Basic, …) |
| **Best for** | Learning MCP, solo local development, quickest editor setup | Shared databases, teams, remote instances, production patterns |
| **Editor config** | `command` and `args` in MCP settings | `url` and auth headers |

**Use `surreal db mcp`** when you want the lowest-friction path on a machine you trust: paste a config, restart the editor, and experiment. Think of it as a self-contained database for your assistant.

**Use `surreal db start` and `/mcp`** when the agent should work against a database that already exists, or one that other clients share. That is the right model for least-privilege users, TLS, audit logging, and anything beyond trusted solo development.

> [!TIP]
> New to MCP? Start with `surreal db mcp` in your coding assistant, then move to HTTP once you want the agent on the same instance as your application.

## Transports

### HTTP (`/mcp`)

When you run `surreal db start`, the server exposes **`POST /mcp`** on the same bind address as the REST API. Authenticate with the same headers you use elsewhere, for example `Authorization: Bearer <jwt>` or HTTP Basic.

```bash
surreal db start --user root --pass secret --bind 127.0.0.1:8000 memory
# MCP endpoint: http://127.0.0.1:8000/mcp
```

For a non-loopback hostname (a public FQDN, a Kubernetes service name, or a load-balancer host), the transport rejects the request with `403 Forbidden: Host header is not allowed` until you opt in. Set `SURREAL_MCP_ALLOWED_HOSTS` to your hostnames, or `SURREAL_MCP_ALLOW_ALL_HOSTS=true` behind a trusted proxy. See [Configuration](#configuration).

> [!WARNING]
> Run `/mcp` behind TLS in production. The session header acts like a bearer token for the life of the session: anyone who holds it can repeat tool calls as the same user until the session expires, five minutes after the last request by default.

### Stdio (`surreal db mcp`)

For local editor integrations, use the dedicated subcommand. It runs the MCP server in the same process as an embedded datastore:

```bash
surreal db mcp --user root --pass secret --ns main --db main memory
```

Every tool call over stdio runs with owner-level access. There is no network handshake to attach credentials to, so there is nothing to narrow the permissions with. **Do not expose this entry point to untrusted users.**

See [`surreal db mcp`](/docs/reference/cli/db/commands/mcp.md) for its flags and environment variables, including `SURREAL_MCP_NS`, `SURREAL_MCP_DB`, and the shared `SURREAL_MCP_*` limits below.

## Connect an editor

For stdio, the editor launches SurrealDB itself, so the config is a command and its arguments.

**Cursor**

`~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):

```json
{
  "mcpServers": {
    "surrealdb": {
      "command": "surreal",
      "args": ["mcp", "--user", "root", "--pass", "secret",
          "--ns", "main", "--db", "main", "memory"]
    }
  }
}
```

Restart Cursor, then check that **Settings → MCP** shows `surrealdb` as connected.

**VS Code**

`.vscode/mcp.json` uses a `servers` object and a `type` field:

```json
{
  "servers": {
    "surrealdb": {
      "type": "stdio",
      "command": "surreal",
      "args": ["mcp", "--user", "root", "--pass", "secret",
          "--ns", "main", "--db", "main", "memory"]
    }
  }
}
```

Reload the window after editing the file, and enable agent mode in your MCP-capable extension.

**Claude Desktop**

`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, or `%APPDATA%\Claude\claude_desktop_config.json` on Windows:

```json
{
  "mcpServers": {
    "surrealdb": {
      "command": "surreal",
      "args": ["mcp", "--user", "root", "--pass", "secret",
          "--ns", "main", "--db", "main", "memory"]
    }
  }
}
```

Quit and reopen Claude Desktop completely, then start a new conversation.

If the editor cannot find the binary, use the full path from `which surreal` (macOS and Linux) or `where surreal` (Windows) as `command`.

> [!NOTE]
> `--user` and `--pass` create the root user on a **new** datastore, the same as [`surreal db start`](/docs/reference/cli/db/commands/start.md). They do not authenticate each MCP call, because stdio runs every call as owner. Prefer an `env` block (`SURREAL_USER`, `SURREAL_PASS`) over literals in `args` so credentials stay out of committed config. To keep data between sessions, replace `memory` with a [file-backed path](/docs/reference/cli/db/commands/start.md#datastore-configuration) such as `rocksdb://tmp/surreal-mcp`.

For HTTP, point the editor at a server you are already running and attach credentials as headers:

```json
{
  "mcpServers": {
    "surrealdb": {
      "url": "http://127.0.0.1:8000/mcp",
      "headers": {
        "Authorization": "Basic <base64-encoded username:password>"
      }
    }
  }
}
```

Once connected, ask the assistant which SurrealDB tools it has. It should list `query`, `select`, `use`, and the rest of the set below.

## Published tools

`tools/list` publishes these tools, and the names are stable:

| Tool | Purpose |
| --- | --- |
| `query` | Run SurrealQL and return serialised results |
| `gql` | Run an [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md) query (on by default from 3.3.0; on 3.2.x needs `--allow-experimental gql`) |
| `graphql` | Run a [GraphQL](/docs/learn/querying/graphql/overview.md) query against the configured schema |
| `select`, `create`, `insert`, `upsert`, `update`, `delete`, `relate` | Data manipulation helpers |
| `run` | Call a database function with typed arguments |
| `list` | List namespaces, databases, tables, indexes, users, … |
| `use` | Select the namespace and database context |
| `info` | Schema or engine information for a scope |

Legacy names such as `list_tables`, `use_database`, and `version` are no longer published. Use `list` and `use` instead.

When an agent inspects the schema with `info` or `list`, any [`COMMENT`](/docs/reference/query-language/statements/define/overview.md#comments-on-definitions) on tables and fields is included. Put query-relevant detail in those comments (record-ID conventions, how to compare a field, graph paths) so the agent does not have to guess from names alone.

Each tool also carries annotation hints that clients use to decide what to auto-approve. `gql` is annotated as neither read-only nor safe, because the GQL dialect parses `INSERT`, `SET`, `REMOVE`, and `DELETE` as well as reads.

> [!NOTE]
> Before SurrealDB 3.3.0, `gql` was annotated as read-only and non-destructive, so a client could auto-approve a GQL statement that modified data.

## Protocol versions

_(since v3.3.0)_

The server states which MCP specification revisions it implements rather than inheriting them from the SDK it links against, and it advertises **`2026-07-28`**. An unknown version in a request degrades to that revision.

`2026-07-28` removes the `initialize` handshake and protocol sessions: a request carries its own protocol version, identity, and capabilities, and anything spanning requests must be named on each one. Earlier handshake-based revisions are served on the same endpoint, so clients built against them keep working unchanged.

### Selecting a namespace and database per call

Because a stateless request has no session to hold a selection, every tool except `use` takes optional `namespace` and `database` arguments. Resolution takes the first value it finds, filling each part independently:

1. The tool call's own `namespace` and `database` arguments
2. The `surreal-ns` and `surreal-db` request headers
3. The handshake session's `use` selection, on the revisions that still have one
4. The server's configured defaults

A call may therefore override the database while inheriting the namespace. A request that names its own scope never changes the connection's state, so the override cannot leak into a later call.

`use` stays in `tools/list` under every revision so the tool surface does not vary by protocol version. Under `2026-07-28` it returns an error pointing at the per-call arguments instead of selecting anything.

> [!NOTE]
> The server also identifies itself to clients as `surrealdb`. Before 3.3.0 it reported the name of the MCP SDK crate it was built with.

## Configuration

MCP-specific limits are read once from the environment, with the prefix `SURREAL_MCP_`. HTTP body size uses the server-wide cap.

| Variable | Default | Effect |
| --- | --- | --- |
| `SURREAL_MCP_QUERY_TIMEOUT_SECS` | 60 | Outer timeout on each tool execution (`0` disables) |
| `SURREAL_MCP_MAX_RESULT_BYTES` | 256 KiB | Cap on serialised tool output (`0` disables) |
| `SURREAL_MCP_RUN_MAX_ARGS` | 64 | Maximum arguments to `run` |
| `SURREAL_MCP_PARAMS_MAX_KEYS` | 256 | Maximum top-level keys in parameter objects |
| `SURREAL_MCP_PARAMS_MAX_QL_BYTES` | 4 KiB | Maximum byte length of a `$ql` string inside a `*_data` payload |
| `SURREAL_MCP_SCHEMA_RESOURCE_MAX_TABLES` | 200 | Cap on tables enriched in the database schema resource |
| `SURREAL_MCP_ALLOWED_HOSTS` _(since v3.2.1)_ | loopback only | Exact `Host` values accepted for HTTP `/mcp` (replaces the loopback default) |
| `SURREAL_MCP_ALLOW_ALL_HOSTS` _(since v3.2.1)_ | `false` | Accept any `Host` (trusted-proxy escape hatch; overrides the allowlist) |
| `SURREAL_HTTP_MAX_MCP_BODY_SIZE` | 4 MiB | Maximum HTTP body size for `/mcp` |

Full tables live under [Environment variables](/docs/reference/cli/db/environment-variables.md). [Observability metrics](/docs/manage/observability/metrics.md) include `surrealdb.mcp.*` counters and histograms from 3.1.0.

## Security checklist

- Prefer a least-privilege `DEFINE USER` (for example a custom role with table-level `PERMISSIONS`) over root credentials for agent clients.
- Lock down capabilities (`--deny-funcs`, `--allow-net`, …) so a hijacked session cannot reach `http::*` or other high-risk functions.
- Set `--allow-origin` explicitly for browser-based MCP clients, and avoid `*` in production.
- On a public hostname, set `SURREAL_MCP_ALLOWED_HOSTS`, or `SURREAL_MCP_ALLOW_ALL_HOSTS` only behind a trusted proxy. The default allowlist is loopback-only.
- Forward the `surrealdb::mcp::audit` tracing target to your SIEM. Audit records include the tool name, subject, namespace, database, and outcome, but never query text or row payloads.

## Troubleshooting

| Symptom | Things to check |
| --- | --- |
| The editor shows the server as disconnected | Is `surreal` on your `PATH`? Use the full path as `command`, or run the same command in a terminal to see the error |
| The tools list is empty | Restart the editor after editing MCP config, and confirm the binary is 3.1 or later |
| `403 Forbidden: Host header is not allowed` | HTTP `/mcp` accepts loopback hosts by default. Set `SURREAL_MCP_ALLOWED_HOSTS` to your hostname, or `SURREAL_MCP_ALLOW_ALL_HOSTS=true` behind a trusted proxy. Needs 3.2.1 or later |
| A tool call times out | The default timeout is 60 seconds (`SURREAL_MCP_QUERY_TIMEOUT_SECS`). Narrow the query |
| A result comes back truncated | Output is capped at 256 KiB by default. Paginate or aggregate in SurrealQL |
| Permission errors over HTTP | The user lacks rights for the operation. Sign in with different credentials, or adjust `DEFINE USER` and table `PERMISSIONS` |

## Next steps

- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - the hosted server for SurrealDB Cloud instances
- [Example usages](/docs/build/ai-agents/mcp/examples.md) - prompts to try once an assistant is connected
- [`surreal db mcp` CLI reference](/docs/reference/cli/db/commands/mcp.md) - flags and environment variables
- [Agent Skills](/docs/build/ai-agents/agent-skills.md) - installable SurrealQL and SDK skills for coding agents
- [AI frameworks](/docs/build/integrations/ai-frameworks/overview.md) - using SurrealDB from LangChain, CrewAI, and others

---

Source: https://surrealdb.com/docs/build/ai-agents/mcp/examples

# Example usages

Prompts that show what an assistant can do with the SurrealDB MCP Server, from deploying an instance to querying its data.

These are prompts to try once you have connected the [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) to your AI tool. Each one explains what the assistant does behind the prompt, so you know what to expect before you ask.

## See what you have

> What SurrealDB instances do I have, and which ones are paused?

The assistant lists your organisations, picks the one you meant or asks which, and reports every instance with its region, size, version, and state. This is a good first prompt, because it confirms the connection is working.

> Which of my instances is on an old version of SurrealDB?

It reads the version of each instance and compares them against the versions available to deploy.

## Deploy an instance

> I need a small instance in Frankfurt for a side project. What would it cost, and can my organisation deploy it?

The assistant looks up the regions and instance types available to you, puts a configuration together, prices it, and checks whether the organisation can deploy at all: free instances left on the plan, billing details filled in, payment method on file. You get the cost and any blockers before anything is created.

> Go ahead and deploy it, and tell me how to connect once it is up.

Only after you say so does it create the instance. It then reports the new instance and how to reach it. If billing is not set up yet, it asks for the contact details it needs and hands you a secure checkout link to add a card yourself.

Clients with a prompt menu also offer a **Deploy a Cloud Instance** wizard, which walks the same path step by step.

## Query and shape your data

> In my dev instance, create a `task` table with a title and a done flag, add a couple of rows, then show me the ones that are not done.

The assistant selects the namespace and database, writes the SurrealQL, and shows you both the statements and the results. The instance has to be running and on SurrealDB 3.1 or later.

> How many orders did we take last week, grouped by status?

It inspects the schema to find the right table and fields, then writes the aggregate query rather than asking you for the shape of your data.

> Something looks wrong with the `customer` table. What does its schema actually say?

It describes the table, its fields, and its indexes, which is often faster than opening a dashboard to check a definition. If you have put operational detail in [`COMMENT`](/docs/reference/query-language/statements/define/overview.md#comments-on-definitions) clauses, that text comes back with the schema and steers the next query.

## Investigate a problem

> My production instance felt slow this morning. Have a look and tell me what you find.

The assistant checks the instance's status, pulls metrics for the window you describe, and reads the logs around it. Because it can also query the database, it can follow a hunch about a missing index in the same conversation.

> Compare CPU and memory over the last day against the same period last week.

It requests both windows and summarises the difference.

## Manage your team

> Invite alex@example.com to the Acme organisation as a developer, and show me who else has access.

It lists the roles the organisation offers, sends the invitation with the role you chose, and reports current members along with any invitations still outstanding.

## Keep costs in check

> What have we spent on SurrealDB this month, and which instance accounts for most of it?

The assistant reads usage for the organisation and for each instance, and pulls up recent invoices.

> Pause anything in the sandbox organisation that nobody has touched this week.

It works out which instances are idle from their usage, tells you what it plans to pause, and waits for you to confirm. Paused instances keep their data.

## Give an agent long-term memory

> Set up a SurrealDB Agent Memory context for this project in Frankfurt, then remember that we settled on event sourcing for the orders service.

The assistant creates the [SurrealDB Agent Memory](/docs/agent-memory.md) context, registers a scope path for the project, and stores the decision in it.

> What do we already know about the orders service?

It recalls what has been stored for that project and answers from it, so a decision made weeks ago still turns up in today's conversation.

## Getting better results

- **Name the instance or organisation** when you have more than one. Otherwise the assistant has to stop and ask.
- **Ask for a plan first** on anything expensive or irreversible: "tell me what you would change, then wait".
- **Let it check before it deploys.** Readiness and cost checks cost nothing, and they stop a deploy failing halfway.
- **Keep approvals on write tools.** Auto-approving read-only tools saves clicks; auto-approving the rest removes the pause that lets you catch a mistake.

## Next steps

- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - what each group of tools does, and how signing in works
- [Agent Skills](/docs/build/ai-agents/agent-skills.md) - teach your assistant idiomatic SurrealQL to go with these tools
- [Embedded MCP](/docs/build/ai-agents/mcp/embedded.md) - the same data tools against a database you run yourself

---

Source: https://surrealdb.com/docs/build/embedding

# Embedding SurrealDB

How to embed SurrealDB into your application. Detailed instructions for each supported programming language.

Instead of connecting to a remote server, you can run SurrealDB directly inside your application process. Embedded mode gives you the full query engine with no network overhead, which is useful for local-first applications, edge deployments, and testing.

## Embedding languages

The following languages are supported:

- [.NET](/docs/build/embedding/by-language/dotnet.md)
- [Go](/docs/build/embedding/by-language/golang.md)
- [JavaScript](/docs/build/embedding/by-language/javascript.md)
- [Python](/docs/build/embedding/by-language/python.md)
- [Rust](/docs/build/embedding/by-language/rust.md)

The [Swift](/docs/reference/swift.md) and [Kotlin](/docs/reference/kotlin.md) SDKs, which cover iOS and Android apps, connect to a SurrealDB server over HTTP or WebSocket, and do not run a database inside the app.

## Browser embedding options

When embedding SurrealDB in web browsers, you have two options:

- **IndexedDB**: SurrealDB can be configured to use IndexedDB to store and persist data within the web browser. SurrealDB first serialises both keys and values into a Uint8Array and uses IndexedDB as a binary key-value store. This gives good performance, and all of the functionality that SurrealDB offers when it runs in other ways.

- **SDK**: Alternatively, you can use the SurrealDB SDK to connect to a remote SurrealDB instance instead of using IndexedDB for local persistence.

Embedding in a browser uses the JavaScript SDK's [WebAssembly engine](/docs/reference/javascript/engines/wasm.md) (`@surrealdb/wasm`). It connects with `mem://` for an in-memory database or `indxdb://` for IndexedDB persistence, and can run inside a Web Worker so that queries do not block the page. [Embedded engines](/docs/reference/javascript/concepts/embedded-engines.md#running-in-a-web-worker) shows both setups.

## Storage

- [Storage engines](/docs/build/embedding/storage-engines.md) - which engine an embedded database can use, and what each gives up

---

Source: https://surrealdb.com/docs/build/embedding/by-language/dotnet

# .NET

The documentation for embedding SurrealDB within .NET has been moved to the .NET SDK documentation.

SurrealDB can be run as an embedded database within your .NET application, allowing you to use SurrealDB without running a separate server process. This is ideal for desktop applications, testing, local development, and edge computing scenarios.

## Embedded database options

SurrealDB supports multiple types of embedded storage in .NET:

- **In-memory database** (`mem://` or `SurrealDbMemoryClient`) - Fastest performance with data stored in RAM. Perfect for testing, caching, or temporary data. Data is lost when the connection closes.

- **File-based database** (`rocksdb://`, `surrealkv://`, `SurrealDbRocksDbClient`, or `SurrealDbKvClient`) - Persistent storage on disk using RocksDB or SurrealKV storage engines. Data persists across connections and application restarts.

## Quick example

```csharp
using SurrealDb.Net;

// In-memory database
using var db = new SurrealDbMemoryClient();
await db.Use("main", "main");
var person = await db.Create("person", new Person { Name = "John Doe" });
Console.WriteLine(person);

// File-based persistent database (RocksDB)
using var db = new SurrealDbRocksDbClient("mydb");
await db.Use("main", "main");
var company = await db.Create("company", new Company { Name = "TechStart" });
Console.WriteLine(company);
```

For complete documentation, installation instructions, examples, best practices, and troubleshooting, see the [.NET SDK embedding guide](/docs/reference/dotnet/embedding.md).

---

Source: https://surrealdb.com/docs/build/embedding/by-language/golang

# Golang

The documentation for embedding SurrealDB within Go can be found in the Go SDK and surrealdb.c.go documentation.

SurrealDB can be run [as an embedded database](/docs/reference/golang/embedding.md) within your Go application, allowing you to use SurrealDB without running a separate server process. This is ideal for desktop applications, testing, local development, and edge computing scenarios.

## Embedded database options

SurrealDB supports multiple types of embedded storage in Go:

- **In-memory database** (`mem://`) - Fastest performance with data stored in RAM. Perfect for testing, caching, or temporary data. Data is lost when the connection closes.

- **File-based database** (`surrealkv://` or `rocksdb://`) - Persistent storage on disk using the SurrealKV storage engine. Data persists across connections and application restarts. The RocksDB backend requires a separate manual build, detailed in [the RocksDB build guide](https://github.com/surrealdb/surrealdb.c.go/blob/main/docs/rocksdb.md)

## Quick example

```csharp
package main

import (
    "context"
    "fmt"
    "log"

    surrealdb "github.com/surrealdb/surrealdb.c.go"
)

type Person struct {
    ID   surrealdb.RecordID[string] `cbor:"id,omitempty"`
    Name string                     `cbor:"name"`
    Age  int64                      `cbor:"age"`
}

func main() {
    ctx := context.Background()

    db, err := surrealdb.Open(ctx, "mem://")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    db.Use(ctx, "main", "main")
    db.Query(ctx, "CREATE $rid CONTENT $content", map[string]any{
        "rid":     surrealdb.NewRecordID("person", "alice"),
        "content": Person{Name: "Alice", Age: 30},
    })

    results, _ := surrealdb.Query[Person](ctx, db, "SELECT * FROM person", nil)
    for _, p := range results[0].Values() {
        fmt.Printf("%s: %s (age %d)\n", p.ID, p.Name, p.Age)
    }
}
```

For complete documentation, installation instructions, examples, best practices, and troubleshooting, see the [Go SDK embedding guide](/docs/reference/golang/embedding.md).

---

Source: https://surrealdb.com/docs/build/embedding/by-language/javascript

# JavaScript

Embedding SurrealDB in JavaScript

SurrealDB is designed to be run in many different ways and in many environments. Due to the [separation of the storage and compute](/docs/learn/data-models/architecture.md) layers, SurrealDB can be run in embedded mode, from within your JavaScript environments.

You can embed SurrealDB in both browser and server environments. In browser environments using the [Wasm engine](/docs/reference/javascript/engines/wasm.md), SurrealDB can be run as an in-memory database, or it can persist data using IndexedDB. In server environments using the [Node.js engine](/docs/reference/javascript/engines/node.md), SurrealDB can be run as an embedded database, backed by an in-memory engine (`mem://`), RocksDB (`rocksdb://`) or [SurrealKV](/docs/running/file-backed.md) (`surrealkv://`).

The sections below cover embedding SurrealDB in browser and in server environments.

## Browser
In browser environments, using the [Wasm engine](/docs/reference/javascript/engines/wasm.md), you can run SurrealDB in-memory or with IndexedDB persistence.

For more information on how to embed SurrealDB in browser environments, please see the [Wasm engine](/docs/reference/javascript/engines/wasm.md) documentation.

## Server

In server environments, you can use the [Node.js engine](/docs/reference/javascript/engines/node.md) to run SurrealDB as an embedded database.

For more information on how to embed SurrealDB in server environments, please see the [Node.js engine](/docs/reference/javascript/engines/node.md) documentation.

---

Source: https://surrealdb.com/docs/build/embedding/by-language/python

# Python

The documentation for embedding SurrealDB within Python has been moved to the Python SDK documentation.

SurrealDB can be run as an embedded database within your Python application, allowing you to use SurrealDB without running a separate server process. This is ideal for desktop applications, testing, local development, and edge computing scenarios.

## Embedded database options

SurrealDB supports two types of embedded storage in Python:

- **In-memory database** (`mem://` or `memory`) - Fastest performance with data stored in RAM. Perfect for testing, caching, or temporary data. Data is lost when the connection closes.

- **File-based database** (`file://`, `rocksdb://`, or `surrealkv://`) - Persistent storage on disk using the SurrealKV storage engine. Data persists across connections and application restarts.

## Quick example

```python
from surrealdb import AsyncSurreal

# In-memory database
async with AsyncSurreal("mem://") as db:
    await db.use("main", "main")
    person = await db.create("person", {"name": "John Doe"})
    print(person)

# File (RocksDB)-based persistent database
async with AsyncSurreal("rocksdb://mydb") as db:
    await db.use("main", "main")
    company = await db.create("company", {"name": "TechStart"})
    print(company)
```

 For complete documentation, installation instructions, examples, best practices, and troubleshooting, see the rest of this guide.

---

Source: https://surrealdb.com/docs/build/embedding/by-language/rust

# Rust

Embedding SurrealDB in Rust

SurrealDB can be run as an embedded database within your Rust application, allowing you to use SurrealDB without running a separate server process. This is ideal for desktop applications, testing, local development, and edge computing scenarios.

## Embedded database options

SurrealDB supports multiple types of embedded storage in Rust:

- **In-memory database** (`Mem`) - Fastest performance with data stored in RAM. Perfect for testing, caching, or temporary data. Data is lost when the connection closes.

- **File-based database** (`RocksDb` or `SurrealKV`) - Persistent storage on disk using RocksDB or SurrealKV storage engines. Data persists across connections and application restarts.

## Quick example

```rust
use surrealdb::engine::local::Mem;
use surrealdb::Surreal;

// In-memory database
let db = Surreal::new::<Mem>(()).await?;
db.use_ns("main").use_db("main").await?;
let person = db.create("person").content(Person { name: "John Doe" }).await?;
println!("{:?}", person);

// File-based persistent database (RocksDB)
use surrealdb::engine::local::RocksDb;
let db = Surreal::new::<RocksDb>("./mydb").await?;
db.use_ns("main").use_db("main").await?;
let company = db.create("company").content(Company { name: "TechStart" }).await?;
println!("{:?}", company);
```

## Desktop apps with a web frontend

In a desktop framework such as [Tauri](https://tauri.app), the interface runs in a webview and the application code in Rust. An embedded database runs inside the Rust process and does not listen on a network port, so the [JavaScript SDK](/docs/reference/javascript.md) in the webview has nothing to connect to. There are two ways to give the interface its data:

- **Query from Rust.** Keep the embedded database in the Rust code, and expose the queries the interface needs as Tauri commands, which the webview calls with `invoke`. The database stays private to the application.
- **Run a server.** Start `surreal db start` as a separate process listening on `localhost`, and connect to it from both the Rust code and the JavaScript SDK. Both see the same data, at the cost of a second process to start and stop with the application.

The JavaScript SDK can also [embed its own engine](/docs/build/embedding/by-language/javascript.md) in the webview, but that is a separate database from the one in the Rust process.

For complete documentation, installation instructions, examples, best practices, and troubleshooting, see the [Rust SDK embedding guide](/docs/reference/rust/embedding.md).

---

Source: https://surrealdb.com/docs/build/embedding/storage-engines

# Storage engines

What a storage engine does for SurrealDB. How to think about choosing one.

SurrealDB sits on top of a storage engine - the component that persists keys and values on disk (or in memory) inside transactions. The query engine speaks SurrealQL; the storage engine decides how durability, concurrency, and replication behave underneath.

Different engines optimise for different deployment shapes. A single embedded process cares about latency and simplicity; a distributed cluster cares about fault tolerance and horizontal capacity. The engine you pick ties your operational playbook to concrete behaviours such as compaction, backup and recovery.

Typical options you will see in SurrealDB deployments include RocksDB for mature local disk storage in write-heavy server workloads, SurrealMX (`memory`) for in-process or server in-memory workloads, and SurrealKV for embedded and local-first workloads where lower resident memory and simpler in-process behaviour are priorities. In-memory modes are ideal for tests and scratch work, and can also be used in embedded deployments when you enable Redis-like SurrealMX persistence. See [Deployment models](/docs/manage/self-hosted/deployment-models.md) for how engines map to hosting models.

To choose an engine, start from your hosting model (embedded, single server, cluster) and read about [deployment models](/docs/manage/self-hosted/deployment-models.md) together with [architecture](/docs/learn/data-models/architecture.md). Then follow the embedding guides for your language under [embedding SurrealDB](/docs/build/embedding.md) for what your SDK supports today - capabilities vary by platform and release.

---

Source: https://surrealdb.com/docs/build/integrations

# Integrations

Integrations connecting SurrealDB to AI frameworks and embeddings. Also agents and data tools.

This section collects guides for wiring SurrealDB into the rest of your stack: AI and agent frameworks, embeddings and model providers, data movement (ELT, automation, and similar), and related topics. Each page focuses on a specific tool or pattern so you can find a starting point quickly.

This section assumes a certain level of familiarity with SurrealDB itself. If more general orientation and tutorials are needed first, be sure to start with [What is SurrealDB?](/docs/what-is-surrealdb.md) and the [Querying](/docs/learn/querying.md) section of the docs.

## Backend platforms

- [Encore](/docs/build/integrations/backend-platforms/encore.md) - build and verify a complete TypeScript backend for durable AI agent memory using type-safe API, Pub/Sub and cron primitives with SurrealDB vector search and graph relationships

## Agent rules

- [Agent rules](/docs/build/integrations/agent-rules.md) - rule files that teach an assistant your project's conventions

## Authentication

- [Better Auth](/docs/build/integrations/authentication/better-auth/overview.md) - use SurrealDB as the database behind Better Auth
- [Getting started](/docs/build/integrations/authentication/better-auth/getting-started.md) - wire the adapter up and run your first sign-in
- [Plugins](/docs/build/integrations/authentication/better-auth/plugins.md) - which Better Auth plugins the adapter supports
- [Transactions & limitations](/docs/build/integrations/authentication/better-auth/transactions-and-limitations.md) - what the adapter cannot do yet, and why

## Data management

- [Data management integrations](/docs/build/integrations/data-management/overview.md) - the pipeline and testing tools that connect to SurrealDB
- [Airbyte](/docs/build/integrations/data-management/airbyte.md) - move data in and out with an Airbyte connector
- [Fivetran](/docs/build/integrations/data-management/fivetran.md) - managed pipelines into SurrealDB
- [n8n](/docs/build/integrations/data-management/n8n.md) - automate workflows against a database
- [Qyrus](/docs/build/integrations/data-management/qyrus.md) - test-data generation

## Embeddings providers

- [Embeddings provider integrations](/docs/build/integrations/embeddings-providers/overview.md) - which providers are covered, and what each needs
- [OpenAI](/docs/build/integrations/embeddings-providers/openai.md) - embed with OpenAI models
- [Mistral](/docs/build/integrations/embeddings-providers/mistral.md) - embed with Mistral models
- [Fastembed](/docs/build/integrations/embeddings-providers/fastembed.md) - embed locally, without an API call
- [Python quickstart](/docs/build/integrations/embeddings-providers/python-quickstart.md) - end to end from Python
- [Rust quickstart](/docs/build/integrations/embeddings-providers/rust-quickstart.md) - end to end from Rust

## Vercel

- [Vercel](/docs/build/integrations/vercel.md) - credentials, connection types and schema setup for SurrealDB on Vercel
- [Next.js](/docs/build/integrations/vercel/nextjs.md) - query from Server Components, Server Actions and Route Handlers, with live updates in the browser
- [AI SDK](/docs/build/integrations/vercel/ai-sdk.md) - vector search and graph tools for models, and chat history in SurrealDB
- [Eve](/docs/build/integrations/vercel/eve.md) - tools and a memory provider for eve agents
- [Vercel Sandbox](/docs/build/integrations/vercel/vercel-sandbox.md) - run SurrealDB inside a sandbox, or give sandboxed code read-only access

---

Source: https://surrealdb.com/docs/build/integrations/agent-rules

# Agent rules

Agent rules for working with SurrealDB.

We've prepared a set of agent rules to help your agents generate better code.

- [SurrealQL](https://github.com/surrealdb/docs.surrealdb.com/blob/main/public/integrations/agent-rules/surrealql.mdc)
- [Vector indexes and queries](https://github.com/surrealdb/docs.surrealdb.com/blob/main/public/integrations/agent-rules/surrealdb-vector.mdc)
- [SurrealDB Python SDK](https://github.com/surrealdb/docs.surrealdb.com/blob/main/public/integrations/agent-rules/surrealdb-python.mdc)
- [SurrealDB running embedded in Python](https://github.com/surrealdb/docs.surrealdb.com/blob/main/public/integrations/agent-rules/surrealdb-python-embedded.mdc)

Place these files in your project directory under `.cursor/rules`. If you are using a different IDE, take a look at the following instructions.

## How to configure agent rules on main IDEs:

- [Cursor rules](https://cursor.com/docs/context/rules)
- [Zed rules](https://zed.dev/docs/ai/overview)
- [OpenCode rules](https://opencode.ai/docs/rules/)

If you are working as a team, consider using the Cursor standard and configure your specific IDE accordingly. For example, for OpenCode you can configure your `opencode.json` like this:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "instructions": [".cursor/rules/*.md"]
}
```

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/agno

# Agno

This section contains information about the Agno framework and how to integrate it with SurrealDB.

[Agno](https://github.com/agno-agi/agno) is a python framework for building multi-agent systems with shared memory, knowledge and reasoning.

## Setup

```shell
docker run --rm \
  --pull always \
  -p 8000:8000 \
  surrealdb/surrealdb:latest \
  start \
  --user root \
  --pass secret
```

or

```shell
./cookbook/scripts/run_surrealdb.sh
```

## Example

```python
from agno.agent import Agent
from agno.embedder.openai import OpenAIEmbedder
from agno.knowledge.pdf_url import PDFUrlKnowledgeBase
from agno.vectordb.surrealdb import SurrealDb
from surrealdb import Surreal

# SurrealDB connection parameters
SURREALDB_URL = "ws://localhost:8000"
SURREALDB_USER = "root"
SURREALDB_PASSWORD = "secret"
SURREALDB_NAMESPACE = "main"
SURREALDB_DATABASE = "main"

# Create a client
client = Surreal(url=SURREALDB_URL)
client.signin({"username": SURREALDB_USER,
    "password": SURREALDB_PASSWORD})
client.use(namespace=SURREALDB_NAMESPACE, database=SURREALDB_DATABASE)

surrealdb = SurrealDb(
    client=client,
    collection="recipes",  # Collection name for storing documents
    efc=150,  # HNSW construction time/accuracy trade-off
    m=12,  # HNSW max number of connections per element
    search_ef=40,  # HNSW search time/accuracy trade-off
)


def sync_demo():
    """Demonstrate synchronous usage of SurrealDb"""
    knowledge_base = PDFUrlKnowledgeBase(
        urls=["https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf"],
        vector_db=surrealdb,
        embedder=OpenAIEmbedder(),
    )

    # Load data synchronously
    knowledge_base.load(recreate=True)

    # Create agent and query synchronously
    agent = Agent(knowledge=knowledge_base, show_tool_calls=True)
    agent.print_response(
        "What are the 3 categories of Thai SELECT is given to restaurants overseas?",
        markdown=True,
    )


if __name__ == "__main__":
    # Run synchronous demo
    print("Running synchronous demo...")
    sync_demo()
```

## Developer resources

* View [Cookbook (Sync)](https://docs.agno.com/vectordb/surrealdb)
* View [Cookbook (Async)](https://docs.agno.com/vectordb/surrealdb)
* View [Agno documentation](https://docs.agno.com/vectordb/surrealdb)

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/camel

# CAMEL

This section contains information about the Camel framework and how to integrate it with SurrealDB.

[🐫 CAMEL](https://www.camel-ai.org) is an open-source community dedicated to finding the scaling laws of agents. We believe that studying these agents on a large scale offers valuable insights into their behaviours, capabilities, and potential risks. To facilitate research in this field, we implement and support various types of agents, tasks, prompts, models, and simulated environments.

## Setup

You can run SurrealDB locally or start with a [free SurrealDB Cloud account](/docs/manage/instances.md).

For local, two options:

1. [Install SurrealDB](/docs/running/installation.md) and run [SurrealDB](/docs/running/in-memory.md). Run in-memory with:

    ```sh
    surreal db start -u root -p secret
    ```

2. [Run with Docker](/docs/running/docker.md).

    ```sh
    docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest start
    ```

## Example

```python
import os

from camel.storages.vectordb_storages import (
    SurrealStorage,
    VectorDBQuery,
    VectorRecord,
)


def main():
    url = os.getenv("SURREAL_URL", "ws://localhost:8000/rpc")
    table = os.getenv("SURREAL_TABLE", "tb")
    vector_dim = int(os.getenv("SURREAL_VECTOR_DIM", 4))
    namespace = os.getenv("SURREAL_NAMESPACE", "ns")
    database = os.getenv("SURREAL_DATABASE", "db")
    user = os.getenv("SURREAL_USER", "user")
    password = os.getenv("SURREAL_PASSWORD")

    # Raise an error if password is not set
    if not password:
        raise ValueError(
            "Environment variable SURREAL_PASSWORD is not set. "
            "Please set it before running."
        )

    # Initialize the SurrealStorage instance with provided parameters
    storage = SurrealStorage(
        url=url,
        table=table,
        namespace=namespace,
        database=database,
        user=user,
        password=password,
        vector_dim=vector_dim,
    )

    # Clear existing data in storage
    storage.clear()

    # Print the current status after clearing
    print("[Step 1] After clear:", storage.status())

        vec1 = VectorRecord(vector=[1, 2, 3, 4],
        payload={"name": "test_1"})
        vec2 = VectorRecord(vector=[5, 6, 7, 8],
        payload={"name": "test_2"})
        vec3 = VectorRecord(vector=[9, 10, 11, 12],
        payload={"name": "test_3"})
        vec4 = VectorRecord(vector=[13, 14, 15, 16],
        payload={"name": "test_4"})
    storage.add([vec1, vec2, vec3, vec4])
    print("[Step 2] After add:", storage.status())

    res = storage.client.query_raw(
        "SELECT * FROM lyz_tb WHERE payload.name = 'test_3';"
    )["result"][0]["result"][0]["id"].id
    print("[Step 3] Query Result ID for 'test_3':", res)

    storage.delete(ids=[res])
    print("[Step 4] After delete 'test_3':", storage.status())

    res = storage.query(
        VectorDBQuery(query_vector=[1.1, 2.1, 3.1, 4.1], top_k=2)
    )
    print("[Step 5] Vector Query Result:", res)
```

The output should look like this:

```text
[Step 1] After clear: vector_dim=4 vector_count=0oceanbasech
[Step 2] After add: vector_dim=4 vector_count=4
[Step 3] Query Result ID for 'test_3': lov5h16x6uog7l2xtsqp
[Step 4] After delete 'test_3': vector_dim=4 vector_count=3
[Step 5] Vector Query Result:
[VectorDBQueryResult(record=VectorRecord(vector=[],
id='9803ae3a-18da-4152-a522-48e1939a3604',
payload={'name': 'test_2'}), similarity=0.027665393972965968),
VectorDBQueryResult(record=VectorRecord(vector=[],
id='de3d085c-ed1b-4d23-9d12-d9fc64ae1e00',
payload={'name': 'test_1'}), similarity=0.00010404203326297434)]
```

## Resources

* [Camel documentation](https://docs.camel-ai.org/)

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/cocoindex

# CocoIndex

Incremental data pipelines for AI agents with declarative SurrealDB graph and vector targets.

[CocoIndex](https://cocoindex.io) is an incremental indexing framework for AI agents and LLM applications. You declare what should exist in a target store - documents, embeddings, knowledge graphs - and CocoIndex keeps it in sync, reprocessing only the delta on each run.

The SurrealDB connector writes to normal tables, relation (graph edge) tables, and vector indexes. CocoIndex tracks declared records across runs: it upserts changes, skips unchanged records, and removes records that are no longer declared. Related tables in the same database reconcile inside a single atomic transaction.

## How it works

1. **Declare sources** - Walk local files, S3, Google Drive, and other connectors; transform with chunking, embeddings, or LLM extraction.
2. **Declare targets** - Mount SurrealDB `TableTarget` and `RelationTarget` states with optional `TableSchema` (SCHEMAFULL) or schemaless tables.
3. **Reconcile** - On each run, CocoIndex compares the declared state with the previous run and applies upserts and deletions. Schema and vector indexes can be managed automatically when `managed_by` is `"system"`.
4. **Query** - Use SurrealQL for full-text search, graph traversals, and vector similarity on the resulting data. All data can be manually queried at its namespace and database in the same way as with any other SurrealDB instance.

## Key capabilities

- **Incremental sync** - Memoised pipeline steps and target-state reconciliation avoid reprocessing unchanged inputs.
- **Graph-native writes** - Relation tables with polymorphic `from` / `to` endpoints map cleanly to SurrealDB `RELATE` edges.
- **Schema lifecycle** - Optional `TableSchema` with `ColumnDef`; CocoIndex can define fields and drop undeclared columns on re-run.
- **Vector indexes** - Declare HNSW indexes on embedding fields; metric and dimension changes trigger index recreation. Pipelines can embed locally with `SentenceTransformerEmbedder` (Rust uses [FastEmbed](https://crates.io/crates/fastembed) internally) so you can exercise vector targets without requiring an API key.
- **Python and Rust** - Pipelines are typically authored in Python; the Rust SDK exposes the same target-state model for native binaries and examples.

## Local embeddings

CocoIndex pipelines that write vectors to SurrealDB often use `SentenceTransformerEmbedder` - models run on your machine and download once, similar to the zero-key graph demos below. The Rust SDK loads them via FastEmbed; Python uses the `sentence-transformers` library with the same Hugging Face model names. The full [`conversation_to_knowledge`](https://github.com/cocoindex-io/cocoindex/tree/main/examples/conversation_to_knowledge) example uses this for entity resolution.

To sanity-check local embeddings with SurrealDB directly - or to pick a model before you wire up a CocoIndex pipeline - see the [FastEmbed integration guide](/docs/build/integrations/embeddings-providers/fastembed.md). It covers ONNX models, vector dimensions, and worked examples in Python and Rust without an API key.

## Podcast → knowledge graph

CocoIndex's flagship SurrealDB example is [`conversation_to_knowledge`](https://github.com/cocoindex-io/cocoindex/tree/main/examples/conversation_to_knowledge), in which podcast episodes become a graph of **sessions**, **statements**, **people**, **technologies**, **organisations**, and **mention** edges.

| Input | What happens |
|-------|----------------|
| `input/*.txt` (YouTube URLs) | **yt-dlp** downloads audio → **AssemblyAI** transcribes with speaker labels → **LLM** extracts claims and entities → entity resolution → SurrealDB graph |
| `input/*.json` (pre-transcribed) | Skips download/transcription; still uses LLM extraction in the full example |

The zero-key demos below use the same interview with musician and YouTuber Rick Beato and Alice in Chains guitarist Jerry Cantrell ([YouTube link](https://www.youtube.com/watch?v=vBlfo0GVqqE)) with a pre-transcribed [`input/sample.json`](#appendix-samplejson). Curated statements stand in for LLM extraction so you can see reconciliation without API keys. CocoIndex does **not** fetch transcripts in these demos.

When connecting with SurrealDB Studio or `surreal db sql`, use the **same namespace and database** your program configures - for example `cocoindex` / `beato_cantrell`, unless you set that explicitly.

## Getting started

Start SurrealDB:

```bash title="Run SurrealDB"
surreal db start --user root --pass secret
```

**Python**

Install CocoIndex with the SurrealDB extra:

```bash title="Install"
pip install "cocoindex[surrealdb]"
```

Copy [`input/sample.json`](#appendix-samplejson) into an `input` directory next to `main.py`. The script mounts `session`, `statement`, `person`, `tech`, and `org` tables plus `session_statement`, `person_session`, `person_statement`, and polymorphic `statement_mentions` relations - the same shape as the full podcast example.

```bash title="Run (twice - second pass reconciles away the Sabbath branch)"
export COCOINDEX_DB=/tmp/cocoindex_beato
export SURREALDB_URL=ws://127.0.0.1:8000/rpc
export SURREALDB_NS=cocoindex
export SURREALDB_DB=beato_cantrell
INCLUDE_SABBATH=1 python main.py
INCLUDE_SABBATH=0 python main.py
```

`COCOINDEX_DB` stores CocoIndex's local change-tracking state between runs. `INCLUDE_SABBATH=1` declares the Tony Iommi / Black Sabbath influence branch; setting it to `0` on the second run removes those nodes and edges.

```python title="main.py"
"""Rick Beato × Jerry Cantrell → SurrealDB knowledge graph (no API keys)."""
from __future__ import annotations

import asyncio
import json
import os
from collections.abc import AsyncIterator
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import cocoindex as coco
from cocoindex.connectors import surrealdb

SURREAL_DB = coco.ContextKey[surrealdb.ConnectionFactory]("surreal_db")
SESSION_ID = 100
INCLUDE_SABBATH = os.environ.get("INCLUDE_SABBATH", "1") != "0"


@dataclass
class Session:
    id: int
    youtube_id: str
    name: str
    transcript: str
    description: str | None = None
    date: str | None = None


@dataclass
class Statement:
    id: int
    statement: str


@dataclass
class Entity:
    id: str
    name: str


def statement_seeds(include_sabbath: bool) -> list[dict[str, Any]]:
    seeds = [
        {
            "id": 200,
            "text": "Songwriting begins with collecting riffs; most Alice in Chains songs start from a riff idea curated over time.",
            "speakers": ["Jerry Cantrell"],
            "persons": ["Jerry Cantrell"],
            "techs": ["Riffs", "Songwriting"],
            "orgs": ["Alice in Chains"],
        },
        # ...talk box, Seattle scene, and recording-vibe statements...
    ]
    if include_sabbath:
        seeds.append(
            {
                "id": 204,
                "text": "Tony Iommi and Black Sabbath were foundational guitar influences; Dave Jerden helped expand Alice in Chains' studio sound.",
                "speakers": ["Jerry Cantrell"],
                "persons": ["Jerry Cantrell", "Tony Iommi", "Dave Jerden"],
                "techs": ["Guitar tone"],
                "orgs": ["Black Sabbath", "Alice in Chains"],
            }
        )
    return seeds


@coco.lifespan
async def coco_lifespan(builder: coco.EnvironmentBuilder) -> AsyncIterator[None]:
    builder.provide(
        SURREAL_DB,
        surrealdb.ConnectionFactory(
            url=os.environ.get("SURREALDB_URL", "ws://127.0.0.1:8000/rpc"),
            namespace=os.environ.get("SURREALDB_NS", "cocoindex"),
            database=os.environ.get("SURREALDB_DB", "beato_cantrell"),
            credentials={
                "username": os.environ.get("SURREALDB_USER", "root"),
                "password": os.environ.get("SURREALDB_PASS", "secret"),
            },
        ),
    )
    yield


@coco.fn(memo=True)
async def declare_graph() -> None:
    session_data = json.loads(Path("input/sample.json").read_text())
    transcript = "\n".join(
        f"({u['speaker']}) {u['text']}" for u in session_data["utterances"]
    )
    seeds = statement_seeds(INCLUDE_SABBATH)

    session_table = await surrealdb.mount_table_target(
        SURREAL_DB, "session", await surrealdb.TableSchema.from_class(Session)
    )
    statement_table = await surrealdb.mount_table_target(
        SURREAL_DB, "statement", await surrealdb.TableSchema.from_class(Statement)
    )
    entity_schema = await surrealdb.TableSchema.from_class(Entity)
    person_table = await surrealdb.mount_table_target(SURREAL_DB, "person", entity_schema)
    tech_table = await surrealdb.mount_table_target(SURREAL_DB, "tech", entity_schema)
    org_table = await surrealdb.mount_table_target(SURREAL_DB, "org", entity_schema)

    session_statement = await surrealdb.mount_relation_target(
        SURREAL_DB, "session_statement", session_table, statement_table
    )
    person_session = await surrealdb.mount_relation_target(
        SURREAL_DB, "person_session", person_table, session_table
    )
    person_statement = await surrealdb.mount_relation_target(
        SURREAL_DB, "person_statement", person_table, statement_table
    )
    statement_mentions = await surrealdb.mount_relation_target(
        SURREAL_DB,
        "statement_mentions",
        statement_table,
        [person_table, tech_table, org_table],
    )

    session_table.declare_record(
        row=Session(
            id=SESSION_ID,
            youtube_id=session_data["id"],
            name=session_data["title"],
            description=session_data.get("description"),
            transcript=transcript,
            date=session_data.get("date"),
        )
    )

    people: set[str] = {"Rick Beato", "Jerry Cantrell"}
    techs: set[str] = set()
    orgs: set[str] = set()

    for seed in seeds:
        statement_table.declare_record(
            row=Statement(id=seed["id"], statement=seed["text"])
        )
        session_statement.declare_relation(from_id=SESSION_ID, to_id=seed["id"])
        for speaker in seed["speakers"]:
            people.add(speaker)
            person_statement.declare_relation(from_id=speaker, to_id=seed["id"])
        people.update(seed["persons"])
        techs.update(seed["techs"])
        orgs.update(seed["orgs"])
        for person in seed["persons"]:
            statement_mentions.declare_relation(
                from_id=seed["id"], to_id=person, to_table=person_table
            )
        for tech in seed["techs"]:
            statement_mentions.declare_relation(
                from_id=seed["id"], to_id=tech, to_table=tech_table
            )
        for org in seed["orgs"]:
            statement_mentions.declare_relation(
                from_id=seed["id"], to_id=org, to_table=org_table
            )

    for name in people:
        person_table.declare_record(row=Entity(id=name, name=name))
        person_session.declare_relation(from_id=name, to_id=SESSION_ID)
    for name in techs:
        tech_table.declare_record(row=Entity(id=name, name=name))
    for name in orgs:
        org_table.declare_record(row=Entity(id=name, name=name))


app = coco.App(coco.AppConfig(name="beato_cantrell_demo"), declare_graph)

if __name__ == "__main__":
    asyncio.run(app.update())
```

For LLM extraction, entity resolution, and live YouTube ingestion, follow CocoIndex's [podcast-to-knowledge-graph tutorial](https://cocoindex.io/docs/examples/podcast-to-knowledge-graph/) and the [`conversation_to_knowledge`](https://github.com/cocoindex-io/cocoindex/tree/main/examples/conversation_to_knowledge) source. The [SurrealDB connector reference](https://cocoindex.io/docs/connectors/surrealdb) covers connection setup, `TableSchema.from_class`, vector indexes, and relation tables in full. For local embedding models, see [FastEmbed](/docs/build/integrations/embeddings-providers/fastembed.md).

**Rust**

The Rust SDK is not yet published on [crates.io](https://crates.io); use a path or git dependency on the [CocoIndex repository](https://github.com/cocoindex-io/cocoindex) (`rust/sdk/cocoindex`, feature `surrealdb`).

Copy [`input/sample.json`](#appendix-samplejson) into an `input` directory next to your Rust project and depend on the local SDK:

```toml title="Cargo.toml"
[dependencies]
cocoindex = { path = "../cocoindex/rust/sdk/cocoindex", features = ["surrealdb"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tempfile = "3"
```

```bash title="Run"
export SURREALDB_URL=127.0.0.1:8000
export SURREALDB_NS=cocoindex
export SURREALDB_DB=beato_cantrell
cargo run
```

The program runs twice in one invocation - the second pass drops the Tony Iommi / Black Sabbath branch to show reconciliation. It mounts the same tables and relations as the Python example above.

For LLM extraction, entity resolution, and live YouTube ingestion, use the full [`conversation_to_knowledge` Rust example](https://github.com/cocoindex-io/cocoindex/tree/main/examples/rust/conversation_to_knowledge) (`ASSEMBLYAI_API_KEY`, `OPENAI_API_KEY`, `yt-dlp`). For vector indexes and schema evolution tests, see the [SDK SurrealDB tests](https://github.com/cocoindex-io/cocoindex/blob/main/rust/sdk/cocoindex/tests/surrealdb_target.rs). For local embedding models outside CocoIndex, see [FastEmbed](/docs/build/integrations/embeddings-providers/fastembed.md).

Code:

```rust
//! Rick Beato × Jerry Cantrell → SurrealDB knowledge graph (no API keys).
//!
//! Loads a pre-transcribed interview JSON (`input/sample.json`, sourced from
//! YouTube captions for https://www.youtube.com/watch?v=vBlfo0GVqqE) and
//! declares sessions, statements, people, gear/techniques, bands, and mention
//! edges. Curated claims stand in for LLM extraction in the full CocoIndex pipeline.

use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::LazyLock;

use cocoindex::prelude::*;
use cocoindex::surrealdb::{self, ColumnDef, Graph, TableSchema};
use serde::Deserialize;

static GRAPH: LazyLock<ContextKey<Graph>> = LazyLock::new(|| ContextKey::new("main_db"));

const PERSON: &str = "person";
const TECH: &str = "tech";
const ORG: &str = "org";

const SESSION_ID: i64 = 100;
const STMT_RIFFS: i64 = 200;
const STMT_TALKBOX: i64 = 201;
const STMT_SEATTLE: i64 = 202;
const STMT_VIBE: i64 = 203;
const STMT_SABBATH: i64 = 204;

#[derive(Debug, Clone, Deserialize)]
struct LocalInput {
    id: String,
    title: String,
    #[serde(default)]
    description: Option<String>,
    #[serde(default)]
    date: Option<String>,
    utterances: Vec<Utterance>,
}

#[derive(Debug, Clone, Deserialize)]
struct Utterance {
    speaker: String,
    text: String,
}

/// Curated claims - in the full CocoIndex pipeline an LLM extracts these from the transcript.
struct StatementSeed {
    id: i64,
    text: &'static str,
    speakers: &'static [&'static str],
    persons: &'static [&'static str],
    techs: &'static [&'static str],
    orgs: &'static [&'static str],
}

fn statement_seeds(include_sabbath_branch: bool) -> Vec<StatementSeed> {
    let mut seeds = vec![
        StatementSeed {
            id: STMT_RIFFS,
            text: "Songwriting begins with collecting riffs; most Alice in Chains songs start from a riff idea curated over time.",
            speakers: &["Jerry Cantrell"],
            persons: &["Jerry Cantrell"],
            techs: &["Riffs", "Songwriting"],
            orgs: &["Alice in Chains"],
        },
        StatementSeed {
            id: STMT_TALKBOX,
            text: "The I Want Blood sessions leaned on talk box tones, including a Heil talk box and a Jeff Beck-style bag talk box.",
            speakers: &["Jerry Cantrell"],
            persons: &["Jerry Cantrell", "Jeff Beck"],
            techs: &["Talk box", "Heil talk box", "Guitar layering"],
            orgs: &[],
        },
        StatementSeed {
            id: STMT_SEATTLE,
            text: "The early Seattle scene had real camaraderie between bands, and Alice in Chains rarely stockpiled unused songs.",
            speakers: &["Jerry Cantrell"],
            persons: &["Jerry Cantrell"],
            techs: &[],
            orgs: &["Alice in Chains", "Seattle grunge scene"],
        },
        StatementSeed {
            id: STMT_VIBE,
            text: "Recording is about catching the vibe, not perfection - rock and roll finds the audience it is meant to reach.",
            speakers: &["Jerry Cantrell"],
            persons: &["Jerry Cantrell"],
            techs: &["I Want Blood"],
            orgs: &[],
        },
    ];
    if include_sabbath_branch {
        seeds.push(StatementSeed {
            id: STMT_SABBATH,
            text: "Tony Iommi and Black Sabbath were foundational guitar influences; Dave Jerden helped expand Alice in Chains' studio sound.",
            speakers: &["Jerry Cantrell"],
            persons: &["Jerry Cantrell", "Tony Iommi", "Dave Jerden"],
            techs: &["Guitar tone"],
            orgs: &["Black Sabbath", "Alice in Chains"],
        });
    }
    seeds
}

fn load_session() -> Result<LocalInput> {
    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("input/sample.json");
    let raw = std::fs::read_to_string(&path)
        .map_err(|e| Error::engine(format!("read {}: {e}", path.display())))?;
    serde_json::from_str(&raw).map_err(|e| Error::engine(format!("parse sample.json: {e}")))
}

fn transcript_text(session: &LocalInput) -> String {
    session
        .utterances
        .iter()
        .map(|u| format!("({}) {}", u.speaker, u.text))
        .collect::<Vec<_>>()
        .join("\n")
}

async fn build_graph(app: &App, session: &LocalInput, include_sabbath_branch: bool) -> Result<()> {
    let seeds = statement_seeds(include_sabbath_branch);
    let transcript = transcript_text(session);
    let session = session.clone();

    let stats = app
        .run(move |ctx| {
            let seeds = seeds;
            let session = session;
            let transcript = transcript;
            async move {
                let session_target = surrealdb::mount_table_target_with_schema(
                    &ctx,
                    &GRAPH,
                    "session",
                    Some(TableSchema::new([
                        ("youtube_id", ColumnDef::new("string")),
                        ("name", ColumnDef::new("string")),
                        ("description", ColumnDef::new("string").nullable()),
                        ("transcript", ColumnDef::new("string")),
                        ("date", ColumnDef::new("string").nullable()),
                    ])?),
                )
                .await?;
                let statement_target = surrealdb::mount_table_target_with_schema(
                    &ctx,
                    &GRAPH,
                    "statement",
                    Some(TableSchema::new([("statement", ColumnDef::new("string"))])?),
                )
                .await?;
                let entity_schema = TableSchema::new([("name", ColumnDef::new("string"))])?;
                let person_target = surrealdb::mount_table_target_with_schema(
                    &ctx,
                    &GRAPH,
                    PERSON,
                    Some(entity_schema.clone()),
                )
                .await?;
                let tech_target = surrealdb::mount_table_target_with_schema(
                    &ctx,
                    &GRAPH,
                    TECH,
                    Some(entity_schema.clone()),
                )
                .await?;
                let org_target = surrealdb::mount_table_target_with_schema(
                    &ctx,
                    &GRAPH,
                    ORG,
                    Some(entity_schema),
                )
                .await?;

                let session_statement = surrealdb::mount_relation_target(
                    &ctx,
                    &GRAPH,
                    "session_statement",
                    &session_target,
                    &statement_target,
                )
                .await?;
                let person_session = surrealdb::mount_relation_target(
                    &ctx,
                    &GRAPH,
                    "person_session",
                    &person_target,
                    &session_target,
                )
                .await?;
                let person_statement = surrealdb::mount_relation_target(
                    &ctx,
                    &GRAPH,
                    "person_statement",
                    &person_target,
                    &statement_target,
                )
                .await?;
                let statement_mentions = surrealdb::mount_relation_target_many(
                    &ctx,
                    &GRAPH,
                    "statement_mentions",
                    &[&statement_target],
                    &[&person_target, &tech_target, &org_target],
                    None,
                )
                .await?;

                session_target.declare_record(
                    &ctx,
                    SESSION_ID,
                    &serde_json::json!({
                        "youtube_id": session.id,
                        "name": session.title,
                        "description": session.description,
                        "transcript": transcript,
                        "date": session.date,
                    }),
                )?;

                let mut people: HashSet<&str> = HashSet::from(["Rick Beato", "Jerry Cantrell"]);
                let mut techs: HashSet<&str> = HashSet::new();
                let mut orgs: HashSet<&str> = HashSet::new();

                for seed in &seeds {
                    statement_target.declare_record(
                        &ctx,
                        seed.id,
                        &serde_json::json!({ "statement": seed.text }),
                    )?;
                    session_statement.declare_relation(&ctx, SESSION_ID, seed.id)?;

                    for speaker in seed.speakers {
                        people.insert(speaker);
                        person_statement.declare_relation(&ctx, *speaker, seed.id)?;
                    }
                    for person in seed.persons {
                        people.insert(person);
                    }
                    for tech in seed.techs {
                        techs.insert(tech);
                    }
                    for org in seed.orgs {
                        orgs.insert(org);
                    }
                    for person in seed.persons {
                        statement_mentions.declare_relation_between(
                            &ctx,
                            "statement",
                            seed.id,
                            PERSON,
                            *person,
                        )?;
                    }
                    for tech in seed.techs {
                        statement_mentions.declare_relation_between(
                            &ctx,
                            "statement",
                            seed.id,
                            TECH,
                            *tech,
                        )?;
                    }
                    for org in seed.orgs {
                        statement_mentions.declare_relation_between(
                            &ctx,
                            "statement",
                            seed.id,
                            ORG,
                            *org,
                        )?;
                    }
                }

                for name in people {
                    person_target.declare_record(
                        &ctx,
                        name,
                        &serde_json::json!({ "name": name }),
                    )?;
                    person_session.declare_relation(&ctx, name, SESSION_ID)?;
                }
                for name in techs {
                    tech_target.declare_record(&ctx, name, &serde_json::json!({ "name": name }))?;
                }
                for name in orgs {
                    org_target.declare_record(&ctx, name, &serde_json::json!({ "name": name }))?;
                }

                Ok(())
            }
        })
        .await?;

    println!("  {stats}");
    Ok(())
}

async fn print_counts(graph: &Graph) -> Result<()> {
    println!(
        "  session: {}, statement: {}, person: {}, tech: {}, org: {}, mentions: {}",
        graph.count("session").await?,
        graph.count("statement").await?,
        graph.count(PERSON).await?,
        graph.count(TECH).await?,
        graph.count(ORG).await?,
        graph.count("statement_mentions").await?,
    );
    Ok(())
}

fn env_or(key: &str, default: &str) -> String {
    std::env::var(key).unwrap_or_else(|_| default.to_string())
}

fn print_query_hints() {
    println!("\nInspect in SurrealDB Studio (or surreal db sql with `surreal db sql --user root --pass secret --ns cocoindex --db beato_cantrell`):");
    println!("  namespace: cocoindex");
    println!("  database:  beato_cantrell");
    println!("\nExample queries:");
    println!("  SELECT name FROM person;");
    println!("  SELECT statement FROM statement WHERE statement CONTAINS 'riff';");
    println!("  SELECT statement FROM statement WHERE statement CONTAINS 'talk box';");
    println!(
        "  SELECT id, ->statement_mentions->org.name AS bands FROM statement WHERE ->statement_mentions->org;"
    );
}

#[tokio::main]
async fn main() -> Result<()> {
    let session = load_session()?;
    println!(
        "Loaded transcript: {} ({} utterances)\n",
        session.title,
        session.utterances.len()
    );

    let graph = Graph::connect(
        &env_or("SURREALDB_URL", "127.0.0.1:8000"),
        &env_or("SURREALDB_NS", "cocoindex"),
        &env_or("SURREALDB_DB", "beato_cantrell"),
        &env_or("SURREALDB_USER", "root"),
        &env_or("SURREALDB_PASS", "secret"),
    )
    .await?;

    let dir = tempfile::tempdir()?;
    let app = Environment::builder()
        .db_path(dir.path().join("cocoindex_lmdb"))
        .provide_key(&GRAPH, graph.clone())
        .build()
        .await?
        .app("beato_cantrell_demo")
        .await?;

    println!("Run 1 - full graph (includes Tony Iommi / Black Sabbath branch)");
    build_graph(&app, &session, true).await?;
    print_counts(&graph).await?;

    println!("\nRun 2 - narrower extraction (Sabbath influence branch reconciled away)");
    build_graph(&app, &session, false).await?;
    print_counts(&graph).await?;

    print_query_hints();
    Ok(())
}
```

## Inspect the graph

After either demo completes, open SurrealDB Studio with namespace **cocoindex** and database **beato_cantrell**. The schema designer shows the session-centric graph CocoIndex declared:

![SurrealDB Studio schema designer showing session, statement, person, tech, and org tables with session_statement, person_session, person_statement, and statement_mentions relation tables after the Beato - Cantrell demo.](~/assets/img/integrations/cocoindex_schema.png)

Example queries:

```surql title="Example queries"
SELECT name FROM person;
SELECT statement FROM statement WHERE statement CONTAINS 'riff';
SELECT statement FROM statement WHERE statement CONTAINS 'talk box';
SELECT ->statement_mentions->org.name AS bands FROM statement WHERE statement CONTAINS 'Alice';
```

After the second run (`INCLUDE_SABBATH=0` in Python, or the built-in second pass in Rust), the Tony Iommi / Black Sabbath statement and its mention edges are gone - reconciliation removed anything no longer declared.

## Appendix: sample.json

Create an `input` directory next to your demo program and save the following as `input/sample.json`. Utterance excerpts were polished from YouTube auto-captions for the [Rick Beato × Jerry Cantrell interview](https://www.youtube.com/watch?v=vBlfo0GVqqE).

<details>
<summary>Show <code>input/sample.json</code></summary>

```json title="input/sample.json"
{
  "id": "vBlfo0GVqqE",
  "title": "Jerry Cantrell: Creating the Iconic Sound of Alice In Chains",
  "channel": "Rick Beato",
  "description": "Rick Beato interviews Jerry Cantrell about Alice in Chains, songwriting, guitar tones, the Seattle scene, and the solo album I Want Blood.",
  "date": "2024-09-17",
  "utterances": [
    {
      "speaker": "A",
      "text": "Jerry, welcome. I'm listening to your new solo record and it has all these great riffs. Do you just sit around and put ideas on your phone? How do you keep track of everything?"
    },
    {
      "speaker": "B",
      "text": "I'm probably a collector and curator of riffs. It always starts there. Sometimes I'll hum something into a phone, but most songs begin when I stumble across a riff and think, that's cool."
    },
    {
      "speaker": "A",
      "text": "Walk me through how a riff becomes an Alice in Chains song."
    },
    {
      "speaker": "B",
      "text": "The music always comes first to me. It begins with the riff and then it develops into what I think is a piece of music that makes a great song."
    },
    {
      "speaker": "B",
      "text": "We leaned heavily on the talk box on this record - the Heil talk box and some older flavors I had not hit that hard in a while, plus a Jeff Beck-style bag talk box I had never played before."
    },
    {
      "speaker": "A",
      "text": "Let's talk about the Seattle scene when Dirt and Jar of Flies were happening."
    },
    {
      "speaker": "B",
      "text": "Seattle at that time had a real camaraderie between bands. We were all pushing each other, but Alice in Chains always put out what we wrote - no pile of unused B-sides sitting around."
    },
    {
      "speaker": "A",
      "text": "Your new solo album is called I Want Blood. How is that different from a band record?"
    },
    {
      "speaker": "B",
      "text": "It's a collaborative thing whether I'm in the band or on a solo record. There's a whole group of people and a producer - it's still a band effort, just with my name on the cover."
    },
    {
      "speaker": "B",
      "text": "It's not about perfection, it's about catching the vibe. Rock and roll is not supposed to be for everybody - it finds the people it is meant to speak to."
    },
    {
      "speaker": "A",
      "text": "Who were the guitar influences that shaped your sound?"
    },
    {
      "speaker": "B",
      "text": "Tony Iommi and Black Sabbath were huge for me early on - that dark, heavy feel. Jimmy Page too, and the way producers like Dave Jerden helped expand what we could do in the studio."
    }
  ]
}
```

</details>

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/crewai

# CrewAI

How to plug SurrealDB into CrewAI as a memory and vector-search layer.

[CrewAI](https://github.com/joaomdmoura/crewAI) lets you orchestrate role-playing AI agents that collaborate to complete complex tasks.
This guide shows how to use SurrealDB as the memory backend for CrewAI, giving agents:

- **Entity memory** for domain objects (products, people, places)
- **Short-term memory** for recent conversations
- **Vector search** to recall relevant information by semantic similarity

The sample below creates two agents that recommend music festival trips:

1. **Researcher** - finds festivals and saves them to SurrealDB
2. **Planner** - queries those saved festivals to build a weekend itinerary

## Install

```bash
# CrewAI, SurrealDB Python SDK, and an embedder (OpenAI here; swap if you like)
pip install "crewai[tools]" surrealdb openai

# (optional) run SurrealDB locally - single binary, no deps
docker run --pull always -p 8000:8000 surrealdb/surrealdb:latest \
       start --user root --pass secret file:/data/db
````

SurrealDB v2 ships native HNSW indexes, so you get ANN vector search without an extra service.

## SurrealDB storage adapter

Create `src/mycrew/surreal_storage.py`:

```python
from __future__ import annotations

import asyncio, hashlib, logging, os, threading
from typing import Any, Dict, List, Optional

import openai
from crewai.memory.storage.rag_storage import RAGStorage
from surrealdb import Surreal, AsyncSurreal

logger = logging.getLogger(__name__)
_EMBED_DIM = 1536  # OpenAI text-embedding-3-small


# ────────────────────────── embeddings ──────────────────────────
def _embed(text: str,
           api_key: Optional[str] = None,
           model: str = "text-embedding-3-small",
           dimensions: int = _EMBED_DIM) -> List[float]:
    """Return an L2-normalised embedding vector."""
    resp = openai.Embedding.create(
        model=model,
        input=[text],
        api_key=api_key or os.getenv("OPENAI_API_KEY"),
        dimensions=dimensions,
    )
    vec = resp["data"][0]["embedding"]
    norm = sum(x * x for x in vec) ** 0.5 or 1.0
    return [x / norm for x in vec]


# ─────────────────────────── storage ────────────────────────────
class SurrealStorage(RAGStorage):
    """CrewAI RAGStorage backend backed by SurrealDB v2."""

    _lock = threading.Lock()
    _tables_ready: set[str] = set()

    def __init__(
        self,
        typ: str,
        *,
        allow_reset: bool = True,
        embedder_config: Optional[Dict[str, Any]] = None,
        crew: Optional[Any] = None,
        url: str = "ws://localhost:8000/rpc",
        namespace: str = "crew",
        database: str = "memories",
        user: str = "root",
        password: str = "secret",
    ):
        super().__init__(typ, allow_reset, embedder_config, crew)
        self.url, self.ns, self.db = url, namespace, database
        self.user, self.pw = user, password
        self.table = f"mem_{typ.replace('-', '_')}"
        self._adb = AsyncSurreal(url)   # async client
        self._sdb = Surreal(url)        # sync client (DDL)
        self._embed_api_key = (embedder_config or {}).get("api_key")

    # ─────────── helpers ───────────
    def _run(self, coro):
        """Run *coro* in any context (sync script or async crew)."""
        try:
            loop = asyncio.get_running_loop()
            if loop.is_running():
                return asyncio.ensure_future(coro)
        except RuntimeError:
            pass
        return asyncio.run(coro)

    # ─────────── sync API (called by CrewAI) ───────────
    def save(self, value: Any, metadata: Dict[str, Any]):
        self._run(self._save_async(value, metadata))

    def search(
        self,
        query: str,
        limit: int = 3,
        filter: Optional[Dict[str, Any]] = None,
        score_threshold: float = 0.0,
    ):
        return self._run(
            self._search_async(query, limit, filter, score_threshold)
        )

    def reset(self):
        self._run(self._reset_async())

    # ─────────── async internals ───────────
    async def _save_async(self, value: Any, metadata: Dict[str, Any]):
        await self._ensure_schema()
        vec = _embed(str(value), self._embed_api_key)
        rec = {
            "id": hashlib.sha1(str(value).encode()).hexdigest(),
            "text": value,
            "metadata": metadata or {},
            "embedding": vec,
        }
        async with self._adb as db:
            await db.signin({"username": self.user, "password": self.pw})
            await db.use(self.ns, self.db)
            await db.create(self.table, rec)

    async def _search_async(
        self,
        query: str,
        limit: int,
        filter: Optional[Dict[str, Any]],
        score_threshold: float,
    ):
        await self._ensure_schema()
        vec = _embed(query, self._embed_api_key)

        filter_params, where_clause = {}, ""
        if filter:
            conds = []
            for i, (k, v) in enumerate(filter.items()):
                pname = f"f{i}"
                conds.append(f"metadata.{k} == ${pname}")
                filter_params[pname] = v
            where_clause = " AND " + " AND ".join(conds)

        async with self._adb as db:
            await db.signin({"username": self.user, "password": self.pw})
            await db.use(self.ns, self.db)
            rs = await db.query(
                f"""
                SELECT *,
                       vector::distance::knn() AS score
                FROM {self.table}
                WHERE embedding <|{limit}|> $vec{where_clause}
                ORDER BY score ASC;
                """,
                {"vec": vec, **filter_params},
            )
        rows = rs[0]["result"]
        return [
            {
                "id": r["id"],
                "metadata": r["metadata"],
                "context": r["text"],
                "score": r["score"],
            }
            for r in rows
            if (score_threshold == 0 or r["score"] <= score_threshold)
        ]

    async def _reset_async(self):
        async with self._adb as db:
            await db.signin({"username": self.user, "password": self.pw})
            await db.use(self.ns, self.db)
            await db.query(f"REMOVE TABLE {self.table};")
        self._tables_ready.discard(self.table)

    # ─────────── table / index setup ───────────
    async def _ensure_schema(self):
        if self.table in self._tables_ready:
            return
        with self._lock:
            if self.table in self._tables_ready:
                return
            self._sdb.signin({"username": self.user, "password": self.pw})
            self._sdb.use(self.ns, self.db)
            self._sdb.query(
                f"""
                DEFINE TABLE {self.table} SCHEMALESS;
                DEFINE FIELD id        ON {self.table} TYPE string;
                DEFINE FIELD text      ON {self.table} TYPE string;
                DEFINE FIELD metadata  ON {self.table} TYPE object;
                DEFINE FIELD embedding ON {self.table} TYPE array;
                DEFINE INDEX IF NOT EXISTS {self.table}_vec_idx
                  ON {self.table} FIELDS embedding
                  HNSW DIMENSION {_EMBED_DIM} DIST COSINE;
                """
            )
            self._tables_ready.add(self.table)
```

## Wire it into a crew

Create `src/mycrew/crew.py`:

```python
import asyncio, logging
from typing import Optional

from crewai import Agent, Task, Crew
from crewai.memory.entity.entity_memory import EntityMemory
from crewai.memory.short_term.short_term_memory import ShortTermMemory

from mycrew.surreal_storage import SurrealStorage, _EMBED_DIM

logging.basicConfig(level=logging.INFO)

async def create_crew(
    openai_api_key: Optional[str] = None,
    surreal_url: str = "ws://localhost:8000/rpc",
) -> Crew:
    embed_cfg = {"api_key": openai_api_key}

    researcher = Agent(
        name="GeoResearcher",
        role="Geo-intelligence analyst",
        goal="collect up-to-date info on travel destinations",
    )

    planner = Agent(
        name="TripPlanner",
        role="Personal itinerary planner",
        goal="craft a perfect 48-hour city break",
    )

    entity_mem = EntityMemory(
        storage=SurrealStorage("entity",
                               url=surreal_url,
                               embedder_config=embed_cfg)
    )
    short_mem = ShortTermMemory(
        storage=SurrealStorage("short-term",
                               url=surreal_url,
                               embedder_config=embed_cfg)
    )

    t1 = Task(
        description="Find vibrant European cities with art festivals in June 2025.",
        expected_output="Top 3 candidate cities with festival names and dates.",
        agent=researcher,
    )

    t2 = Task(
        description="Design a relaxed 2-day itinerary for the best candidate.",
        expected_output="Detailed schedule with cafes, galleries, evening events.",
        agent=planner,
        context=[t1],
    )

    crew = Crew(
        memory=True,
        entity_memory=entity_mem,
        short_term_memory=short_mem,
    )
    crew.add_agents(researcher, planner)
    crew.add_tasks(t1, t2)
    return crew

async def main():
    crew = await create_crew()
    result = await crew.run()
    print(result)

if __name__ == "__main__":
    asyncio.run(main())
```

Run the crew:

```bash
python -m mycrew.crew
```

## Customising further

| Piece             | How to tweak it                                                                                 |
| ----------------- | ----------------------------------------------------------------------------------------------- |
| Embedding model   | Swap `_embed()` for JinaAI, Instructor, or a local transformer; adjust `_EMBED_DIM`.            |
| Similarity metric | Change `DIST COSINE` to `L2` or `DOT` to match your embeddings.                                 |
| Metadata filters  | Extend the simple `filter` mapping to support CrewAI’s richer operators (`$gt`, `$in`, …).      |
| Remote / Cloud DB | Replace `ws://localhost:8000/rpc` with `wss://<YOUR-ENDPOINT>/rpc` and supply a token/password. |

## Resources

* [Vector Search reference guide](/docs/learn/data-models/vector-search/overview.md)
* [Using SurrealDB as a Vector Database](/docs/learn/data-models/vector-search/overview.md)
* [Python SDK docs](/docs/reference/python.md)
* [DEFINE INDEX statement](/docs/reference/query-language/statements/define/indexes.md)

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/dagster

# Dagster

This section contains information about the Dagster framework and how to integrate it with SurrealDB.

Dagster is a tool for building data pipelines and workflows, and is widely used by data engineers and data scientists.

## Install

```bash title="Install"
pip install dagster surrealdb openai  # or swap out OpenAI for any embedder
# optional - launch a local SurrealDB daemon
docker run -p 8000:8000 surrealdb/surrealdb:latest \
       start --user root --pass secret file:/data/db
```

## A Dagster "SurrealResource"

```python title="src/dagster_surreal.py"
import dagster as dg
from surrealdb import Surreal
from typing import List, Sequence, Optional
import hashlib, json, os, contextlib

_EMBED_DIM = 1536  # match your embedder

def embed(text: str) -> List[float]:
    """Tiny helper - replace with your preferred model."""
    import openai
    resp = openai.Embedding.create(
        model="text-embedding-3-small",
        input=[text],
        dimensions=_EMBED_DIM,
        api_key=os.getenv("OPENAI_API_KEY"),
    )
    return resp["data"][0]["embedding"]


class SurrealConfig(dg.Config):
    url: str = dg.Field(str, default_value="ws://localhost:8000/rpc")
    namespace: str = dg.Field(str, default_value="dagster")
    database: str = dg.Field(str, default_value="vector")
    user: str = dg.Field(str, default_value="root")
    password: str = dg.Field(str, default_value="secret")


@dg.resource(config_schema=SurrealConfig)
class SurrealResource:
    """
    A very small wrapper that exposes .add() and .query() like dagster-qdrant.
    """

    def __init__(self, context):
        cfg: SurrealConfig = context.resource_config
        self.url = cfg.url
        self.namespace = cfg.namespace
        self.database = cfg.database
        self.user = cfg.user
        self.password = cfg.password

    # - helpers ----------------------------------------------------------
    def _ensure_table(self, table: str):
        with Surreal(self.url) as db:
            db.signin({"username": self.user, "password": self.password})
            db.use(self.namespace, self.database)
            db.query(
                """
                DEFINE TABLE $table SCHEMALESS;
                DEFINE FIELD id        ON $table TYPE string;
                DEFINE FIELD text      ON $table TYPE string;
                DEFINE FIELD embedding ON $table TYPE array;
                DEFINE INDEX IF NOT EXISTS ${table}_vec
                       ON $table FIELDS embedding
                       HNSW DIMENSION $dim DIST COSINE;
                """,
                {
                    "table": table,
                    "dim": _EMBED_DIM
                }
            )

    # - public API -------------------------------------------------------
    def add(self, collection_name: str, documents: Sequence[str]):
        self._ensure_table(collection_name)
        with Surreal(self.url) as db:
            db.signin({"username": self.user, "password": self.password})
            db.use(self.namespace, self.database)
            for doc in documents:
                rec = {
                    "id": hashlib.sha1(doc.encode()).hexdigest(),
                    "text": doc,
                    "embedding": embed(doc),
                }
                db.create(collection_name, rec)

    def query(
        self,
        collection_name: str,
        query_text: str,
        limit: int = 3,
        score_threshold: float = 0.4,
    ):
        self._ensure_table(collection_name)
        vec = embed(query_text)
        with Surreal(self.url) as db:
            db.signin({"username": self.user, "password": self.password})
            db.use(self.namespace, self.database)
            result = db.query(
                """
                SELECT text,
                       vector::similarity::cosine(embedding, $vec) AS score
                FROM $table
                WHERE embedding <|$limit|> $vec
                ORDER BY score DESC
                """,
                {
                    "table": collection_name,
                    "vec": vec,
                    "limit": limit
                }
            )
            rows = result[0]["result"]
            return [r for r in rows if r['score'] <= score_threshold or score_threshold == 0]

    # Use the resource as a context-manager inside assets
    @contextlib.contextmanager
    def get_client(self):
        try:
            yield self
        finally:
            pass  # No need to close since we use context managers for each operation
```

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/google-agent

# Google Agent

Google Agent is a framework for building and deploying agents.

Vertex AI Agent Builder is a framework for developing and deploying agents in Google Cloud. Any agent running on Vertex AI Agent Engine or the open-source ADK runtime can be [surfaced through Agentspace](https://cloud.google.com/products/agentspace), which makes it available to everyone in your organisation.

## TL;DR

*Build the agent with ADK → deploy to Agent Engine → "Add to Agentspace"* - the SurrealDB retrieval tool keeps working unchanged, giving your Gemini-based agent sub-second RAG over your own vector index, all inside Google Cloud.

Below is a concise walkthrough that shows:

1. creating a SurrealDB-powered retrieval tool
2. wiring it into an **ADK** agent
3. deploying that agent to Agent Engine
4. registering it in Agentspace so employees can chat with it from the Agentspace UI.

## Prerequisites

**Bash**

```bash
pip install google-adk google-genai surrealdb    # ADK + Gemini SDK + SurrealDB
docker run -p 8000:8000 surrealdb/surrealdb:latest \
       start --user root --pass secret file:/data/db   # optional local DB
export GOOGLE_API_KEY=<your-Gemini-key>
```

**PowerShell**

```powershell
pip install google-adk google-genai surrealdb    # ADK + Gemini SDK + SurrealDB
docker run -p 8000:8000 surrealdb/surrealdb:latest `
       start --user root --pass secret file:/data/db   # optional local DB
$env:GOOGLE_API_KEY = "<your-Gemini-key>"
```

## Prepare SurrealDB (one-time DDL)

```surql
DEFINE TABLE kb_docs SCHEMALESS;
DEFINE FIELD id        ON kb_docs TYPE string;
DEFINE FIELD text      ON kb_docs TYPE string;
DEFINE FIELD source    ON kb_docs TYPE string;
DEFINE FIELD embedding ON kb_docs TYPE array;

-- 1 536-D cosine HNSW (matches Gemini embeddings)
DEFINE INDEX IF NOT EXISTS kb_vec
  ON kb_docs FIELDS embedding
  HNSW DIMENSION 1536 DIST COSINE;
```

## Custom "Surreal Retrieve" tool

```python
from typing import List, Dict, Any, Optional
from google import genai
from surrealdb import AsyncSurreal
from surrealdb.exception import SurrealException
import os
import json
import hashlib
import logging
from dataclasses import dataclass
from contextlib import asynccontextmanager

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@dataclass
class SurrealConfig:
    """Configuration for SurrealDB connection."""
    url: str = "ws://localhost:8000/rpc"
    namespace: str = "agent"
    database: str = "demo"
    username: str = "root"
    password: str = "secret"

class SurrealRetriever:
    """SurrealDB-powered retrieval tool for Google Agent."""
    
    def __init__(
        self,
        config: Optional[SurrealConfig] = None,
        api_key: Optional[str] = None
    ):
        """Initialize the retriever.
        
        Args:
            config: Optional SurrealDB configuration
            api_key: Optional Google API key
        """
        self.config = config or SurrealConfig()
        self.api_key = api_key or os.environ.get("GOOGLE_API_KEY")
        if not self.api_key:
            raise ValueError("Google API key is required")
            
        genai.configure(api_key=self.api_key)
        self.embedder = genai.Client().models.get_embedding_model("models/embedding-001")
        
    @asynccontextmanager
    async def _get_connection(self) -> AsyncSurreal:
        """Get a database connection with proper authentication.
        
        Yields:
            Connected AsyncSurreal instance
            
        Raises:
            SurrealException: If connection fails
        """
        db = AsyncSurreal(self.config.url)
        try:
            await db.signin({
                "username": self.config.username,
                "password": self.config.password
            })
            await db.use(self.config.namespace, self.config.database)
            yield db
        except SurrealException as e:
            logger.error(f"Failed to connect to SurrealDB: {str(e)}")
            raise SurrealException(f"Connection failed: {str(e)}")
        finally:
            await db.close()

    async def _embed(self, text: str) -> List[float]:
        """Generate embeddings for text.
        
        Args:
            text: Text to embed
            
        Returns:
            List of embedding values
            
        Raises:
            Exception: If embedding generation fails
        """
        try:
            return self.embedder.embed(content=text).embedding
        except Exception as e:
            logger.error(f"Failed to generate embeddings: {str(e)}")
            raise Exception(f"Embedding generation failed: {str(e)}")

    async def retrieve(self, question: str, k: int = 4) -> str:
        """Retrieve top-k passages related to question from SurrealDB.
        
        Args:
            question: Search query
            k: Number of results to return
            
        Returns:
            Formatted string of matching passages
            
        Raises:
            SurrealException: If database operations fail
        """
        try:
            qv = await self._embed(question)
            async with self._get_connection() as db:
                result = await db.query("""
                    SELECT text, source,
                           vector::similarity::cosine(embedding, $vec) AS score
                    FROM kb_docs
                    WHERE embedding <|$k|> $vec
                    ORDER BY score DESC
                """, {
                    "vec": qv,
                    "k": k
                })
                
                rows = result[0]["result"]
                return "\n".join(
                    f"- {r['text']} (src: {r['source']})"
                    for r in rows
                )
        except SurrealException as e:
            logger.error(f"Retrieval failed: {str(e)}")
            raise SurrealException(f"Retrieval failed: {str(e)}")

    async def ingest(self, docs: List[Dict[str, str]]) -> None:
        """Ingest documents into SurrealDB.
        
        Args:
            docs: List of documents with text and source
            
        Raises:
            SurrealException: If database operations fail
        """
        try:
            async with self._get_connection() as db:
                for doc in docs:
                    rec = {
                        "id": hashlib.sha1(doc["text"].encode()).hexdigest(),
                        "text": doc["text"],
                        "source": doc["source"],
                        "embedding": await self._embed(doc["text"]),
                    }
                    await db.create("kb_docs", rec)
                logger.info(f"Ingested {len(docs)} documents")
        except SurrealException as e:
            logger.error(f"Ingestion failed: {str(e)}")
            raise SurrealException(f"Ingestion failed: {str(e)}")

    async def ensure_schema(self) -> None:
        """Ensure the required table and index exist.
        
        Raises:
            SurrealException: If schema operations fail
        """
        try:
            async with self._get_connection() as db:
                await db.query("""
                    DEFINE TABLE IF NOT EXISTS kb_docs SCHEMALESS;
                    DEFINE FIELD id        ON kb_docs TYPE string;
                    DEFINE FIELD text      ON kb_docs TYPE string;
                    DEFINE FIELD source    ON kb_docs TYPE string;
                    DEFINE FIELD embedding ON kb_docs TYPE array;
                    DEFINE INDEX IF NOT EXISTS kb_vec
                        ON kb_docs FIELDS embedding
                        HNSW DIMENSION 1536 DIST COSINE;
                """)
                logger.info("Schema ensured successfully")
        except SurrealException as e:
            logger.error(f"Schema setup failed: {str(e)}")
            raise SurrealException(f"Schema setup failed: {str(e)}")

# Example usage
async def main():
    retriever = SurrealRetriever()
    
    try:
        # Ensure schema exists
        await retriever.ensure_schema()
        
        # Ingest sample documents
        await retriever.ingest([
            {
                "text": "Nikola Tesla patented the first practical AC induction motor in 1888.",
                "source": "wiki/Tesla"
            },
            {
                "text": "The Wright brothers achieved powered flight on 17 Dec 1903.",
                "source": "wiki/Wright"
            },
        ])
        
        # Test retrieval
        result = await retriever.retrieve(
            "Who invented the AC motor and when?",
            k=2
        )
        print(result)
        
    except Exception as e:
        logger.error(f"Error: {str(e)}")
        raise

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())
```

## Build an ADK agent that uses the tool

```python
from typing import Optional
from adk import Agent, Tool
from surreal_tool import SurrealRetriever
import asyncio
import logging
from contextlib import asynccontextmanager

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize retriever
retriever = SurrealRetriever()

@Tool
async def knowledge_search(query: str) -> str:
    """Search SurrealDB for passages relevant to the query.
    
    Args:
        query: Search query
        
    Returns:
        Formatted string of matching passages
    """
    try:
        return await retriever.retrieve(query, k=4)
    except Exception as e:
        logger.error(f"Knowledge search failed: {str(e)}")
        return f"Error searching knowledge base: {str(e)}"

@asynccontextmanager
async def get_agent():
    """Get an agent instance with proper setup and teardown.
    
    Yields:
        Configured Agent instance
    """
    try:
        agent = Agent(
            name="Surreal-Facts-Bot",
            llm="gemini-pro",
            instructions="""
You are a helpful assistant that must cite retrieved passages verbatim.
When knowledge is required, call knowledge_search().
""",
            tools=[knowledge_search],
        )
        logger.info("Agent created successfully")
        yield agent
    except Exception as e:
        logger.error(f"Failed to create agent: {str(e)}")
        raise

async def main():
    """Run the agent."""
    try:
        async with get_agent() as agent:
            response = await agent.chat("Who invented the AC motor and when?")
            print(response)
            logger.info("Agent execution completed successfully")
    except Exception as e:
        logger.error(f"Agent execution failed: {str(e)}")
        raise

if __name__ == "__main__":
    asyncio.run(main())
```

Run locally (`python agent.py`) to verify the agent calls your SurrealDB tool.

## Deploy to **Vertex AI Agent Engine**

Create `agent.yaml` (full schema in the ADK docs):

```yaml
name: surreal-facts
entrypoint: agent:agent        # python-module:object
requirements:
  - surrealdb
  - google-genai
  - google-adk
  - google-cloud-aiplatform
```

```bash
gcloud ai agent-engines deploy surreal-facts \
  --agent-spec=agent.yaml \
  --region=us-central1
```

## Publish in **Agentspace**

Agentspace can list any agent that's running on Agent Engine ([Google Cloud][3], [Medium][4]).

* **Console path:** *Agentspace → Agent Gallery → "Add agent" → Source: Agent Engine*
* **CLI (preview):**

```bash
gcloud ai agentspace agent-galleries add-agent \
  --agent-engine=projects/$PROJECT/locations/us-central1/agentEngines/surreal-facts
```

Once added, employees will see **"Surreal Facts Bot"** in the gallery and can chat with it from the Agentspace UI; every time it needs knowledge it silently calls your SurrealDB index.

## Operational notes

<table>
  <thead>
    <tr>
      <th>Topic</th>
      <th>Guidance</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Networking</strong></td>
      <td>Run SurrealDB on Cloud Run, GKE, or a VM inside the same VPC; use Private Service Connect so the Agent Engine can reach it.</td>
    </tr>
    <tr>
      <td><strong>Security</strong></td>
      <td>Protect the DB with <code>DEFINE ACCESS</code> tokens or basic auth; Agentspace inherits IAM & VPC-SC you already set in Agent Builder (<a href="https://cloud.google.com/agentspace/docs/overview">Google Cloud</a>).</td>
    </tr>
    <tr>
      <td><strong>Hybrid search</strong></td>
      <td>Mix Boolean filters with the <code>&lt;|K|&gt;</code> operator (<code>WHERE source ~ 'wiki' AND embedding &lt;|3|&gt;</code>).</td>
    </tr>
    <tr>
      <td><strong>Updates</strong></td>
      <td>New docs can be inserted at any time; SurrealDB's HNSW index updates incrementally, or run <code>REBUILD INDEX</code> during low-traffic windows (<a href="[SurrealDB][6]">SurrealDB</a>).</td>
    </tr>
  </tbody>
</table>

## Resources

- [Google Next 25 Updates: ADK, Agentspace, Application Integration](https://www.googlecloudcommunity.com/gc/Cloud-Product-Articles/Google-Next-25-Updates-ADK-Agentspace-Application-Integration/ta-p/898343)
- [DEFINE INDEX statement](/docs/reference/query-language/statements/define/indexes.md)
- [Vertex AI Agent Builder](https://cloud.google.com/products/agent-builder)
- [A Foundational Framework for Agentic AI Ecosystems: Enabling](https://medium.com/google-cloud/a-foundational-framework-for-agentic-ai-ecosystems-enabling-development-discovery-and-2aeb120949f6)
- [Introduction to Google Agentspace](https://cloud.google.com/agentspace/docs/overview)
- [REBUILD statement](/docs/reference/query-language/statements/rebuild.md)

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/hermes

# Hermes

Give a Hermes agent a persistent filesystem in SurrealDB, as a toolset it calls explicitly and as a memory provider that files and recalls every turn.

[Hermes](https://hermes-agent.nousresearch.com) is Nous Research's terminal agent. [SurrealDB filesystem](https://github.com/surrealdb/surrealfs) gives it somewhere durable to keep its work: a `file` table in SurrealDB, plus the tools to hand that table to a model. It supports files and folders and full-text and semantic search, and everything in it is queryable with SurrealQL, because it is an ordinary table.

**SurrealDB filesystem** ships two Hermes plugins. They install separately and either one works on its own:

- **The toolset** registers 14 `surrealfs_*` tools and a bundled skill, so the agent reads and writes the filesystem when it decides to.
- **The memory provider** files every completed turn into the same filesystem and searches it for context before each turn.

They are better together: the provider's recall surfaces the notes the agent wrote with the tools.

> [!NOTE]
> This page covers **SurrealDB filesystem** on a SurrealDB instance you run. For hosted memory with fact extraction and semantic recall, see the [SurrealDB Agent Memory integration for Hermes](/docs/agent-memory/integrations/frameworks/hermes.md), which is a separate provider.

## Requirements

- Python 3.12+
- A SurrealDB 3.x server, [installed locally](/docs/running/installation.md) or on [SurrealDB Cloud](/docs/manage/instances.md)
- A Hermes install

Install Hermes if you do not have it, and follow the [installation guide](https://hermes-agent.nousresearch.com/docs/getting-started/installation) through to `Installation Complete`:

```bash
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
```

## Prepare a database

Start SurrealDB with a persistent backend, either [installed directly](/docs/running/installation.md):

```bash
surreal db start --user root --pass secret rocksdb://surrealfs.db
```

or [with Docker](/docs/running/docker.md):

```bash
docker run --rm --pull always -p 8000:8000 -v ./data:/data \
  surrealdb/surrealdb:latest start --user root --pass secret rocksdb://data/surrealfs.db
```

Then define the `file` table. The schema command reads the same `SURREALDB_*` variables as everything else, and it is safe to re-run:

```bash
python -m surrealfs.schema
```

Add `--include-user` for the optional `user` table and record access that the `file` table's `owner` permissions need for multi-tenancy, or `--print` to dump the DDL without connecting to anything.

A file's `parent` is a record link and its `path` is a computed field derived from the parent chain:

```surql
DEFINE FIELD path ON file COMPUTED (
    ($this.parent.path || '') + '/' + <string>$this.filename
) TYPE string;
```

Nothing stores a path, so moving or renaming a folder is a single `UPDATE` and every descendant's path follows automatically. A folder is a record with no content, `hash` is maintained by an event, and the HNSW and BM25 indexes behind the two search modes are part of the same schema.

## Connection settings

Both plugins read the same environment variables and default to a local server, so the defaults already work against `surreal db start` on your own machine.

| Variable | Default | Purpose |
| --- | --- | --- |
| `SURREALDB_URL` | `ws://localhost:8000/rpc` | Server to connect to |
| `SURREALDB_USER` / `SURREALDB_PASS` | `root` / `root` | Credentials |
| `SURREALDB_NAMESPACE` / `SURREALDB_DATABASE` | `surrealfs` / `demo` | Where the `file` table lives |
| `SURREALFS_SEMANTIC` | unset | `1` adds a vector arm to search and recall |

Put these in `$HERMES_HOME/.env` rather than your shell profile. Hermes cron jobs run with a sanitised environment, so a scheduled [indexer](#keep-the-vectors-current) reads its connection details from that file.

## The toolset

Hermes runs from its own virtualenv, so install the package there rather than into your project. `surrealfs` is not on PyPI yet, so install it from Git:

```bash
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python \
  "surrealfs @ git+https://github.com/surrealdb/surrealfs.git"
hermes plugins enable surrealfs
hermes chat --toolsets surrealfs
```

Installing the package is what makes the plugin discoverable, because it registers through the `hermes_agent.plugins` entry point. Nothing in the plugin imports Hermes: the plugin contract is a `register` function and a manifest, so there is no extra dependency to install.

To run from a checkout instead, symlink the plugin directory into Hermes' plugin folder. The package still has to be importable by Hermes' interpreter:

```bash
ln -s "$PWD/surrealfs/integrations/hermes" ~/.hermes/plugins/surrealfs
```

### The tools

| Group | Tools |
| --- | --- |
| Read | `surrealfs_ls` `surrealfs_glob` `surrealfs_cat` `surrealfs_read_bytes` `surrealfs_tail` |
| Write | `surrealfs_write_file` `surrealfs_write_bytes` `surrealfs_edit` `surrealfs_touch` `surrealfs_mkdir` |
| Organise | `surrealfs_cp` `surrealfs_mv` `surrealfs_rm` |
| Search | `surrealfs_search` |

The prefix is deliberate. Hermes keeps one flat tool namespace that its own `read_file` and `write_file` already sit in, and the prefix also tells the model which filesystem it is addressing: the built-in file tools are the local disk, `surrealfs_*` is the shared one in SurrealDB. Every tool description says so too.

The plugin opens its own connection per call, and every result comes back as JSON, errors included. Handlers are registered as async, so Hermes awaits them on whichever event loop it is using.

### The bundled skill

`surrealfs:notes` teaches the agent to keep its notes and memories in SurrealDB filesystem, and how to lay them out:

| Path | Holds |
| --- | --- |
| `/home/<agent>/` | The agent's own working files |
| `/home/<agent>/memories/` | Turns filed by the memory provider |
| `/preferences/<user>/` | What the agent learns about the user |
| `/projects/<name>/` | Per-project notes, plans and to-do lists |

Paths are absolute and there is no working directory. Everything outside `/home/` is shared, which is how an agent hands something to its user or to another agent working for the same person.

Hermes advertises plugin skills nowhere: they are absent from the system prompt's `<available_skills>` index and from `skills_list()`, and resolve only by exact qualified name. The plugin therefore registers a `pre_llm_call` hook that names the skill on the first turn of each session. Hermes appends that to the user message and never persists it, so the system prompt stays byte-stable and the prompt cache holds.

## The memory provider

The provider needs two installs, because it is two different things. The *directory* is what Hermes scans to discover memory providers, and the *package* is what the provider imports. Providers are found by directory, never through the `hermes_agent.plugins` entry point:

```bash
hermes plugins install surrealdb/surrealfs/surrealfs/integrations/hermes_memory
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python \
  "surrealfs @ git+https://github.com/surrealdb/surrealfs.git"
hermes memory setup surrealfs-memory
```

`hermes plugins install` accepts a subdirectory of a repository and takes the installed name from `plugin.yaml`, which is why the directory lands as `surrealfs-memory`. That is the name Hermes matches the active-provider setting against.

> [!NOTE]
> Answer **N** to install's `Enable now?` prompt. That prompt belongs to the general plugin manager, which this directory does not go through: `kind: exclusive` routes it to memory discovery instead, and `hermes memory setup` is what activates it.

`hermes memory setup` walks every setting below, offering the current value or the default, writes what you change to `$HERMES_HOME/.env`, activates the provider in `~/.hermes/config.yaml`, and then connects, so a wrong URL surfaces during setup rather than as a per-turn warning. Only one provider can be active at a time. To activate one by hand:

```yaml
memory:
  provider: surrealfs-memory
```

Two commands report on it afterwards. `hermes memory` lists the discovered providers and whether each reports itself available. `hermes surrealfs-memory status` re-runs the connection check at any time, naming the database it resolved and counting the turns filed there.

Upgrades are two commands for the same reason installs are: `hermes plugins update surrealfs-memory` for the directory, and the `uv pip install` again for the package. Run the second again whenever `hermes memory` starts reporting the provider unavailable, because a virtualenv rebuild strips it.

### Settings

Beyond the [connection settings](#connection-settings), the provider adds two of its own:

| Variable | Default | Purpose |
| --- | --- | --- |
| `SURREALFS_AGENT_USER` | Your Unix username | The agent's home under `/home` |
| `SURREALFS_MEMORY_DIR` | `/home/<agent>/memories` | Folder to file turns under |

Set `SURREALFS_AGENT_USER` whenever two agents would otherwise share a home: two Hermes installs owned by different people against one database, or an agent running under the same account as its human. `hermes-tobie` alongside the human's own `/home/tobie` is the shape to aim for. On a VM or sandbox where the agent has an account to itself, the default already matches the machine.

### What gets filed

Each completed turn becomes its own file:

```text
/home/hermes-tobie/memories/default/2026-08-06/a1b2c3-142251003.md
```

```markdown
# 2026-08-06 14:22:51 UTC

session: a1b2c3

## User

how do I get paid for the consulting work

## Assistant

You invoice monthly through ...

## Tools

→ surrealfs_search({"query": "invoice"})
→ surrealfs_cat({"path": "/projects/acme/contract.md"})
```

Each turn gets its own file rather than sharing one per session, because a search hit is then a single exchange with its snippet centred on the match. The `## Tools` section records which files were read and which commands ran. It is often the substance of the turn and appears in neither message's text, and it is omitted when the turn made no tool calls.

Turns where both sides carry no signal are skipped: a prompt like "ok" or "thanks", answered briefly. A trivial prompt with a long answer is still filed, because "go ahead" followed by a design document is a turn worth keeping. Anything that is not a primary agent is skipped too, so a cron run's system prompt is never filed as a memory and cannot corrupt what the agent believes about the user.

The filename ends in a wall-clock stamp (`HHMMSSmmm`) rather than a turn number. A counter has to live somewhere, and in-process is the one place it cannot: `hermes --resume` starts a second process on the same session id and restarts counting at one, so turn one of the resumed conversation would overwrite turn one of the original.

Two other things are filed under the same profile subtree:

- **Hermes' own memory.** When the built-in memory tool writes to `MEMORY.md` or `USER.md`, the same change is applied to `<memories>/<profile>/builtin/memory.md` or `.../user.md`. That mirrors what those files say rather than logging the writes, so recall reads the current state.
- **Messages about to be compressed away.** Before Hermes compresses a long conversation it asks each provider what to preserve. This provider writes the doomed messages out verbatim, tool calls included, and hands the compressor a pointer to the file, so the detail survives at full fidelity and the agent can read it back with `surrealfs_cat`.

> [!IMPORTANT]
> Everything filed here goes to the SurrealDB at your `SURREALDB_URL` and nowhere else. Hermes hands memory providers the turn's full message list; this provider writes part of it to your own database, and it leaves your machine only if you pointed it at a remote server yourself.

### What gets recalled

Before each turn the provider searches the whole filesystem, excluding other agents' homes. The notes the agent wrote itself under `/preferences/` and `/projects/` are the highest-signal memories present, so excluding them would mean the bundled `surrealfs:notes` skill and the provider ignored each other's work.

Filed turns are held to two of the five recalled slots, and the current conversation's own turns are never recalled at all. Recall reads the tree it writes, and one turn per file means short documents, which the ranking favours. Without a cap the top five drifts into verbatim transcript and the curated notes stop showing up.

The prompt is cut down to search terms before it reaches the index: stopwords out, a dozen terms at most. Every word in a whole paragraph is another `OR` branch, and matching records are read back in full to be ranked.

The search runs after the previous turn rather than during the current one, through Hermes' `queue_prefetch` and `prefetch` pair, so nothing waits on it, in particular not the embedding round trip that `SURREALFS_SEMANTIC=1` adds. The cost is that a recall is one turn behind: switch topic abruptly and the first turn on the new subject still carries context for the old one.

The provider registers no tools of its own. The 14 `surrealfs_*` tools from the toolset already cover explicit reads and writes, and a second set would collide in Hermes' flat tool namespace.

### Homes and profiles

Turns are filed under `/home/<agent>/memories/<profile>/`. The path has two segments, because there are two ways for memories to end up in one database that should not mix.

`<agent>` is `SURREALFS_AGENT_USER`, or the Unix account the agent runs as. A database shared between people holds one home per agent, plus the humans' own. The notes skill already treats `/home/<username>/` as the agent's working directory, so the memories sit inside the folder the agent already owns, and recall reads that home and no other.

`<profile>` comes from the `hermes_home` that Hermes passes to `initialize()`: `default` for `~/.hermes`, and the directory name for anything under `<root>/profiles/`. It separates one agent's own profiles, which share a home and a machine account.

Everything outside `/home/` stays shared. `/preferences/`, `/projects/`, and anything else written with the `surrealfs_*` tools is the handover point between an agent and its user, or between two agents working for the same person. Anything that must not be shared at all belongs in a `SURREALDB_DATABASE` of its own, and since `.env` lives in `$HERMES_HOME`, that is already per-profile.

> [!WARNING]
> `hermes backup` captures none of this. It walks `HERMES_HOME`, and the memories are in SurrealDB. Use [`surreal db export`](/docs/reference/cli/db/commands/export.md) instead.

## Semantic search

`SURREALFS_SEMANTIC=1` adds a vector arm to `surrealfs_search` and to recall, so a note about invoicing surfaces for a query of "how do I get paid". Both arms run and are fused by rank, because full-text scores are BM25 and vector scores are distances. The full-text arm matches a file that shares any term with the query, so search works before any vector exists; the vector arm is what makes ranking follow meaning rather than shared words.

It needs `OPENAI_API_KEY` and the `embed` extra, which neither plugin's install pulls in.

### Keep the vectors current

Skip this unless you set `SURREALFS_SEMANTIC=1`. A file is not embedded as it is written, so recall matches on meaning only for what the indexer has already reached. Recall keeps working on its full-text arm the whole time the vectors are behind.

Hermes has no way for a plugin to declare a daemon of its own, but its scheduler runs scripts without an agent, which covers this without a systemd unit or a terminal left open:

```bash
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python \
  "surrealfs[embed] @ git+https://github.com/surrealdb/surrealfs.git"

mkdir -p "${HERMES_HOME:-$HOME/.hermes}/scripts"
cat > "${HERMES_HOME:-$HOME/.hermes}/scripts/surrealfs-embed.sh" <<'SCRIPT'
#!/usr/bin/env bash
set -a; . "${HERMES_HOME:-$HOME/.hermes}/.env"; set +a
exec ~/.hermes/hermes-agent/venv/bin/python -m surrealfs.embed --once
SCRIPT

hermes cron create 'every 5m' --no-agent --name surrealfs-embed \
  --script surrealfs-embed.sh
```

`--no-agent` skips the inference layer entirely, so a `--once` pass per tick costs no tokens and replaces the polling loop the daemon would otherwise run. The script must live under `$HERMES_HOME/scripts/`, because paths outside it are rejected. Cron sanitises the subprocess environment and `OPENAI_API_KEY` is on the blocklist, so the key and the `SURREALDB_*` details both have to come from the `.env` the script sources.

An idle pass prints nothing, and empty output is a silent tick, so you hear from this only when it has news. A pass that embedded something reports `embedded 4 file(s)`, and a pass that fails exits non-zero, which Hermes reports as an alert. `hermes cron runs` has the history.

Two things to know before relying on it. The scheduler ticks inside the gateway daemon, so this runs only while that does; run `hermes gateway install` if it is not already a service. And a five-minute period means a file written now is matchable by meaning in up to five minutes, and by term immediately. Lower the interval if that gap matters, at a cost of one `SELECT` per tick that returns nothing when there is no work.

## Browse the filesystem

Point the agents at a shared SurrealDB, a [Cloud instance](/docs/manage/instances.md) for example, and everyone on the team can run the file browser on their own machine to see and edit the same tree the agents write to:

```bash
pip install "surrealfs[browser]"
surrealfs-browser                # http://127.0.0.1:7933
```

The tree is on the left and the open file is on the right. Text is editable and saves back to the table, markdown renders with a source toggle, agent-authored HTML renders in a sandboxed iframe, images display, and the search box is the same hybrid search behind the agent's own tool calls.

Credentials come from a `.env` in the working directory or from the environment, which wins over the file. Every variable has a command-line form too; run `surrealfs-browser --help` for the list. Add `SURREALDB_AUTH_LEVEL` when the credentials are not root ones: the server infers the user kind from the signin payload, and a database-scoped Cloud credential needs `database`. All three levels are system users, so they bypass the `file` table's `owner = $auth.id` permissions and everyone sharing the database sees the one shared tree, which is the point.

> [!WARNING]
> The browser binds loopback. `--host 0.0.0.0` exposes it, and anyone who can then reach the port has whatever access those credentials do, because the page has no login of its own.

## Next steps

- [SurrealDB filesystem](https://github.com/surrealdb/surrealfs) - the repository, the full schema in `surrealfs/schema/file.surql`, and the plain async Python API to build your own integration on
- [Hermes toolset README](https://github.com/surrealdb/surrealfs/blob/main/surrealfs/integrations/hermes/README.md) - the tools, the prefix, and the bundled skill
- [Hermes memory provider README](https://github.com/surrealdb/surrealfs/blob/main/surrealfs/integrations/hermes_memory/README.md) - what gets filed, what gets recalled, and how homes and profiles divide it
- [Keeping the vectors current under Hermes](https://github.com/surrealdb/surrealfs/blob/main/docs/hermes-indexer.md) - the indexer cron job in full
- [SurrealDB Agent Memory integration for Hermes](/docs/agent-memory/integrations/frameworks/hermes.md) - hosted memory with fact extraction and semantic recall, as an alternative provider

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/kreuzberg

# Kreuzberg

Integrate Kreuzberg document intelligence extraction pipelines with SurrealDB.

Kreuzberg is a polyglot document intelligence framework that allows you to extract text, metadata, images, and structured information from PDFs, Office documents, images, and 91+ formats.

The `kreuzberg-surrealdb` package connects Kreuzberg's document extraction pipeline to SurrealDB. It handles schema creation, content deduplication, optional chunking and embedding, and index configuration.

## How it works

1. **Extract** - Kreuzberg parses the source documents and runs OCR where needed.
2. **Connect** - The connector receives the extracted output and manages the SurrealDB connection.
3. **Store** - Each document is hashed (SHA-256) for deduplication, optionally chunked and embedded, then written to SurrealDB under an auto-generated schema.
4. **Search** - Full-text (BM25), vector (HNSW), and hybrid (RRF) search are available immediately after ingestion.

## Key capabilities

- **Schema management** - `setup_schema()` creates tables, indexes, and analyzers. No manual DDL required.
- **Deduplication** - Deterministic record IDs derived from content hashes prevent duplicate records across ingestion runs.
- **Flexible ingestion** - Single files, file lists, directories (with glob), or raw bytes.
- **Extraction control** - Pass Kreuzberg's `ExtractionConfig` to set OCR behaviour, output format, and quality processing.
- **Batch tuning** - Adjust `insert_batch_size` to balance throughput against memory usage.

## Supported formats

Formats supported by Kreuzberg are as follows:

* Document formats: "pdf", "docx", "docm", "dotx", "dotm", "dot", "doc", "odt", "pptx", "ppsx", "pptm", "potx", "potm", "pot", "ppt", "xlsx", "xlsm", "xlsb", "xlam", "xla", "xltx", "xlt", "xls", "ods", "dbf", "hwp", "hwpx"
* Text formats: "txt", "md", "markdown", "commonmark", "html", "htm", "xml", "rtf", "rst", "org"
* Data formats: "json", "yaml", "yml", "toml", "csv", "tsv"
* Email formats: "eml", "msg"
* Archives: "zip", "tar", "gz", "tgz", "7z"
* Images (OCR supported): "bmp", "gif", "jpg", "jpeg", "png", "tiff", "tif", "webp", "jp2", "jpx", "jpm", "mj2", "j2k", "j2c", "jbig2", "jb2", "pnm", "pbm", "pgm", "ppm"
* Academic / publishing formats: "epub", "fb2", "bib", "ris", "nbib", "enw", "ipynb", "tex", "latex", "typst", "typ"
* Markup / structured formats: "opml", "dbk", "docbook", "jats"
* Other: "svg", "djot"

For more information on supported formats, see the [Kreuzberg docs](https://docs.kreuzberg.dev).

## Getting started

**Python**

To get started if using Python, visit [SurrealDB in the Kreuzberg Docs](https://kreuzberg.dev). For the complete API reference, embedding model options, chunking configuration, and database schema details, see the [Kreuzberg repository](https://github.com/Goldziher/kreuzberg).

**Rust**

As Kreuzberg is written in Rust and has its own [crate](https://crates.io/crates/kreuzberg), code can be written directly with much more boilerplate but also quite a bit of manual customisation. The following example shows a setup somewhat similar to the Python extension. It demonstrates a number of file types used to populate the same SurrealDB instance, in this case a JSON file `demo.json`, a Markdown file `demo.md`, and even an HWPX file `demo.hwpx`, which are assumed to be in a folder `/assets`. Most LLMs are able to generate sample files of these types such as the content below in JSON.

```json title="Sample data"
{
  "documents": [
    {
      "title": "English Quarterly Revenue",
      "language": "en",
      "content": "Quarterly revenue increased significantly this quarter."
    },
    {
      "title": "English Product",
      "language": "en",
      "content": "Product launch includes search and analytics."
    },
    {
      "title": "한국어 문서",
      "language": "ko",
      "content": "이 문서는 분기 매출과 검색 기능을 설명합니다."
    },
    {
      "title": "日本語 文書",
      "language": "ja",
      "content": "この文書は四半期売上と検索について説明します。"
    },
    {
      "title": "Mixed Doc",
      "language": "mixed",
      "content": "Quarterly revenue 데이터 日本語 検索 mixed language example."
    }
  ]
}
```

The code can be run with either `cargo run -- memory` or `cargo run -- local`, depending on if you prefer a one-time embedded instance or a local instance that can be connected to via SurrealDB Studio to manually query the data after the Rust code has run.

For more ideas on how to redo this example to suit your own needs, see the [Kreuzberg repository](https://github.com/Goldziher/kreuzberg).

```rust
// Required features to run:
// kreuzberg = { version = "4.9.4", default-features = true, features = ["hwpx", "language-detection"] }
// surrealdb = { version = "3.2.0", default-features = true, features = ["protocol-ws", "kv-mem"] }
use anyhow::{Context, Result};
use kreuzberg::{extract_file, ExtractionConfig, LanguageDetectionConfig};
use sha2::{Digest, Sha256};
use std::{
    fs::read_to_string,
    path::{Path, PathBuf},
};
use surrealdb::{
    engine::any::connect,
    opt::auth::Root,
    types::{ToSql, Value},
};

use kreuzberg::types::ExtractionResult;
use serde::{Deserialize, Serialize};
use surrealdb::types::{RecordId, SurrealValue};

/// One logical document inside `assets/demo.json` (`documents` array).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct JsonDocumentSpec {
    pub title: String,
    #[serde(default)]
    pub language: Option<String>,
    pub content: String,
}

#[derive(Debug, Deserialize)]
pub struct JsonDocumentsFile {
    pub documents: Vec<JsonDocumentSpec>,
}

/// Parse `demo.json`-style bundles to ingest records
pub fn parse_json_documents_file(path: &Path) -> anyhow::Result<JsonDocumentsFile> {
    let raw = read_to_string(path).with_context(|| format!("read {}", path.display()))?;
    serde_json::from_str(&raw)
        .with_context(|| format!("parse JSON documents bundle {}", path.display()))
}

#[derive(Debug, Clone, SurrealValue)]
pub struct ExtractionMetadataNested {
    subject: Option<String>,
    language: Option<String>,
    modified_at: Option<String>,
    extraction_duration_ms: Option<u64>,
}

#[derive(Debug, Clone, SurrealValue)]
pub struct ConnectorDocument {
    id: RecordId,
    source: String,
    content: String,
    mime_type: String,
    title: Option<String>,
    authors: Option<Vec<String>>,
    created_at: Option<String>,
    metadata: ExtractionMetadataNested,
    quality_score: Option<f64>,
    content_hash: String,
    detected_languages: Option<Vec<String>>,
    keywords: Option<Vec<String>>,
}

/// Build the SurrealDB document payload for `INSERT IGNORE …`.
pub fn document_record_value(
    table: &str,
    extracted: &ExtractionResult,
    source_label: String,
    content_hash: &str,
) -> ConnectorDocument {
    let meta = &extracted.metadata;
    ConnectorDocument {
        id: RecordId::new(table, content_hash),
        source: source_label,
        content: extracted.content.clone(),
        mime_type: extracted.mime_type.as_ref().to_string(),
        title: meta.title.clone(),
        authors: meta.authors.clone(),
        created_at: meta.created_at.clone(),
        metadata: ExtractionMetadataNested {
            subject: meta.subject.clone(),
            language: meta.language.clone(),
            modified_at: meta.modified_at.clone(),
            extraction_duration_ms: meta.extraction_duration_ms,
        },
        quality_score: extracted.quality_score,
        content_hash: content_hash.to_string(),
        detected_languages: extracted.detected_languages.clone(),
        keywords: meta.keywords.clone(),
    }
}

/// Build a connector row from a JSON catalogue entry (no Kreuzberg pass - plain text is already known).
pub fn document_record_from_json_entry(
    table: &str,
    entry: &JsonDocumentSpec,
    source: String,
    content_hash: &str,
) -> ConnectorDocument {
    ConnectorDocument {
        id: RecordId::new(table, content_hash),
        source,
        content: entry.content.clone(),
        mime_type: "application/json".to_string(),
        title: Some(entry.title.clone()),
        authors: None,
        created_at: None,
        metadata: ExtractionMetadataNested {
            subject: None,
            language: entry.language.clone(),
            modified_at: None,
            extraction_duration_ms: None,
        },
        quality_score: None,
        content_hash: content_hash.to_string(),
        detected_languages: entry.language.as_ref().map(|l| vec![l.clone()]),
        keywords: None,
    }
}

const TABLE: &str = "documents";

fn sha256_hex(content: &str) -> String {
    let mut h = Sha256::new();
    h.update(content.as_bytes());
    format!("{:x}", h.finalize())
}

pub fn connector_schema(table: &str) -> String {
    format!(
        "DEFINE ANALYZER IF NOT EXISTS doc_analyzer TOKENIZERS class FILTERS lowercase,snowball(english);
        DEFINE TABLE IF NOT EXISTS {table} SCHEMAFULL;
        DEFINE FIELD IF NOT EXISTS source ON TABLE {table} TYPE string;
        DEFINE FIELD IF NOT EXISTS content ON TABLE {table} TYPE string;
        DEFINE FIELD IF NOT EXISTS mime_type ON TABLE {table} TYPE string;
        DEFINE FIELD IF NOT EXISTS title ON TABLE {table} TYPE option<string>;
        DEFINE FIELD IF NOT EXISTS authors ON TABLE {table} TYPE option<array<string>>;
        DEFINE FIELD IF NOT EXISTS created_at ON TABLE {table} TYPE option<string>;
        DEFINE FIELD IF NOT EXISTS ingested_at ON TABLE {table} TYPE datetime DEFAULT time::now();
        DEFINE FIELD IF NOT EXISTS metadata ON TABLE {table} TYPE object FLEXIBLE;
        DEFINE FIELD IF NOT EXISTS quality_score ON TABLE {table} TYPE option<float>;
        DEFINE FIELD IF NOT EXISTS content_hash ON TABLE {table} TYPE string;
        DEFINE FIELD IF NOT EXISTS detected_languages ON TABLE {table} TYPE option<array<string>>;
        DEFINE FIELD IF NOT EXISTS keywords ON TABLE {table} TYPE option<array<string>>;
        DEFINE INDEX IF NOT EXISTS idx_doc_source ON TABLE {table} FIELDS source UNIQUE;
        DEFINE INDEX IF NOT EXISTS idx_doc_hash ON TABLE {table} FIELDS content_hash UNIQUE;
        DEFINE INDEX IF NOT EXISTS idx_doc_content ON TABLE {table} FIELDS content FULLTEXT ANALYZER doc_analyzer BM25 HIGHLIGHTS;"
        )
}

// Add files such as demo.md, demo.hwpx, demo.json to /assets folder
fn default_asset_paths() -> Vec<PathBuf> {
    let assets = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets");
    ["demo.md", "demo.hwpx", "demo.json"]
        .into_iter()
        .map(|name| assets.join(name))
        .collect()
}

fn json_entry_source_label(path: &Path, index: usize) -> String {
    format!("{}#{}", path.display(), index)
}

async fn ingest_path(
    path: &Path,
    table: &str,
    config: &ExtractionConfig,
) -> Result<Vec<ConnectorDocument>> {
    let path = path
        .canonicalize()
        .with_context(|| format!("resolve path {}", path.display()))?;
    if !path.is_file() {
        anyhow::bail!("not a file: {}", path.display());
    }

    let ext = path
        .extension()
        .and_then(|e| e.to_str())
        .unwrap_or("")
        .to_ascii_lowercase();

    if ext == "json" {
        let bundle = parse_json_documents_file(&path)?;
        if bundle.documents.is_empty() {
            anyhow::bail!("empty `documents` array in {}", path.display());
        }
        let mut records = Vec::with_capacity(bundle.documents.len());
        for (i, entry) in bundle.documents.iter().enumerate() {
            let hash = sha256_hex(&entry.content);
            let source = json_entry_source_label(&path, i);
            records.push(document_record_from_json_entry(table, entry, source, &hash));
        }
        Ok(records)
    } else {
        let extracted = extract_file(&path, None, config)
            .await
            .with_context(|| format!("Kreuzberg extract {}", path.display()))?;
        if !extracted.processing_warnings.is_empty() {
            eprintln!(
                "Kreuzberg warnings ({}): {:?}",
                path.display(),
                extracted.processing_warnings
            );
        }
        let hash = sha256_hex(extracted.content.as_str());
        Ok(vec![document_record_value(
            table,
            &extracted,
            path.display().to_string(),
            &hash,
        )])
    }
}

fn usage() -> &'static str {
    "usage: kreuzberg-surrealdb-example memory|local \n\
     \n\
       memory - SurrealDB in-memory (no server).\n\
       local - SurrealDB at ws://localhost:8000 (expects root/secret, ns/db main)."
}

#[tokio::main]
async fn main() -> Result<()> {
    let mut args = std::env::args_os().skip(1).collect::<Vec<_>>();
    if args.is_empty() {
        eprintln!("{}\n", usage());
        anyhow::bail!("missing mode: memory or local");
    }

    let mode = args.remove(0);
    let mode_str = mode
        .to_str()
        .ok_or_else(|| anyhow::anyhow!("mode must be valid UTF-8"))?;
    let endpoint = match mode_str {
        "memory" => "memory",
        "local" => "ws://localhost:8000",
        _ => {
            eprintln!("{}\n", usage());
            anyhow::bail!(
                "first argument must be `memory` or `local`, got {:?}",
                mode_str
            );
        }
    };

    let paths: Vec<PathBuf> = if args.is_empty() {
        default_asset_paths()
    } else {
        args.into_iter().map(PathBuf::from).collect()
    };

    let config = ExtractionConfig {
        chunking: None,
        language_detection: Some(LanguageDetectionConfig {
            enabled: true,
            min_confidence: 0.55,
            detect_multiple: true,
        }),
        ..Default::default()
    };

    let mut all_docs = Vec::new();
    for p in &paths {
        let mut records = ingest_path(p, TABLE, &config).await?;
        all_docs.append(&mut records);
    }

    //eprintln!("Ingested {} record(s) from {} path(s).", all_docs.len(), paths.len());

    let client = connect(endpoint).await?;
    client.use_ns("main").use_db("main").await?;

    if endpoint == "local" {
        client
            .signin(Root {
                username: "root".into(),
                password: "secret".into(),
            })
            .await?;
    }

    client.query(connector_schema(TABLE)).await?.check()?;

    for doc in &all_docs {
        client
            .query(format!("INSERT IGNORE INTO {TABLE} $doc;"))
            .bind(("doc", doc.clone()))
            .await?
            .check()?;
    }

    let records: Value = client
        .query(
        "SELECT
            source,
            content,
            mime_type,
            title,
            metadata.language AS language,
            search::score(0) AS score
        FROM documents
        WHERE content @0@ 'product'
        ORDER BY score DESC
        LIMIT 10;",
        )
        .await?
        .check()?
        .take(0)?;

    println!("BM25 hits:\n{}", records.to_sql_pretty());

    Ok(())
}
```

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/langchain

# LangChain

Use SurrealDB native vector indexes as a drop-in vector store for LangChain.

[LangChain](https://python.langchain.com/docs/introduction/) is a framework for developing applications powered by large language models (LLMs). SurrealDB suits [LangChain](https://python.langchain.com/docs/introduction/) projects mainly because of its [multi-model capabilities](/docs/learn/data-models.md): a single database manages all of the application's data.

It handles structured and unstructured data, with vector search, [graph traversal](/docs/learn/data-models/graph/overview.md), [relational queries](/docs/learn/data-models.md), [full-text search](/docs/learn/data-models/full-text-search/overview.md), [document storage](/docs/learn/data-models/document/overview.md), and [time-series data](/docs/learn/data-models/time-series/overview.md) all within one ACID-compliant engine.

LangChain applications often work with several types of data for tasks such as context retrieval. Keeping all of them in one database simplifies the architecture, reduces latency, and keeps the data consistent.

This guide shows how to use SurrealDB as a vector store for LangChain.

> [!NOTE]
> This page uses the Python package, `langchain-surrealdb`. For TypeScript and JavaScript, the `@surrealdb/langgraph` package stores LangGraph.js checkpoints, long-term memory and node results in SurrealDB. See [LangGraph](/docs/build/integrations/ai-frameworks/langgraph.md).

<video style="width: 100%;" height="300" controls>
    <source src={VidMp4} type="video/mp4" />
    <source src={VidWebm} type="video/webm" />
</video>

## Setup

You can run SurrealDB locally or start with a [free SurrealDB Cloud account](/docs/manage/instances.md).

For local, two options:

1. [Install SurrealDB](/docs/running/installation.md) and run [SurrealDB](/docs/running/in-memory.md). Run in-memory with:

    ```sh
    surreal db start -u root -p secret
    ```

2. [Run with Docker](/docs/running/docker.md).

    ```sh
    docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest start
    ```

## Install dependencies

```bash
# -- Using pip
pip install -U langchain-surrealdb langchain_ollama surrealdb
# -- Using poetry
poetry add langchain-surrealdb langchain_ollama surrealdb
# -- Using uv
uv add --upgrade langchain-surrealdb langchain_ollama surrealdb
```

* `surrealdb` → [SurrealDB Python SDK](/docs/reference/python/)
* `langchain-surrealdb` → houses `SurrealDBVectorStore`
* `langchain_ollama`, `langchain-openai` (or HF, Cohere, etc.) → embeddings

## Quick start

Create a vector store, and documents with embeddings, and do a similarity search.

```python
from langchain_core.documents import Document
from langchain_surrealdb.vectorstores import SurrealDBVectorStore
from langchain_ollama import OllamaEmbeddings
from surrealdb import Surreal

conn = Surreal("ws://localhost:8000/rpc")
conn.signin({"username": "root", "password": "secret"})
conn.use("langchain", "demo")
vector_store = SurrealDBVectorStore(OllamaEmbeddings(model="llama3.2"), conn)

doc_1 = Document(page_content="foo", metadata={"source": "https://surrealdb.com"})
doc_2 = Document(page_content="SurrealDB", metadata={"source": "https://surrealdb.com"})

vector_store.add_documents(documents=[doc_1, doc_2], ids=["1", "2"])

results = vector_store.similarity_search_with_score(
    query="surreal", k=1, custom_filter={"source": "https://surrealdb.com"}
)

for doc, score in results:
    print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]")
```

Internally the helper will:

1. Create table **`documents`** (if it doesn’t exist).
2. Add an **HNSW** index with the correct dimensionality, using cosine distance and `F32` vectors.
3. Insert each text with its freshly generated embedding.

## Similarity search

```python
query = "How do I enable vector search in SurrealDB?"
docs = vector_store.similarity_search(
    query=query, k=1, custom_filter={"source": "https://surrealdb.com"}
)
for doc in results:
    print(f"{doc.page_content} [{doc.metadata}]")
```

```text
The Vector Search feature of SurrealDB... [{'source': 'https://surrealdb.com'}]
```

If you want to get the score with the results, use `similarity_search_with_score` instead.

You can also transform the vector store into a retriever for easier usage in your chains.

```python
query = "How do I enable vector search in SurrealDB?"
docs = vector_store.similarity_search(
retriever = vector_store.as_retriever(
    search_type="mmr", search_kwargs={"k": 1, "lambda_mult": 0.5}
)
retriever.invoke(query)
```

```text
[Document(id='4', metadata={'source': 'https://surrealdb.com'}, page_content='The Vector Search feature of SurrealDB...')]
```

## Next steps

The following resources cover more advanced use of SurrealDB with LangChain.

Several example implementations are available:

* A [basic example](https://github.com/surrealdb/langchain-surrealdb/tree/main/examples/basic) with a ready-to-use Dockerfile - perfect for your first steps
* A more advanced [graph example](https://github.com/surrealdb/langchain-surrealdb/tree/main/examples/graph) showcasing graph capabilities
* An interactive [Jupyter notebook](https://python.langchain.com/docs/integrations/vectorstores/surrealdb/) for hands-on learning

### Further reading and resources

For a deeper understanding of the technology stack:

* Explore the [SurrealDB vector-search reference](/docs/learn/data-models/vector-search/overview.md) for detailed technical information
* Check out the [LangChain API docs for `SurrealDBStore`](https://api.python.langchain.com/en/latest/vectorstores/langchain_community.vectorstores.surrealdb.SurrealDBStore.html) for the full API documentation
* Browse [Awesome SurrealDB](https://github.com/surrealdb/awesome-surreal) for a curated collection of resources, tools, and applications
That’s it - you now have a fully-featured LangChain vector store powered by SurrealDB, no boilerplate required. 🚀

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/langgraph

# LangGraph

Use SurrealDB as the checkpointer, long-term memory store and node cache for LangGraph.js agents and graphs with the @surrealdb/langgraph package.

[LangGraph.js](https://langchain-ai.github.io/langgraphjs/) is the TypeScript framework from LangChain for building stateful agents and multi-step graphs. The [`@surrealdb/langgraph`](https://www.npmjs.com/package/@surrealdb/langgraph) package stores the three kinds of state that LangGraph persists in a SurrealDB database that you run: conversation checkpoints, long-term memory, and cached node results.

This page is for TypeScript and JavaScript applications. The companion package `@surrealdb/langchain` provides LangChain.js components such as a vector store, retrievers and tools, and its [README](https://github.com/surrealdb/langchain.py#readme) documents them. For Python, see [LangChain](/docs/build/integrations/ai-frameworks/langchain.md).

| Class | LangGraph interface | What it stores |
| --- | --- | --- |
| `SurrealDBSaver` | `BaseCheckpointSaver` | Short-term state: the graph state of each thread after every step, so a conversation can continue, resume after an interrupt, or go back to an earlier step. |
| `SurrealDBStore` | `BaseStore` | Long-term memory: JSON items in namespaces that every thread can read, with optional semantic search. |
| `SurrealDBNodeCache` | `BaseCache` | Node results for nodes that declare a `cachePolicy`, so a repeated input does not run the node again. |

> [!NOTE]
> The same package also includes `AgentMemoryStore`, a read-only store that reads from the hosted [SurrealDB Agent Memory](/docs/agent-memory.md) service instead of your own database. It is documented on the [Agent Memory LangChain page](/docs/agent-memory/integrations/frameworks/langchain.md).

## Requirements

- [SurrealDB v3](/docs/running/installation.md), local or on [SurrealDB Cloud](/docs/manage/instances.md). The adapters check the server version when they connect and throw on v2.
- Node.js 22+ or Bun 1+
- `@langchain/core` 1.2.9+ and `@langchain/langgraph-checkpoint` 1.1.5+. LangChain v0.3 is not supported.

## Setup

Start SurrealDB locally, either [installed directly](/docs/running/installation.md):

```sh
surreal start --user root --pass secret memory
```

or [with Docker](/docs/running/docker.md):

```sh
docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest start --user root --pass secret
```

The `memory` storage engine keeps data only until the server stops. To keep checkpoints across restarts, start the server with a [persistent storage engine](/docs/running/file-backed.md).

## Install dependencies

```sh
bun add @surrealdb/langgraph @langchain/langgraph @langchain/core
```

The quick start below also uses the `langchain` package for `createAgent` and `@langchain/openai` for the model:

```sh
bun add langchain @langchain/openai
```

## Quick start

`createAgent` accepts a checkpointer and a store, so you can pass both adapters to it directly. The checkpointer saves the conversation under the `thread_id` in the run config, which makes the second call aware of the first:

```typescript
import { ChatOpenAI } from "@langchain/openai";
import { SurrealDBSaver, SurrealDBStore } from "@surrealdb/langgraph";
import { createAgent } from "langchain";

const surreal = {
    url: "ws://localhost:8000",
    username: "root",
    password: "secret",
    namespace: "app",
    database: "agent",
};

const checkpointer = new SurrealDBSaver({ surreal });
const store = new SurrealDBStore({ surreal });
await store.start();

const agent = createAgent({
    model: new ChatOpenAI({ model: "gpt-4.1-mini" }),
    tools: [],
    checkpointer,
    store,
});

const config = { configurable: { thread_id: "demo-thread" } };

await agent.invoke(
    { messages: [{ role: "user", content: "My name is Ada." }] },
    config,
);

const result = await agent.invoke(
    { messages: [{ role: "user", content: "What is my name?" }] },
    config,
);

console.log(result.messages.at(-1)?.text);

await checkpointer.close();
await store.stop();
```

Each adapter creates its tables and indexes the first time it is used. You do not need to define a schema first.

## Connect to SurrealDB

Every adapter takes a `surreal` option. Pass it either a connection config or a `SurrealDBClient` that you created yourself.

A connection config signs in with a username and password, or authenticates with a token:

```typescript
// Username and password
const surreal = {
    url: "ws://localhost:8000",
    username: "root",
    password: "secret",
    namespace: "app",
    database: "agent",
};

// Token, for example a record user's JWT
const surrealWithToken = {
    url: "wss://<your-instance>.aws.surrealdb.cloud",
    token: process.env.SURREALDB_TOKEN!,
    namespace: "app",
    database: "agent",
};
```

| Field | Required | Notes |
| --- | --- | --- |
| `url` | Yes | WebSocket or HTTP endpoint of the SurrealDB server. |
| `username`, `password` | One of these or `token` | Credentials for `signin`. |
| `token` | One of these or `username` and `password` | An existing access token for `authenticate`. |
| `namespace` | No | Defaults to `langchain`. |
| `database` | No | Defaults to `langchain`. |

An adapter that receives a config opens its own connection and closes it in `close()` or `stop()`. To use one connection for several adapters, create a `SurrealDBClient` and pass it to each one. An adapter does not close a client that you passed in, so close it yourself when the application stops:

```typescript
import {
    SurrealDBClient,
    SurrealDBNodeCache,
    SurrealDBSaver,
    SurrealDBStore,
} from "@surrealdb/langgraph";

const client = new SurrealDBClient({
    url: "ws://localhost:8000",
    username: "root",
    password: "secret",
    namespace: "app",
    database: "agent",
});

const checkpointer = new SurrealDBSaver({ surreal: client });
const store = new SurrealDBStore({ surreal: client });
const cache = new SurrealDBNodeCache({ surreal: client });

// ...

await client.close();
```

## Checkpointer

`SurrealDBSaver` implements the full LangGraph `BaseCheckpointSaver` contract. Pass it as `checkpointer` to `createAgent`, or to `compile()` on a `StateGraph`:

```typescript
import { SurrealDBSaver } from "@surrealdb/langgraph";

const checkpointer = new SurrealDBSaver({ surreal });

const graph = builder.compile({ checkpointer });

await graph.invoke(input, { configurable: { thread_id: "user-42" } });
```

LangGraph writes a checkpoint after each step of a run. Checkpoint data is serialised with the LangGraph serialiser and stored as SurrealDB `bytes`, so message objects and other state values come back with their types intact.

### Read the history of a thread

`list()` returns checkpoints newest first. It supports the `before`, `limit` and `filter` options, and fills in `pendingWrites`, so `getStateHistory()` on a compiled graph also reports pending tasks:

```typescript
const config = { configurable: { thread_id: "user-42" } };

for await (const tuple of checkpointer.list(config, { limit: 5 })) {
    console.log(
        tuple.config.configurable?.checkpoint_id,
        tuple.metadata?.step,
        `${tuple.pendingWrites?.length ?? 0} pending writes`,
    );
}
```

Leave out `thread_id` to list checkpoints across all threads, and leave out `checkpoint_ns` to list across all namespaces. `filter` matches fields in the checkpoint metadata and takes the same operators as the store filter described in [Filter items](#filter-items).

### Delete a thread

`deleteThread()` removes every checkpoint and pending write for a thread in one transaction:

```typescript
await checkpointer.deleteThread("user-42");
```

### Options

| Option | Default | Notes |
| --- | --- | --- |
| `surreal` | Required | A connection config or a `SurrealDBClient`. |
| `checkpointsTable` | `langgraph_checkpoints` | Table for checkpoints. |
| `writesTable` | `langgraph_checkpoint_writes` | Table for pending writes. |
| `serde` | `JsonPlusSerializer` | A LangGraph `SerializerProtocol`. |
| `skipInitSchema` | `false` | Do not create tables and indexes. See [Manage the schema yourself](#manage-the-schema-yourself). |
| `skipVersionCheck` | `false` | Do not check that the server is v3 or later. |

> [!NOTE]
> The checkpointer reads checkpoint format v4 and later, which is the format that current LangGraph versions write. It refuses to load an older checkpoint rather than return state in a format the graph cannot use.

## Long-term memory store

`SurrealDBStore` implements the LangGraph `BaseStore`. A store holds JSON items that stay available across threads, such as user preferences or facts that an agent learnt in an earlier conversation. Each item has a namespace, which is an array of strings, and a key that is unique within that namespace.

Call `start()` once before the first use. It connects, and creates the table and indexes:

```typescript
import { SurrealDBStore } from "@surrealdb/langgraph";

const store = new SurrealDBStore({ surreal });
await store.start();

await store.put(["users", "alice", "preferences"], "theme", { value: "dark" });
await store.put(["users", "alice", "preferences"], "language", { value: "en" });
await store.put(["users", "bob", "preferences"], "theme", { value: "light" });

const theme = await store.get(["users", "alice", "preferences"], "theme");
console.log(theme?.value);
```

```text title="Output"
{ value: 'dark' }
```

`put()` on an existing namespace and key replaces the item and updates its `updatedAt` time. `delete(namespace, key)` removes an item.

`search()` takes a namespace prefix and returns the items under it, newest first. `listNamespaces()` returns the distinct namespaces under a prefix, and `maxDepth` shortens each one:

```typescript
const items = await store.search(["users", "alice"]);
console.log(items.length);

const namespaces = await store.listNamespaces({ prefix: ["users"], maxDepth: 2 });
console.log(namespaces);
```

```text title="Output"
2
[ [ 'users', 'alice' ], [ 'users', 'bob' ] ]
```

Inside a graph node or an agent tool, LangGraph passes the store in the runtime config, so the node reads and writes the same SurrealDB table without a reference of its own.

### Filter items

`filter` matches fields inside the item value. A bare value means equality, several keys must all match, and these operators are supported: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin` and `$exists`.

```typescript
await store.search(["users"], {
    filter: { value: { $in: ["dark", "high-contrast"] } },
    limit: 20,
});
```

### Semantic search

To search items by meaning, give the store an `index` with an embeddings model and the number of dimensions it produces. The store then embeds each item when it is saved and defines an [HNSW vector index](/docs/learn/data-models/vector-search/overview.md) on the table:

```typescript
import { OpenAIEmbeddings } from "@langchain/openai";
import { SurrealDBStore } from "@surrealdb/langgraph";

const store = new SurrealDBStore({
    surreal,
    index: {
        dims: 1536,
        embeddings: new OpenAIEmbeddings({ model: "text-embedding-3-small" }),
        fields: ["text"],
    },
});
await store.start();

await store.put(["users", "alice"], "pref-ui", { text: "Prefers dark mode" });
await store.put(["users", "alice"], "pref-food", { text: "Is vegetarian" });

const hits = await store.search(["users", "alice"], {
    query: "user interface settings",
    limit: 1,
});

console.log(hits[0]?.key, hits[0]?.score);
```

`fields` names the paths in the value to embed. Without it, the store embeds the whole value as JSON. To skip embedding for one item, pass `false` as the fourth argument to `put()`. With the default cosine distance, `score` is the cosine similarity, so a higher score is a closer match.

If `search()` gets a `query` but the store has no `index`, it logs a warning once and returns recent items without ranking. Set `strictSearch: true` to throw an error instead.

> [!IMPORTANT]
> The vector index is created with the `dims` value from the first `start()`. If you change the embeddings model to one with a different number of dimensions, remove the index or use a new `tableName`.

### Options

| Option | Default | Notes |
| --- | --- | --- |
| `surreal` | Required | A connection config or a `SurrealDBClient`. |
| `tableName` | `langgraph_store` | Table for items. |
| `index` | None | `{ dims, embeddings, fields?, distanceStrategy? }`. Enables semantic search. |
| `index.distanceStrategy` | `cosine` | `cosine`, `euclidean`, `manhattan` or `hamming`. |
| `indexType` | `hnsw` | `hnsw`, `diskann` (SurrealDB 3.1+), or `none` for a brute-force search. |
| `ef` | `40` | Search breadth for the vector index. A higher value is more accurate and slower. |
| `strictSearch` | `false` | Throw when `search()` gets a `query` and no `index` is configured. |
| `skipInitSchema` | `false` | Do not create the table and indexes. |
| `skipVersionCheck` | `false` | Do not check that the server is v3 or later. |

The store runs the operations in a batch one after another, not in a single transaction. A `put()` with an `index` calls the embeddings provider, and a transaction would keep a write lock open during that network call.

## Node cache

LangGraph v1 lets a node declare a `cachePolicy`. When the same node runs again with the same input before the time to live (TTL) ends, LangGraph returns the cached result and does not run the node. `SurrealDBNodeCache` keeps those results in SurrealDB, so the cache is shared between processes and stays after a restart. Pass it as `cache` to `compile()`:

```typescript
import { Annotation, END, START, StateGraph } from "@langchain/langgraph";
import { SurrealDBNodeCache } from "@surrealdb/langgraph";

const cache = new SurrealDBNodeCache({ surreal });

const State = Annotation.Root({
    input: Annotation<string>(),
    output: Annotation<string>(),
});

let calls = 0;

const graph = new StateGraph(State)
    .addNode(
        "summarise",
        async (state) => {
            calls++;
            return { output: state.input.toUpperCase() };
        },
        { cachePolicy: { ttl: 120 } },
    )
    .addEdge(START, "summarise")
    .addEdge("summarise", END)
    .compile({ cache });

await graph.invoke({ input: "hello" });
await graph.invoke({ input: "hello" });

console.log(`node ran ${calls} time(s)`);

await cache.close();
```

```text title="Output"
node ran 1 time(s)
```

`ttl` is in seconds. A cached result without a TTL does not expire.

LangGraph does not wait for a cache write to finish, so a write that fails cannot stop the graph. The cache catches the error and passes it to `onError`, which logs a console warning by default:

```typescript
const cache = new SurrealDBNodeCache({
    surreal,
    onError: (err) => logger.error({ err }, "node cache write failed"),
});
```

| Option | Default | Notes |
| --- | --- | --- |
| `surreal` | Required | A connection config or a `SurrealDBClient`. |
| `tableName` | `langgraph_cache` | Table for cached results. |
| `serde` | `JsonPlusSerializer` | A LangGraph `SerializerProtocol`. |
| `onError` | Console warning | Called when a cache write fails. |
| `skipInitSchema` | `false` | Do not create the table. |
| `skipVersionCheck` | `false` | Do not check that the server is v3 or later. |

## Manage the schema yourself

By default, each adapter runs `DEFINE TABLE IF NOT EXISTS`, `DEFINE FIELD` and `DEFINE INDEX` statements the first time it connects. This needs a user that can define tables, such as a namespace or database [system user](/docs/reference/query-language/statements/define/user.md) with the `EDITOR` or `OWNER` role.

To run the application as a user with fewer permissions, create the schema once with a separate user or a migration, then set `skipInitSchema: true` on each adapter. The tables are:

| Adapter | Default tables |
| --- | --- |
| `SurrealDBSaver` | `langgraph_checkpoints`, `langgraph_checkpoint_writes` |
| `SurrealDBStore` | `langgraph_store` |
| `SurrealDBNodeCache` | `langgraph_cache` |

The quickest way to get the exact definitions is to let the adapters create the tables in a development database, then run [`INFO FOR TABLE`](/docs/reference/query-language/statements/info.md) on each one or use [`surreal export`](/docs/reference/cli/surrealdb-cli/commands/export.md).

## Close connections

An adapter that created its own connection closes it in `checkpointer.close()`, `store.stop()` or `cache.close()`. Call these when the process shuts down. If you passed a shared `SurrealDBClient`, these methods leave it open, and you close the client instead.

## Examples and source

- The [adapter repository](https://github.com/surrealdb/langchain.py) contains the source and runnable examples for an agent with a checkpointer, long-term memory, and node caching.
- [LangChain](/docs/build/integrations/ai-frameworks/langchain.md) covers SurrealDB as a vector store for LangChain in Python.
- [Vector search](/docs/learn/data-models/vector-search/overview.md) explains the HNSW and DiskANN indexes that semantic search uses.
- [Agent Memory LangChain integration](/docs/agent-memory/integrations/frameworks/langchain.md) covers retrievers, tools and `AgentMemoryStore` for the hosted memory service.

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/llamaindex

# LlamaIndex

Use SurrealDB’s native HNSW vector index as the backing store for a Llama Index `VectorStoreIndex`.

Llama Index is a framework for building RAG pipelines. It provides a flexible and powerful way to build and deploy RAG systems.

> [!NOTE]
> Llama Index doesn’t (yet!) ship an off-the-shelf SurrealDB adapter, so in this guide we’ll implement a **20-line `SurrealVectorStore`** that satisfies Llama Index’s minimal vector-store interface. You can drop it into any RAG pipeline just like you would with Pinecone, Qdrant, Chroma, etc.

## TL;DR

1. **Install** `surrealdb` + `llama-index`.
2. **Create** a table with an `HNSW` index.
3. **Drop-in** the 20-line `SurrealVectorStore`.
4. Use `VectorStoreIndex.from_documents()` and you’re done.

## Install the libraries

```bash
pip install surrealdb llama-index numpy
```

## Connect to SurrealDB

```python
from surrealdb import Surreal
import asyncio, os

# - connection details -
DB_URL   = os.getenv("SDB_URL", "http://localhost:8000/rpc")
DB_USER  = os.getenv("SDB_USER", "root")
DB_PASS  = os.getenv("SDB_PASS", "secret")
NS, DB   = "demo", "demo"
TABLE    = "llama_chunks"

sdb = Surreal(DB_URL)

async def init():
    await sdb.signin({"user": DB_USER, "pass": DB_PASS})
    await sdb.use(NS, DB)

asyncio.run(init())
```

## Create the table and HNSW index

```python
# SurrealDB ≥ v1.5 exposes an in-memory HNSW ANN index
schema = """
DEFINE TABLE $tb SCHEMALESS PERMISSIONS NONE;
DEFINE FIELD text      ON $tb TYPE string;
DEFINE FIELD embedding ON $tb TYPE array;

DEFINE INDEX idx_hnsw ON $tb
  FIELDS embedding
  HNSW DIMENSION 768
  DIST   COSINE;
"""
asyncio.run(sdb.query(schema, {"tb": TABLE}))
```

SurrealQL’s `DEFINE INDEX … HNSW` activates a high-speed, cosine-distance ANN index.

## A tiny SurrealDB adapter for Llama Index

```python
import numpy as np
from typing import Any, List, Sequence
from llama_index.core.vector_stores.types import (
    BasePydanticVectorStore,
    VectorStoreQuery,
    VectorStoreQueryResult,
)
from llama_index.core.schema import NodeWithEmbedding

class SurrealVectorStore(BasePydanticVectorStore):
    """Minimal Llama Index adapter using SurrealDB’s <|K,EF|> operator."""

    def __init__(self, table: str, conn: Surreal):
        self.table, self.conn = table, conn

    # ---- required APIs ----------------------------------------------------
    def add(self, nodes: Sequence[NodeWithEmbedding], **_) -> List[str]:
        rows = [
            {
                "id": f"{self.table}:{i}",
                "text": n.get_content(metadata_mode="all"),
                "embedding": n.embedding,
            }
            for i, n in enumerate(nodes)
        ]
        asyncio.run(self.conn.query(f"INSERT INTO {self.table} $data", {"data": rows}))
        return [r["id"] for r in rows]

    def delete(self, doc_id: str, **_) -> None:  # optional but easy
        asyncio.run(self.conn.delete(doc_id))

    def query(
        self, query: VectorStoreQuery, **_
    ) -> VectorStoreQueryResult:  # semantic search
        q_vec = query.query_embedding
        surql = """
        LET $q := $vec;
        SELECT id, text, vector::distance::knn() AS score
        FROM {self.table}
        WHERE embedding <|{query.similarity_top_k},64|> $q
        ORDER BY score;
        """
        res = asyncio.run(self.conn.query(surql, {"vec": q_vec}))[0].result
        return VectorStoreQueryResult(
            nodes=[],  # Llama Index will fetch raw docs separately
            ids=[r["id"] for r in res],
            similarities=[r["score"] for r in res],
        )
```

The `SurrealVectorStore` class is a minimal adapter cribbed from the official [build a vector store from scratch](https://docs.llamaindex.ai/en/stable/examples/low_level/vector_store/) example.

## Index documents with Llama Index

```python
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

# 5-1  load + chunk a handful of docs (replace with your own)
docs = SimpleDirectoryReader("./my_pdfs").load_data()

# 5-2  build the index - Llama Index will call SurrealVectorStore.add()
surreal_store = SurrealVectorStore(table=TABLE, conn=sdb)
index = VectorStoreIndex.from_documents(
    docs,
    vector_store=surreal_store,
    show_progress=True,
)
```

## Query

```python
qe   = index.as_query_engine(similarity_top_k=4)
resp = qe.query("Which document talks about vector search in SurrealDB?")
print(resp)
```

The query flow is:

```text
User → Llama Index → SurrealVectorStore.query()
          ↘︎ top-K doc IDs & scores
                  ↘︎ fetch full text → synthesize answer
```

## What about metadata filters, deletes, hybrid search?

SurrealDB supports **metadata-rich JSON payloads**, additional **filter clauses**, and full-text search; extend `SurrealVectorStore.query()` to:

* add `WHERE` predicates before the `<|K,EF|>` operator,
* combine with `vector::distance::knn()` for re-ranking,
* or fall back to `vector::similarity::cosine()` for exact search.

All other Llama Index abstractions (retrievers, query engines, agents) work unchanged, because they talk to the vector store through the same tiny interface you just implemented.

Enjoy Llama Index + SurrealDB!

## References

- [DEFINE INDEX statement](/docs/reference/query-language/statements/define/indexes.md)
- [Building a RAG with Astro, FastAPI, SurrealDB and Llama 3.1](https://fireworks.ai/blog/rag-with-astro-fastapi-surrealdb-tailwind)
- [Building a (Very Simple) Vector Store from Scratch - LlamaIndex](https://docs.llamaindex.ai/en/stable/examples/low_level/vector_store/)

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/mastra

# Mastra

Use SurrealDB as the storage backend for Mastra agents, covering conversation memory, workflow snapshots, scores, observability spans, and native vector search.

[Mastra](https://mastra.ai) is a TypeScript framework for building AI agents, workflows, and RAG pipelines. [`@surrealdb/mastra-ai`](https://github.com/surrealdb/mastra-ai) provides `SurrealDBStore`, a storage adapter that backs Mastra with a single SurrealDB instance: conversation memory (threads, messages, and working memory), workflow suspend/resume snapshots, scores, observability spans, and [HNSW vector indexes](/docs/learn/data-models/vector-search/overview.md) for RAG.

Because SurrealDB is [multi-model](/docs/learn/data-models.md), one database covers everything a Mastra application persists. Message history, workflow state, and vector embeddings live in the same ACID-compliant engine, so there is no separate vector database to deploy or keep in sync.

> [!NOTE]
> This page covers the storage adapter, which runs against a SurrealDB instance you manage. The same package also integrates with [SurrealDB Agent Memory](/docs/agent-memory/integrations/frameworks/mastra.md), a hosted memory provider for fact extraction and semantic recall.

## Requirements

- [SurrealDB v3](/docs/running/installation.md), local or on [SurrealDB Cloud](/docs/manage/instances.md)
- Bun 1+ or Node.js 22+
- `@mastra/core` 1.31.0+

## Setup

Start SurrealDB locally, either [installed directly](/docs/running/installation.md):

```sh
surreal db start --user root --pass secret memory
```

or [with Docker](/docs/running/docker.md):

```sh
docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest start --user root --pass secret
```

Alternatively, create a managed instance on [SurrealDB Cloud](/docs/manage/instances.md) and connect with its `wss://` URL.

## Install dependencies

```sh
bun add @surrealdb/mastra-ai
```

## Quick start

Create a store, pass it to `Mastra` as `storage`, and call `store.init()` to connect and apply the table schemas. The agent's conversation history then persists in SurrealDB, keyed by `resourceId` and `threadId`:

```typescript
import { Mastra } from "@mastra/core/mastra";
import { Agent } from "@mastra/core/agent";
import { anthropic } from "@ai-sdk/anthropic";
import { SurrealDBStore } from "@surrealdb/mastra-ai";

const store = new SurrealDBStore({
    id: "my-store",
    url: "ws://localhost:8000",
    username: "root",
    password: "secret",
    namespace: "mastra",
    database: "my_app",
});

const agent = new Agent({
    name: "assistant",
    instructions: "You are a helpful assistant.",
    model: anthropic("claude-sonnet-4-6"),
});

const mastra = new Mastra({
    agents: { assistant: agent },
    storage: store,
});

await store.init();

const response = await mastra.getAgent("assistant").generate("Hello!", {
    resourceId: "user-001",
    threadId: "thread-001",
});

console.log(response.text);
await store.close();
```

## Configuration

`SurrealDBStore` accepts three configuration shapes. `namespace` and `database` are optional in the first two and both default to `mastra`.

Username and password:

```typescript
new SurrealDBStore({
    id: "my-store",
    url: "ws://localhost:8000",
    username: "root",
    password: "secret",
    namespace: "mastra",
    database: "my_app",
});
```

Token authentication, for example against a SurrealDB Cloud instance:

```typescript
new SurrealDBStore({
    id: "my-store",
    url: "wss://cloud.surrealdb.com",
    token: "<your-jwt-token>",
    namespace: "mastra",
    database: "my_app",
});
```

A pre-connected `Surreal` instance from the [JavaScript SDK](/docs/reference/javascript.md), when your application already manages its own connection:

```typescript
import { Surreal } from "surrealdb";

const db = new Surreal();
await db.connect("ws://localhost:8000");

new SurrealDBStore({ id: "my-store", db });
```

## Workflow suspend and resume

Mastra workflows can suspend mid-run and resume later, for example to wait for human approval. With `SurrealDBStore` as storage, each snapshot is written atomically, so a run survives a process restart and resumes from the suspended step:

```typescript
import { Mastra } from "@mastra/core/mastra";
import { createWorkflow, createStep } from "@mastra/core/workflows";
import { SurrealDBStore } from "@surrealdb/mastra-ai";
import { z } from "zod";

const store = new SurrealDBStore({ id: "store", url: "ws://localhost:8000", username: "root", password: "secret" });
const mastra = new Mastra({ storage: store });

const approveStep = createStep({
    id: "approve",
    inputSchema: z.object({ value: z.number() }),
    resumeSchema: z.object({ approved: z.boolean() }),
    outputSchema: z.object({ approved: z.boolean() }),
    execute: async ({ inputData, resumeData, suspend }) => {
        if (!resumeData) {
            await suspend({});
        }
        return { approved: resumeData!.approved };
    },
});

const workflow = createWorkflow({
    id: "approval",
    mastra,
    inputSchema: z.object({ value: z.number() }),
    outputSchema: z.object({ approved: z.boolean() }),
    steps: [approveStep],
}).then(approveStep).commit();

await store.init();

const run = workflow.createRun();
await run.start({ inputData: { value: 42 } });

await run.resume({
    step: approveStep,
    resumeData: { approved: true },
});

await store.close();
```

## Observational Memory

`SurrealDBStore` is a supported backend for Mastra's [Observational Memory](https://mastra.ai/docs/memory/observational-memory), the observer/reflector system in `@mastra/memory` that compresses long message histories into observations. [Memory extractors](https://mastra.ai/blog/introducing-memory-extractors) also work, pulling structured facts out of conversations during observation cycles, and extracted values persist through the same SurrealDB tables. Observational Memory requires `@mastra/memory` 1.1.0+, and extractors require 1.22.0+:

```typescript
import { Extractor, Memory } from "@mastra/memory";
import { SurrealDBStore } from "@surrealdb/mastra-ai";
import { z } from "zod";

const store = new SurrealDBStore({
    id: "om-store",
    url: "ws://localhost:8000",
    username: "root",
    password: "secret",
});
await store.init();

const memory = new Memory({
    storage: store,
    options: {
        observationalMemory: {
            model: "anthropic/claude-haiku-4-5",
            observation: {
                extract: [
                    new Extractor({
                        name: "User profile",
                        instructions: "Extract stable user profile facts.",
                        schema: z.object({
                            preferredName: z.string().optional(),
                            timezone: z.string().optional(),
                        }),
                    }),
                ],
            },
        },
    },
});
```

## Vector search

SurrealDB v3 includes native [HNSW vector indexes](/docs/learn/data-models/vector-search/overview.md), so the same database that stores agent memory also serves RAG queries. The package exposes `SurrealDBClient` for raw SurrealQL, letting you define a schema, upsert documents with embeddings, and run a k-nearest-neighbour search:

```typescript
import { SurrealDBClient } from "@surrealdb/mastra-ai";

const SCHEMA = `
DEFINE TABLE IF NOT EXISTS documents SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS content   ON documents TYPE string;
DEFINE FIELD IF NOT EXISTS embedding ON documents TYPE array<float>;
DEFINE INDEX IF NOT EXISTS idx_hnsw
  ON documents FIELDS embedding HNSW DIMENSION 1536 DIST COSINE;
`;

const client = new SurrealDBClient({ id: "rag", url: "ws://localhost:8000", username: "root", password: "secret" });
await client.connect();
await client.execute(SCHEMA);

await client.execute(
    `UPSERT type::record('documents', $id) CONTENT $data`,
    { id: "doc-1", data: { content: "SurrealDB supports vector search.", embedding: [] } },
);

const results = await client.queryAll(
    `SELECT content, vector::distance::cosine(embedding, $qe) AS dist
     FROM documents WHERE embedding <|5|> $qe ORDER BY dist ASC`,
    { qe: [] },
);
```

## API

### `SurrealDBStore`

| Member | Description |
| ------ | ----------- |
| `init()` | Connect and apply all table schemas |
| `close()` | Disconnect |
| `client` | The underlying `SurrealDBClient` for raw queries |
| `stores` | Individual domain stores (`memory`, `workflows`, `scores`, `observability`) |

### `SurrealDBClient`

| Method | Description |
| ------ | ----------- |
| `connect(config?)` | Open the WebSocket connection |
| `close()` | Disconnect |
| `queryAll<T>(surql, bindings?)` | Run a query and return all rows |
| `queryOne<T>(surql, bindings?)` | Run a query and return the first row or `null` |
| `execute(surql, bindings?)` | Run a statement with no return value |
| `txBatch(statements, bindings?)` | Run statements in a single `BEGIN`/`COMMIT TRANSACTION` request |

## Next steps

The [repository](https://github.com/surrealdb/mastra-ai) includes runnable examples for each area:

- [basic-agent](https://github.com/surrealdb/mastra-ai/tree/main/examples/basic-agent): multi-turn agent conversation with SurrealDB memory
- [workflow-persistence](https://github.com/surrealdb/mastra-ai/tree/main/examples/workflow-persistence): suspend/resume workflow with snapshot storage
- [rag-pipeline](https://github.com/surrealdb/mastra-ai/tree/main/examples/rag-pipeline): vector similarity search with SurrealDB HNSW indexes
- [observational-memory](https://github.com/surrealdb/mastra-ai/tree/main/examples/observational-memory): Observational Memory and extractors on SurrealDB

For hosted memory with fact extraction and semantic recall, see the [SurrealDB Agent Memory integration for Mastra](/docs/agent-memory/integrations/frameworks/mastra.md).

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/overview

# AI framework integrations

This section contains information about the different frameworks that can be used to integrate with SurrealDB.

SurrealDB integrates with popular AI and data frameworks, so you can use features such as vector search, graph relationships, and structured data storage from within them. These integrations help you build applications that combine LLMs, agents, data pipelines and more, using tools and frameworks you already know.

<table style={{ width: '100%' }}>
    <thead>
        <tr>
            <th>Integration</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/agno.md">Agno</a></td>
            <td>Agno is a python framework for building multi-agent systems with shared memory, knowledge and reasoning.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/vercel/ai-sdk.md">AI SDK</a></td>
            <td>Vercel's TypeScript library for language and embedding models, with SurrealDB for vector search tools, graph tools and chat history.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/camel.md">Camel</a></td>
            <td>A Python framework for building multi-agent LLM systems with SurrealDB vector storage capabilities.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/cocoindex.md">CocoIndex</a></td>
            <td>An incremental indexing framework for AI agents with declarative SurrealDB table, relation, and vector targets.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/crewai.md">CrewAI</a></td>
            <td>A framework for orchestrating role-playing AI agents with SurrealDB for entity and short-term memory.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/dagster.md">Dagster</a></td>
            <td>A data orchestration framework with SurrealDB vector search integration for ML pipelines.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/vercel/eve.md">Eve</a></td>
            <td>Vercel's agent framework, with SurrealDB tools for your data and a SurrealDB memory provider.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/google-agent.md">Google Agent</a></td>
            <td>A framework for building and deploying intelligent agents in Google Cloud with SurrealDB vector storage for RAG.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/hermes.md">Hermes</a></td>
            <td>Nous Research's terminal agent, given a persistent filesystem in SurrealDB as a toolset and as a memory provider.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/kreuzberg.md">Kreuzberg</a></td>
            <td>A polyglot document intelligence framework to extract text, metadata, images, and structured information from documents (PDFs,images, etc.).</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/langchain.md">LangChain</a></td>
            <td>A framework for building LLM based applications.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/langgraph.md">LangGraph</a></td>
            <td>A TypeScript framework for stateful agents and graphs, with SurrealDB as the checkpointer, long-term memory store and node cache.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/llamaindex.md">Llama Index</a></td>
            <td>A framework for building RAG pipelines with SurrealDB's native HNSW vector index as the backing store.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/mastra.md">Mastra</a></td>
            <td>A TypeScript agent framework with SurrealDB as a storage backend for conversation memory, workflow snapshots, and vector search.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/pydantic-ai.md">Pydantic AI</a></td>
            <td>A Python framework designed to help you quickly, confidently, and painlessly build production grade applications and workflows with Generative AI.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/ai-frameworks/smolagents.md">Smol Agents</a></td>
            <td>A complete walkthrough for building a code-generating AI agent that recommends grocery items by querying SurrealDB's HNSW vector index.</td>
        </tr>
    </tbody>
</table>

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/pydantic-ai

# Pydantic AI

This section contains information about the Pydantic AI framework and how to integrate it with SurrealDB.

[Pydantic AI](https://ai.pydantic.dev) is a Python agent framework for building production-grade applications and workflows with Generative AI.

## Example

This is a simple RAG application that uses Pydantic AI and embedded SurrealDB. The integration is done by providing the agent with a custom retrieval tool, which takes a search query, executes a SurrealDB vector-search query, and returns the results.

**To run the example:**

Set up your OpenAI API key:

**Bash**

```bash
export OPENAI_API_KEY=your-api-key
```

**PowerShell**

```powershell
$env:OPENAI_API_KEY = "your-api-key"
```

Or, store it in a .env file and add `--env-file .env` to your `uv run` commands.

Build the vector store:

```bash
uv run --env-file .env -m pydantic_ai_examples.rag_surrealdb build
```

Ask the agent a question:

```bash
uv run --env-file .env -m pydantic_ai_examples.rag_surrealdb search "How do I register a function as a custom tool for my agent?"
```

Or use the web UI:

```bash
uv run --env-file .env -m pydantic_ai_examples.rag_surrealdb web
```

### Code

```python
from __future__ import annotations as _annotations

import asyncio
import re
import sys
import unicodedata
from collections.abc import Sequence
from contextlib import asynccontextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import TypeVar

import httpx
import logfire
import uvicorn
from pydantic import BaseModel, TypeAdapter
from surrealdb import (
    AsyncEmbeddedSurrealConnection,
    AsyncHttpSurrealConnection,
    AsyncSurreal,
    AsyncWsSurrealConnection,
    RecordID,
    Value,
)
from typing_extensions import AsyncGenerator

from pydantic_ai import Agent, Embedder

SurrealConn = (
    AsyncWsSurrealConnection
    | AsyncHttpSurrealConnection
    | AsyncEmbeddedSurrealConnection
)

# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
logfire.configure(send_to_logfire='if-token-present')
logfire.instrument_pydantic_ai()
logfire.instrument_surrealdb()

THIS_DIR = Path(__file__).parent

SURREALDB_NS = 'pydantic_ai_examples'
SURREALDB_DB = 'rag_surrealdb'
SURREALDB_USER = 'root'
SURREALDB_PASS = 'secret'

embedder = Embedder('openai:text-embedding-3-small')
agent = Agent('openai:gpt-5.2')

RecordType = TypeVar('RecordType')


class RetrievalQueryResult(BaseModel):
    url: str
    title: str
    content: str
    dist: float


async def query(
    conn: SurrealConn,
    query_: str,
    vars_: dict[str, Value],
    record_type: type[RecordType],
) -> list[RecordType]:
    result = await conn.query(query_, vars_)
    result_ta = TypeAdapter(list[record_type])
    rows = result_ta.validate_python(result)
    return rows


@agent.tool_plain
async def retrieve(search_query: str) -> str:
    """Retrieve documentation sections based on a search query.

    Args:
        search_query: The search query.
    """
    with logfire.span(
        'create embedding for {search_query=}', search_query=search_query
    ):
        result = await embedder.embed_query(search_query)
        embedding = result.embeddings

    # Embedder method guarantees there's one item here
    embedding_vector = embedding[0]

    # SurrealDB vector search using HNSW index
    async with database_connect(False) as db:
        rows = await query(
            db,
            """
            SELECT url, title, content, vector::distance::knn() AS dist
            FROM doc_sections
            WHERE embedding <|8, 40|> $vector
            ORDER BY dist ASC
            """,
            {'vector': list(embedding_vector)},
            RetrievalQueryResult,
        )

    return '\n\n'.join(
        f'# {row.title}\nDocumentation URL:{row.url}\n\n{row.content}' for row in rows
    )


async def run_agent(question: str):
    """Entry point to run the agent and perform RAG based question answering."""
    logfire.info('Asking "{question}"', question=question)
    answer = await agent.run(question)
    print(answer.output)


# Web chat UI
app = agent.to_web()

#######################################################
# The rest of this file is dedicated to preparing the #
# search database, and some utilities.                #
#######################################################

# JSON document from
# https://gist.github.com/samuelcolvin/4b5bb9bb163b1122ff17e29e48c10992
DOCS_JSON = (
    'https://gist.githubusercontent.com/'
    'samuelcolvin/4b5bb9bb163b1122ff17e29e48c10992/raw/'
    '80c5925c42f1442c24963aaf5eb1a324d47afe95/logfire_docs.json'
)


def build_doc_rec_id(url: str) -> RecordID:
    return RecordID('doc_sections', slugify(url, '_'))


async def build_search_db():
    """Build the search database."""
    async with httpx.AsyncClient() as client:
        response = await client.get(DOCS_JSON)
        response.raise_for_status()
    sections = sections_ta.validate_json(response.content)

    async with database_connect(True) as db:
        missing_sections: list[DocsSection] = []
        for section in sections:
            url = section.url()
            record_id = build_doc_rec_id(url)
            existing = await db.select(record_id)
            if existing:
                logfire.info('Skipping {url=}', url=url)
                continue
            missing_sections.append(section)

        if missing_sections:
            with logfire.span('create embeddings'):
                result = await embedder.embed_documents(
                    [section.embedding_content() for section in missing_sections]
                )
                embeddings = result.embeddings

            for section, embedding_vector in zip(
                missing_sections, embeddings, strict=True
            ):
                await insert_doc_section(db, section, embedding_vector)
        else:
            logfire.info('All documents already exist; skipping embedding generation')


async def insert_doc_section(
    db: SurrealConn,
    section: DocsSection,
    embedding_vector: Sequence[float],
) -> None:
    url = section.url()
    record_id = build_doc_rec_id(url)

    # Create record with embedding, using record ID directly
    res = await db.create(
        record_id,
        {
            'url': url,
            'title': section.title,
            'content': section.content,
            'embedding': list(embedding_vector),
        },
    )
    if not isinstance(res, dict):
        raise ValueError(f'Unexpected response from database: {res}')


@dataclass
class DocsSection:
    id: int
    parent: int | None
    path: str
    level: int
    title: str
    content: str

    def url(self) -> str:
        url_path = re.sub(r'\.md$', '', self.path)
        return (
            f'https://logfire.pydantic.dev/docs/{url_path}/#{slugify(self.title, "-")}'
        )

    def embedding_content(self) -> str:
        return '\n\n'.join((f'path: {self.path}', f'title: {self.title}', self.content))


sections_ta = TypeAdapter(list[DocsSection])


@asynccontextmanager
async def database_connect(
    create_db: bool = False,
) -> AsyncGenerator[SurrealConn, None]:
    # Running SurrealDB embedded
    db_path = THIS_DIR / f'.{SURREALDB_DB}'
    db_url = f'file://{db_path}'
    requires_auth = False

    # Running SurrealDB in a separate process, connect with URL
    # db_url = 'ws://localhost:8000/rpc'
    # requires_auth = True

    async with AsyncSurreal(db_url) as db:
        # Sign in to the database
        if requires_auth:
            await db.signin({'username': SURREALDB_USER, 'password': SURREALDB_PASS})

        # Set namespace and database
        await db.use(SURREALDB_NS, SURREALDB_DB)

        # Initialize schema if creating database
        if create_db:
            with logfire.span('create schema'):
                await db.query(DB_SCHEMA)

        yield db


DB_SCHEMA = """
DEFINE TABLE doc_sections SCHEMALESS;

DEFINE FIELD embedding ON doc_sections TYPE array<float>;

DEFINE INDEX hnsw_idx_doc_sections ON doc_sections
    FIELDS embedding
    HNSW DIMENSION 1536
    DIST COSINE
    TYPE F32;
"""


def slugify(value: str, separator: str, unicode: bool = False) -> str:
    """Slugify a string, to make it URL friendly."""
    # Taken unchanged from https://github.com/Python-Markdown/markdown/blob/3.7/markdown/extensions/toc.py#L38
    if not unicode:
        # Replace Extended Latin characters with ASCII, i.e. `žlutý` => `zluty`
        value = unicodedata.normalize('NFKD', value)
        value = value.encode('ascii', 'ignore').decode('ascii')
    value = re.sub(r'[^\w\s-]', '', value).strip().lower()
    return re.sub(rf'[{separator}\s]+', separator, value)


if __name__ == '__main__':
    action = sys.argv[1] if len(sys.argv) > 1 else None
    if action == 'build':
        asyncio.run(build_search_db())
    elif action == 'search':
        if len(sys.argv) == 3:
            q = sys.argv[2]
        else:
            q = 'How do I configure logfire to work with FastAPI?'
        asyncio.run(run_agent(q))
    elif action == 'web':
        uvicorn.run(app, host='127.0.0.1', port=7932)
    else:
        print(
            'uv run --extra examples -m pydantic_ai_examples.rag_surrealdb build|search|web',
            file=sys.stderr,
        )
        sys.exit(1)
```

---

Source: https://surrealdb.com/docs/build/integrations/ai-frameworks/smolagents

# SmolAgents

A complete walkthrough for building a code-generating AI agent that recommends grocery items by querying SurrealDB's HNSW vector index.

In this guide you will build an agent that, given a natural-language shopping request, finds the most relevant grocery items in your database and returns them in a single reply.

## Install the dependencies

```bash
pip install smolagents surrealdb fastembed datasets
```

| Library        | Purpose                                |
| -------------- | -------------------------------------- |
| **surrealdb**  | Async Python SDK for SurrealDB         |
| **smolagents** | Code-generating agent framework        |
| **fastembed**  | Local Jina v2 embedding model (768-D)  |
| **datasets**   | Pulls the public *GroceryList* dataset |

## Create a SurrealDB "grocery search" tool

```python
from fastembed import TextEmbedding
from surrealdb import AsyncSurreal
from smolagents import Tool
from datasets import load_dataset
import asyncio, os
from typing import List, Dict, Any

class GroceryQueryTool(Tool):
    name = "surreal_grocery_search"
    description = "Semantic search over grocery items stored in SurrealDB."
    inputs = {
        "query": {
            "type": "string",
            "description": "A natural-language description of a grocery need.",
        }
    }
    output_type = "string"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # Connection details
        self.uri = "ws://localhost:8000/rpc"
        self.ns = "demo"
        self.dbname = "demo"
        self.table = "groceries"
        self.user = os.getenv("SURREAL_USER", "root")
        self.pw = os.getenv("SURREAL_PASS", "secret")

        self.emb = TextEmbedding(model_name="jinaai/jina-embeddings-v2-base-en")

        # one-time async bootstrap
        asyncio.run(self._setup())

    async def _setup(self):
        """Connect, create schema, and ingest groceries if empty."""
        async with AsyncSurreal(self.uri) as db:
            await db.signin({"username": self.user, "password": self.pw})
            await db.use(self.ns, self.dbname)

            schema = """
            DEFINE TABLE $tb SCHEMALESS PERMISSIONS NONE;
            DEFINE FIELD item_name   ON $tb TYPE string;
            DEFINE FIELD category    ON $tb TYPE string;
            DEFINE FIELD description ON $tb TYPE string;
            DEFINE FIELD embedding   ON $tb TYPE array;

            DEFINE INDEX idx_hnsw ON $tb
              FIELDS embedding
              HNSW DIMENSION 768
              DIST   COSINE;
            """
            await db.query(schema, {"tb": self.table})

            # skip ingest if we already have rows
            existing = await db.query(f"SELECT count() FROM {self.table};")
            if existing[0]["result"][0]["count"] > 0:
                return

            print("🛒 Ingesting GroceryList dataset …")
            ds = load_dataset("AmirMohseni/GroceryList")["train"]  # 225 rows

            BATCH = 64
            items = ds["item"]
            cats = ds["category"]
            descs = [f"{it.capitalize()} is in the {cat} aisle." for it, cat in zip(items, cats)]

            for i in range(0, len(descs), BATCH):
                vecs = self.emb.query_embed(descs[i:i+BATCH])
                rows = [
                    {
                        "id": f"{self.table}:{i+j}",
                        "item_name": items[i+j],
                        "category": cats[i+j],
                        "description": descs[i+j],
                        "embedding": list(vec),
                    }
                    for j, vec in enumerate(vecs)
                ]
                await db.create(self.table, rows)

    async def _lookup(self, query_vec: List[float]) -> List[Dict[str, Any]]:
        """Perform vector search with proper connection management."""
        async with AsyncSurreal(self.uri) as db:
            await db.signin({"username": self.user, "password": self.pw})
            await db.use(self.ns, self.dbname)

            surql = """
            LET $q := $vec;
            SELECT item_name, category, description,
                   vector::distance::knn() AS dist
            FROM $tb
            WHERE embedding <|$k,$ef|> $q      -- top-k, efSearch
            ORDER BY dist;
            """
            result = await db.query(
                surql,
                {
                    "vec": query_vec,
                    "tb": self.table,
                    "k": 5,
                    "ef": 64
                }
            )
            return result[0]["result"]

    def forward(self, query: str) -> str:
        """Return the five closest grocery items."""
        q_vec = next(self.emb.query_embed(query))
        hits = asyncio.run(self._lookup(q_vec))

        return "Retrieved items:\n" + "".join(
            f"== {hit['item_name'].title()} ==\n"
            f"Category: {hit['category']}\n"
            f"{hit['description']}\n\n"
            for hit in hits
        )
```

---

Source: https://surrealdb.com/docs/build/integrations/authentication/better-auth/getting-started

# Getting started

Install the @surrealdb/better-auth adapter, connect to SurrealDB, configure the adapter, and generate your Better Auth schema.

This guide walks through installing the `@surrealdb/better-auth` adapter, connecting it to SurrealDB, and generating the schema Better Auth needs.

## Installation

Install the adapter alongside `better-auth` and the [SurrealDB JavaScript SDK](/docs/reference/javascript.md):

**Bun**

```bash
bun add @surrealdb/better-auth better-auth surrealdb
```

**npm**

```bash
npm install @surrealdb/better-auth better-auth surrealdb
```

**pnpm**

```bash
pnpm add @surrealdb/better-auth better-auth surrealdb
```

## Quick start

Connect a `Surreal` client, then pass it to `surrealAdapter` as the `database` option in your Better Auth configuration. Better Auth manages all table operations through the adapter.

```typescript
import { betterAuth } from 'better-auth';
import { Surreal } from 'surrealdb';
import { surrealAdapter } from '@surrealdb/better-auth';

const db = new Surreal();
await db.connect('ws://localhost:8000/rpc');
await db.use({ namespace: 'namespace', database: 'database' });

export const auth = betterAuth({
    database: surrealAdapter({ db }),
    emailAndPassword: { enabled: true },
});
```

## Connecting to SurrealDB

The adapter accepts any connected `Surreal` instance. Connect it before passing it to `surrealAdapter`. Use WebSocket for long-running servers and HTTP for stateless environments such as serverless functions.

```typescript
import { Surreal } from 'surrealdb';

const db = new Surreal();

// WebSocket (recommended for persistent servers)
await db.connect('ws://localhost:8000/rpc', {
    namespace: 'myapp',
    database: 'production',
    authentication: {
        username: 'root',
        password: 'secret',
    },
});

// HTTP (for stateless environments)
await db.connect('http://localhost:8000', {
    namespace: 'myapp',
    database: 'production',
});
```

For a full reference of connection options, see [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) in the JavaScript SDK documentation.

## Configuration options

`surrealAdapter` accepts the following options:

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `db` | `Surreal` | required | A connected SurrealDB client instance. |
| `usePlural` | `boolean` | `false` | Use plural table names (`users` instead of `user`). |
| `schemaMode` | `'schemafull' \| 'schemaless'` | `'schemafull'` | Table mode used by the generated schema. `schemaless` keeps known fields typed and indexed while still accepting writes to undeclared fields. |

```typescript
surrealAdapter({
    db,
    usePlural: true, // use plural table names
});
```

## Schema generation

The adapter includes a `createSchema` implementation that generates SurrealQL DDL statements for all Better Auth tables. Use the Better Auth CLI to produce a `schema.surql` file:

```bash
bunx @better-auth/cli generate --output schema.surql
```

Then apply it to your SurrealDB instance:

```bash
surreal db import --endpoint http://localhost:8000 \
    --ns myapp --db production \
    --user root --pass secret \
    schema.surql
```

The generated schema uses `SCHEMAFULL` tables by default. Each field is typed from the Better Auth schema:

- Required fields take a concrete type (`string`, `datetime`, `bool`, and so on).
- Optional fields use `option<T | null>`, so they accept a typed value, a stored `NULL`, or a missing value.
- Object fields use `TYPE object FLEXIBLE` so nested objects can hold arbitrary keys.
- `DEFINE INDEX` statements are emitted for unique fields and for fields Better Auth marks as indexed.

Every statement uses `IF NOT EXISTS`, so the file is safe to reapply.

> [!NOTE]
> If your app adds many dynamic plugin fields and you would rather not regenerate the schema each time, set `schemaMode: 'schemaless'`. Known fields stay typed and indexed, and writes to undeclared fields are accepted.

## Table names

By default the adapter uses singular table names: `user`, `session`, `verification`, and `account`. Set `usePlural: true` to use plural names instead.

Better Auth also lets you override model names per-table via the `modelName` option in your configuration, and the adapter respects those overrides automatically.

## Next steps

- [Overview](/docs/build/integrations/authentication/better-auth/overview.md): adapter capabilities and prerequisites
- [Plugins](/docs/build/integrations/authentication/better-auth/plugins.md): use any Better Auth plugin and the SurrealQL helper functions generated for the organisation plugin.
- [Transactions & limitations](/docs/build/integrations/authentication/better-auth/transactions-and-limitations.md): how transactions behave and what to be aware of.

---

Source: https://surrealdb.com/docs/build/integrations/authentication/better-auth/overview

# Better Auth

Use SurrealDB as the database behind Better Auth with the @surrealdb/better-auth adapter, including schema generation, transactions, and support for all Better Auth plugins.

[Better Auth](https://better-auth.com) is a comprehensive, framework-agnostic authentication and authorisation library for TypeScript. The `@surrealdb/better-auth` adapter lets you use SurrealDB as the database behind Better Auth: you pass it a single connected [`Surreal`](/docs/reference/javascript.md) client and Better Auth manages all of its table operations through the adapter.

The adapter supports schema generation, transactions, the full set of Better Auth WHERE operators, and every Better Auth plugin.

> [!NOTE]
> This section covers the **Better Auth** integration only. For SurrealDB's own sign-in, tokens, and permissions model, see [Security](/docs/learn/security.md).

> [!NOTE]
> The `@surrealdb/better-auth` adapter works with SurrealDB versions `v3.1.0` and later.

## Prerequisites

- SurrealDB 3.1 or later
- Better Auth 1.6.x
- Node.js 18+ or Bun 1.x

## Feature support

The adapter advertises the following capabilities to Better Auth:

| Capability | Supported |
|------------|-----------|
| JSON / object fields | Yes |
| Dates | Yes |
| Booleans | Yes |
| Arrays | Yes |

## Identifiers

By default, Better Auth generates string identifiers in JavaScript. The adapter stores them as SurrealDB [record IDs](/docs/reference/query-language/language-primitives/data-types/record-ids.md) and deserialises them back to strings when reading, so a record round-trips to your application as the plain string `id`.

UUIDs are also supported. Enable them in your Better Auth configuration:

```typescript
betterAuth({
    database: surrealAdapter({ db }),
    advanced: { database: { generateId: 'uuid' } },
});
```

Better Auth generates the UUID in JavaScript and passes it to the adapter, which stores and reads it transparently (it round-trips as the plain UUID string). SurrealDB does not mint the UUID natively, which is why the adapter reports `supportsUUIDs: false` to Better Auth: that flag means "the database generates UUIDs itself", not "UUIDs are unsupported".

Numeric (auto-increment) identifiers are not supported.

## In this section

- [Getting started](/docs/build/integrations/authentication/better-auth/getting-started.md) - Install the adapter, connect to SurrealDB, configure options, and generate your schema.

- [Plugins](/docs/build/integrations/authentication/better-auth/plugins.md) - Use any Better Auth plugin and call the SurrealQL helper functions generated for the organisation plugin.

- [Transactions & limitations](/docs/build/integrations/authentication/better-auth/transactions-and-limitations.md) - How transactions behave and the current limitations to be aware of.

## Sources

- [GitHub repository](https://github.com/surrealdb/better-auth)
- [npm package](https://www.npmjs.com/package/@surrealdb/better-auth)
- [Better Auth documentation](https://better-auth.com)

---

Source: https://surrealdb.com/docs/build/integrations/authentication/better-auth/plugins

# Plugins

Use any Better Auth plugin with the SurrealDB adapter and call the SurrealQL helper functions generated for the organisation plugin.

The `@surrealdb/better-auth` adapter works with all Better Auth plugins. Pass them in the `plugins` array of your Better Auth configuration:

```typescript
import { betterAuth } from 'better-auth';
import { organization } from 'better-auth/plugins/organization';
import { twoFactor } from 'better-auth/plugins/two-factor';
import { admin } from 'better-auth/plugins/admin';
import { Surreal } from 'surrealdb';
import { surrealAdapter } from '@surrealdb/better-auth';

const db = new Surreal();
await db.connect('ws://localhost:8000/rpc');
await db.use({ namespace: 'myapp', database: 'production' });

export const auth = betterAuth({
    database: surrealAdapter({ db }),
    plugins: [
        organization(),
        twoFactor(),
        admin(),
    ],
});
```

A wider example combining several plugins:

```typescript
import { betterAuth } from 'better-auth';
import { admin } from 'better-auth/plugins/admin';
import { anonymous } from 'better-auth/plugins/anonymous';
import { bearer } from 'better-auth/plugins/bearer';
import { magicLink } from 'better-auth/plugins/magic-link';
import { multiSession } from 'better-auth/plugins/multi-session';
import { organization } from 'better-auth/plugins/organization';
import { twoFactor } from 'better-auth/plugins/two-factor';
import { username } from 'better-auth/plugins/username';
import { Surreal } from 'surrealdb';
import { surrealAdapter } from '@surrealdb/better-auth';

const db = new Surreal();
await db.connect('ws://localhost:8000/rpc');
await db.use({ namespace: 'myapp', database: 'production' });

export const auth = betterAuth({
    secret: process.env.BETTER_AUTH_SECRET!,
    baseURL: process.env.BETTER_AUTH_URL ?? 'http://localhost:3000',
    emailAndPassword: { enabled: true },
    database: surrealAdapter({ db }),
    plugins: [
        organization({
            teams: { enabled: true },
            dynamicAccessControl: { enabled: true },
        }),
        twoFactor(),
        admin(),
        bearer(),
        username(),
        magicLink({
            sendMagicLink: async ({ email, url }) => {
                // send an email containing the magic link url
            },
        }),
        anonymous(),
        multiSession(),
    ],
});
```

> [!IMPORTANT]
> Each plugin may add new tables. Re-run [schema generation](/docs/build/integrations/authentication/better-auth/getting-started.md#schema-generation) after adding plugins to produce the updated DDL, or use `schemaMode: 'schemaless'` to accept the new fields without regenerating.

## SurrealDB helper functions

When you use the `organization` plugin (with or without the `teams` option), schema generation also emits a set of `fn::*` SurrealQL functions you can call directly in your own queries, [rules, and permissions](/docs/reference/query-language/statements/define/table.md#defining-permissions). These functions are defined with `IF NOT EXISTS`, so re-running the generated schema is safe.

### `fn::auth::organization::*`

These functions are emitted when the `organization` plugin is active.

| Function | Signature | Returns | Description |
|---|---|---|---|
| `fn::auth::organization::member_of` | `(userId: string, organizationId: string)` | `bool` | True if the user is a member of the organisation. |
| `fn::auth::organization::get_role` | `(userId: string, organizationId: string)` | `option<string>` | The member's role (`"owner"`, `"admin"`, `"member"`), or `NONE` if not a member. |
| `fn::auth::organization::has_role` | `(userId: string, organizationId: string, minRole: string)` | `bool` | True if the user's role is equal to or senior to `minRole` (owner > admin > member). |
| `fn::auth::organization::members` | `(organizationId: string)` | `array` | All `members` records for the organisation. |
| `fn::auth::organization::teams` | `(organizationId: string)` | `array` | All `teams` records for the organisation (requires `teams: { enabled: true }`). |
| `fn::auth::organization::has_permission` | `(userId: string, organizationId: string, resource: string, action: string)` | `bool` | True if the user's role has a custom permission for the given resource and action (requires `dynamicAccessControl`). |

```surql
-- Check if a user belongs to an organisation
IF fn::auth::organization::member_of($userId, $organizationId) {
    -- allow access
};

-- Require at least admin-level access
IF fn::auth::organization::has_role($userId, $organizationId, "admin") {
    -- perform a privileged operation
};

-- Get a user's role string
LET $role = fn::auth::organization::get_role($userId, $organizationId);

-- List all members
LET $members = fn::auth::organization::members($organizationId);
```

### `fn::auth::team::*`

These functions are emitted when `teams: { enabled: true }` is set on the `organization` plugin.

| Function | Signature | Returns | Description |
|---|---|---|---|
| `fn::auth::team::member_of` | `(userId: string, teamId: string)` | `bool` | True if the user is a member of the team. |
| `fn::auth::team::members` | `(teamId: string)` | `array` | All `teamMembers` records for the team. |

```surql
-- Check team membership
IF fn::auth::team::member_of($userId, $teamId) {
    -- allow team-scoped access
};

-- List all team members
LET $members = fn::auth::team::members($teamId);
```

---

Source: https://surrealdb.com/docs/build/integrations/authentication/better-auth/transactions-and-limitations

# Transactions & limitations

How the @surrealdb/better-auth adapter handles transactions, and the current limitations to be aware of.

The `@surrealdb/better-auth` adapter uses transactions internally, and exposes them for operations you want to group yourself. This page covers that behaviour and the limitations that come with it.

## Transactions

Transaction support is built in. Better Auth uses transactions internally for operations that need atomicity (for example, creating a session alongside a new user record), so most applications never call the transaction API directly.

If you need manual transaction access, call `transaction` on the adapter. Operations run inside the callback are committed together when it resolves, or discarded if it throws.

```typescript
const adapter = surrealAdapter({ db })(options);

await adapter.transaction(async (trx) => {
    await trx.create({ model: 'user', data: { /* ... */ } });
    await trx.create({ model: 'session', data: { /* ... */ } });
});
```

Transactions are backed by [`SurrealDB.beginTransaction()`](/docs/reference/query-language/statements/begin.md). If any operation inside the callback throws, the transaction is cancelled and changes are discarded.

## Limitations

Keep the following in mind when adopting the adapter.

### Version coupling

The adapter targets **Better Auth 1.6.x** and **SurrealDB 3.1+**. Other major versions of either dependency are not supported.

### Numeric identifiers are not supported

By default Better Auth uses string identifiers, and [UUIDs](/docs/build/integrations/authentication/better-auth/overview.md#identifiers) can be enabled in your Better Auth configuration. Numeric (auto-increment) identifier strategies are **not** supported, because SurrealDB does not generate them natively and the adapter has no JavaScript fallback for them.

### `SCHEMAFULL` schemas require regeneration

With the default `schemaMode: 'schemafull'`, writes to fields that are not in the generated schema are rejected. Whenever you add a plugin or otherwise introduce new fields, [regenerate and reapply the schema](/docs/build/integrations/authentication/better-auth/getting-started.md#schema-generation). If you would rather not regenerate each time, set `schemaMode: 'schemaless'`; known fields stay typed and indexed, and writes to undeclared fields are accepted.

### Transaction rollback is best-effort

SurrealDB's `cancel()` is not honoured in every setup (for example, the in-memory engine). To keep rollback reliable, the adapter compensates by deleting records it created inside a failed transaction. This covers records created during the transaction; it does not restore prior values for records that were updated or deleted within it.

### Value deserialisation

When reading records, the adapter converts SurrealDB types back to JavaScript values: a `datetime` becomes a JavaScript `Date`, and a `RecordId` becomes its string `id`. Bear this in mind if you query the underlying tables directly rather than through Better Auth.

---

Source: https://surrealdb.com/docs/build/integrations/backend-platforms/encore

# Encore

Build and verify an AI agent memory service with Encore APIs, Pub/Sub and scheduled jobs around SurrealDB vector and graph queries.

[Encore](https://encore.dev/) is a platform that lets engineers and their agents build and verify complete backends using type-safe APIs and infrastructure primitives defined in application code, backed by local infrastructure during development and managed services in their AWS or GCP account when deployed. In this guide, those primitives define and connect the APIs, Pub/Sub ingestion, and scheduled retention workflow around SurrealDB's vector and graph capabilities.

The example application stores durable memory for AI agents. Each memory contains text and a vector embedding, with graph relationships connecting it to the entities it mentions. Memories are written asynchronously through Pub/Sub, recalled through vector similarity or graph traversal, and removed by a scheduled retention job.

## Run the example

You need the [Encore CLI](https://encore.dev/docs/install), Docker and Node.js 22 or later.

> [!TIP]
> Using a coding agent? Copy the prompt below to skip the manual setup.

```text
Set up and run the Encore and SurrealDB agent memory example. First check whether the Encore CLI is installed and, if needed, follow https://encore.dev/docs/install. Then run `encore app create agent-memory --example=ts/surrealdb-agent-memory`, follow the example README to start SurrealDB and the Encore application locally, verify that storing and recalling a memory works, and run `encore check` and `encore test`.
```

Start a local SurrealDB 3.3 instance:

```bash
docker run --rm --name encore-surrealdb-memory \
  -p 8000:8000 \
  surrealdb/surrealdb:v3.3.0 \
  start --user root --pass secret memory
```

This starts a temporary SurrealDB instance at `http://localhost:8000`. Its data is discarded when the container stops.

In another terminal, create and run the example application:

```bash
encore app create agent-memory --example=ts/surrealdb-agent-memory
cd agent-memory
npm install
encore run
```

Encore starts the APIs and local Pub/Sub and cron infrastructure. The application connects to the SurrealDB container as `root` with the password `secret`.

## How the integration works

The `remember` API accepts memory text, its embedding, extracted entity names and an optional expiry time. It publishes the request to an at-least-once Encore Pub/Sub topic, whose subscriber writes the memory and its `mentions` relationships to SurrealDB.

The API, topic and subscriber are defined together in the application code, allowing Encore to connect the ingestion flow locally and provision it using the corresponding managed services when deployed:

```typescript
import { randomUUID } from "node:crypto";
import { api } from "encore.dev/api";
import { CronJob } from "encore.dev/cron";
import { Subscription, Topic } from "encore.dev/pubsub";

const memoriesToStore = new Topic<MemoryEvent>("memories-to-store", {
    deliveryGuarantee: "at-least-once",
});

export const remember = api(
    { expose: true, method: "POST", path: "/agents/:agentID/memories" },
    async (params: RememberParams): Promise<{ memoryID: string; status: string }> => {
        const memoryID = `m_${randomUUID().replaceAll("-", "")}`;
        await memoriesToStore.publish({
            ...params,
            memoryID,
            entities: normalizeEntities(params.entities ?? []),
        });
        return { memoryID, status: "queued" };
    },
);

new Subscription(memoriesToStore, "store-memory-in-surrealdb", {
    handler: async (event) => storeMemory(await getSurreal(), event),
});
```

Stable record and relationship IDs make this write safe to repeat if Pub/Sub retries an event. Recall can then combine an HNSW vector search with graph traversal in one SurrealQL query:

```surql
SELECT id, text, created_at, expires_at,
       vector::distance::knn() AS distance,
       ->mentions->entity.name AS entities
FROM memory
WHERE embedding <|10, 100|> $embedding
  AND agent_id = $agent_id
  AND (expires_at = NONE OR expires_at > time::now())
ORDER BY distance
LIMIT $limit;
```

An Encore cron job calls the retention API every 24 hours to delete expired memories:

```typescript
export const pruneExpired = api(
    { expose: true, method: "POST", path: "/memories/prune" },
    async (): Promise<{ deleted: number }> => ({
        deleted: await pruneExpiredMemories(await getSurreal()),
    }),
);

new CronJob("prune-agent-memory", {
    title: "Remove expired agent memories",
    every: "24h",
    endpoint: pruneExpired,
});
```

The [complete example](https://github.com/encoredev/examples/tree/main/ts/surrealdb-agent-memory) contains the remaining API definitions, retry-safe transaction, schema and tests.

## Store and recall a memory

Store a memory for an agent:

```bash
curl -X POST http://localhost:4000/agents/research-agent/memories \
  -H 'Content-Type: application/json' \
  -d '{
    "text": "SurrealDB stores vector and graph context for the agent.",
    "embedding": [0.94, 0.06, 0, 0, 0, 0, 0, 0],
    "entities": ["SurrealDB", "Encore"]
  }'
```

The API returns a generated `memoryID` and the status `queued`. Once the subscriber has written the event, recall the nearest memories:

```bash
curl -X POST http://localhost:4000/agents/research-agent/recall \
  -H 'Content-Type: application/json' \
  -d '{
    "embedding": [0.95, 0.05, 0, 0, 0, 0, 0, 0],
    "limit": 5
  }'
```

The example uses eight-dimensional vectors to keep requests readable. Change both `EMBEDDING_DIMENSIONS` and the HNSW index dimension before connecting a production embedding model.

Run the checks and tests with:

```bash
encore check
encore test
```

## Connect to SurrealDB Cloud

Set the SurrealDB endpoint and database token as Encore secrets before deploying the application:

```bash
encore secret set --type dev,prod SurrealDBURL
encore secret set --type dev,prod SurrealDBToken
```

`SurrealDBToken` must be a database token with access to the `encore` namespace and `agent_memory` database. A personal access token beginning with `sdbp_` authenticates the SurrealDB Cloud control plane and cannot be used as the application's database token.

## Resources

- [Complete Encore and SurrealDB example](https://github.com/encoredev/examples/tree/main/ts/surrealdb-agent-memory)
- [SurrealDB JavaScript SDK](/docs/reference/javascript.md)
- [Vector indexes](/docs/learn/data-models/vector-search/vector-indexes.md)
- [Encore infrastructure primitives](https://encore.dev/docs/ts/primitives)

---

Source: https://surrealdb.com/docs/build/integrations/data-management/airbyte

# Airbyte

The Airbyte connector allows you to sync data to SurrealDB from hundreds of sources.

This guide helps you configure SurrealDB as a destination in [Airbyte](https://airbyte.com) using the [official connector](https://github.com/surrealdb/airbyte-connector).

## Prerequisites
To connect SurrealDB to Airbyte, you need the following:

- An Airbyte deployment with access to the SurrealDB destination connector.
- SurrealDB `v2.2.0` or later.
- A SurrealDB instance (self-hosted or [SurrealDB Cloud](https://studio.surrealdb.com/cloud)) reachable by Airbyte.
- Ensure your SurrealDB database can be accessed by Airbyte. If your database is within a VPC, you may need to allow access from the IP you're using to expose Airbyte.
- A token or user credentials with [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md), [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md), [`UPSERT`](/docs/reference/query-language/statements/upsert.md), [`SELECT`](/docs/reference/query-language/statements/select.md), and [`REMOVE`](/docs/reference/query-language/statements/remove.md) permissions.

## Setup instructions

Follow these steps to configure SurrealDB as a destination. You can use either a self-hosted instance or SurrealDB Cloud .

Before proceeding, ensure you have the endpoint URL and credentials for your SurrealDB instance and that Airbyte can reach it over the network.

### Option 1: Self-hosted SurrealDB

1. Ensure your SurrealDB instance is reachable from Airbyte. This might involve opening network access or configuring an SSH tunnel.
2. Create a dedicated user or token in SurrealDB, for example:

```surql
DEFINE USER airbyte ON ROOT PASSWORD "YourPassword" ROLES OWNER;
```

Use the generated credentials when setting up the destination.

### Option 2: SurrealDB Cloud

1. Visit the [Instances page](https://studio.surrealdb.com/cloud/instances) and select your instance.
2. Click **Connect with Surreal CLI** to obtain a connection command containing the `--endpoint` and authentication details.
3. Verify you can connect using `surreal db sql` with those parameters.

## Finish Airbyte configuration

1. Open the Airbyte dashboard and add a new **Destination**.
2. Select **SurrealDB** from the list of destination types.
3. Provide the host, port, namespace, database, and either the username/password or token you created earlier.
4. Click **Set up destination** and run the connection test.

Upon a successful test, you can start syncing data from your sources into SurrealDB tables. Each stream will be output into its own table in SurrealDB. Each table will contain 3 fields:

- `_airbyte_raw_id`: a uuid assigned by Airbyte to each event that is processed. The field type in SurrealDB is string. The connector uses this as the ID of each record in the destination SurrealDB table.
- `_airbyte_extracted_at`: a timestamp representing when the event was pulled from the data source. The field type in SurrealDB is datetime.
- `_airbyte_data`: a JSON blob holding the event data. The field type in SurrealDB is object.

## Known limitations
SurrealDB destination forces all identifier (table, schema and columns) names to be lowercase.

## Related links

- [Airbyte destination documentation](https://github.com/surrealdb/airbyte-connector)
- [Connect to airbyte via a dev container](https://github.com/surrealdb/airbyte-connector/blob/main/devcontainer.md)

---

Source: https://surrealdb.com/docs/build/integrations/data-management/fivetran

# Fivetran

The Fivetran integration for SurrealDB allows you to sync data from Fivetran to SurrealDB.

This guide will help you connect SurrealDB to Fivetran. The Fivetran integration is available through their [partner-built program](https://fivetran.com/docs/partner-built-program). For support with self-hosted SurrealDB destinations, please contact [SurrealDB Support](/contact). For SurrealDB Cloud related questions, reach out to [SurrealDB Cloud Support](/docs/manage/organisations/support.md).

## Prerequisites
To connect SurrealDB to Fivetran, you need the following:

- A Fivetran role with the [Create Destinations or Manage Destinations](https://fivetran.com/docs/using-fivetran/fivetran-dashboard/account-settings/role-based-access-control#rbacpermissions) permissions.
- A SurrealDB token.
- A SurrealDB instance (self-hosted or Cloud) that is accessible by Fivetran.

## Setup instructions

Follow these steps to set up SurrealDB as a destination in Fivetran. You have two options: self-hosted SurrealDB or SurrealDB Cloud .

Before proceeding with either option, ensure you have:

- Access to your SurrealDB instance with appropriate permissions
- The necessary connection details (endpoint URL, credentials)
- Network connectivity between Fivetran and your SurrealDB instance

### Option 1: Self-hosted SurrealDB

1. For self-hosted SurrealDB, ensure your SurrealDB instance is accessible by Fivetran according to your Fivetran deployment:
   - For [Fivetran SaaS Deployment](https://fivetran.com/docs/deployment-models/saas-deployment), ensure your SurrealDB is accessible via Internet.
   - For [Fivetran Hybrid Deployment](https://fivetran.com/docs/deployment-models/hybrid-deployment), ensure your SurrealDB is accessible by the Fivetran Hybrid Deployment Agent.
   - For [Fivetran Self-Hosted Deployment](https://fivetran.com/docs/deployment-models/self-hosted-deployment), ensure your SurrealDB is accessible by the Fivetran HVR Agent.
2. Set up the token and use it following [SurrealDB's Authentication documentation](/docs/learn/security/authentication/users.md#token).

### Option 2: SurrealDB Cloud

1. Ensure your SurrealDB instance is up and running and accessible via Internet.
2. Browse the [Instances page](https://studio.surrealdb.com/cloud/instances) and select your chosen instance.
3. Click **Connect with Surreal CLI** and locate the `surreal db sql --endpoint wss://YOUR_INSTANCE_HOSTNAME --token YOUR_TOKEN` command.
4. Run the command, and set up your own [`ACCESS`](/docs/reference/query-language/statements/define/access.md) or [`USER`](/docs/reference/query-language/statements/define/user.md). The example below works for testing purposes:

    ```surql
    USE NS your_ns;
    USE DB your_db;
    DEFINE USER your_user ON DATABASE PASSWORD "YourPassword" ROLES OWNER;
    ```

5. Ensure the user/pass is working by running:

    ```bash
    surreal db sql --endpoint wss://YOUR_INSTANCE_HOSTNAME --user your_user --pass YourPassword --ns your_ns --db your_db
    ```

6. Make a note of the `endpoint`, `user`, and `pass` parameters. You will need them to configure Fivetran.

7. (Optional) If you prefer using `token`, we recommend `DEFINE ACCESS ... TYPE JWT`. Refer to the [`DEFINE ACCESS > JWT` documentation](/docs/reference/query-language/statements/define/access/jwt.md) to set up JWT access.

    - Verify if the token is working before proceeding to the next section, by running:
    ```bash
    surreal db sql --endpoint wss://YOUR_INSTANCE_HOSTNAME --token your_token --ns your_ns --db your_db
    ```

## Finish Fivetran configuration

1. Log in to your [Fivetran account](https://fivetran.com/login).
2. Go to the **Destinations** page and click **Add destination**.
3. Enter a **Destination name** of your choice.
4. Click **Add**.
5. Select **SurrealDB** as the destination type.
6. Enter the `url`, `user` and `pass` (or `token`) you verified in the previous step.

> [!NOTE]
> The `url` setting corresponds to the `endpoint` parameter you verified in the previous step.

7. Click **Save & Test**.

Fivetran [tests and validates](https://fivetran.com/docs/destinations/surrealdb#setup-tests) the SurrealDB connection. Upon successfully completing the setup tests, you can sync your data using Fivetran connectors to the SurrealDB destination.

In addition, Fivetran automatically configures a [Fivetran Platform Connector](https://fivetran.com/docs/logs/fivetran-platform) to transfer the connector logs and account metadata to a schema in this destination. The Fivetran Platform Connector enables you to monitor your connectors, track your usage, and audit changes. The connector sends all these details at the destination level.

> [!IMPORTANT]
> If you are an Account Administrator, you can manually add the Fivetran Platform Connector on an account level so that it syncs all the metadata and logs for all the destinations in your account to a single destination. If an account-level Fivetran Platform Connector is already configured in a destination in your Fivetran account, then we don't add destination-level Fivetran Platform Connectors to the new destinations you create.

## Setup tests

Fivetran performs the following SurrealDB connection tests:

- The Database Connection test checks if we can connect to your SurrealDB database using the provided URL and token.

The test should complete in a few seconds if your Fivetran deployment can access the target SurrealDB instance.

## Related articles

- [Destination Overview](https://fivetran.com/docs/destinations/surrealdb)

- [API Destination Configuration](https://fivetran.com/docs/rest-api/api-reference/destinations/create-destination?service=surrealdb_destination)

- [Documentation Home](/docs)

---

Source: https://surrealdb.com/docs/build/integrations/data-management/n8n

# n8n

The official n8n node for SurrealDB. It provides both action and tool nodes to interact with a SurrealDB database, allowing you to create, read, update, and delete records, as well as execute custom SurrealQL queries.

This guide shows how to integrate SurrealDB with [n8n](https://n8n.io/), a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.

The SurrealDB node is the official n8n node for SurrealDB. It provides both action and tool nodes to interact with a SurrealDB database, allowing you to create, read, update, and delete records, as well as execute custom SurrealQL queries. It is available in the [n8n Community Nodes](https://docs.n8n.io/integrations/community-nodes/) repository.

> [!IMPORTANT]
> As with all community nodes, this node works only with self-hosted n8n instances, not with n8n Cloud. This node has been tested with SurrealDB `v2.x`

## Features

- **Dual Node Types**: Functions as both an action node and a tool node for AI workflows
- **Complete CRUD Operations**: Create, read, update, and delete SurrealDB records
- **Custom Queries**: Execute any SurrealQL query with full parameter support
- **Enhanced query builder**: Visual interface for building `SELECT` queries with `WHERE`, `ORDER BY`, `GROUP BY`, and other clauses
- **Table Operations**: List fields and explore table structure
- **Relationship Support**: Query and manage record relationships
- **Native Data Format**: Works with SurrealDB's native data formats
- **Connection Pooling**: Configurable connection pooling for improved performance and resource management
- **Enhanced error handling**: Comprehensive error classification, automatic retry logic, and connection recovery
    - **Intelligent Recovery**: Different error handling strategies for different operation types
    - **Detailed Error Reporting**: Rich error context with categorization and severity levels
- **Pool Monitoring**: Built-in pool statistics and performance monitoring

## Prerequisites

1. You need a self-hosted n8n instance (`v0.214.0` or later recommended).
2. You need access to a SurrealDB instance (`2.0.0` or later recommended).

## Installation steps

1. Open your n8n instance
2. Go to **Settings** > **Community Nodes**
3. Click **Install**
4. Enter `@surrealdb/n8n-nodes-surrealdb` and click **Install**
5. Restart your n8n instance if prompted

> [!NOTE]
> To use this node as a tool in AI workflows, you must set the environment variable `N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true`.

## Configuration

In order to use SurrealDB in n8n, you need to configure the SurrealDB node.

### Credentials

To use the SurrealDB node, you need to create credentials with the following properties:

<table>
  <thead>
    <tr>
      <th>Property</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Connection String</strong></td>
      <td>The connection string to your SurrealDB instance (must start with <code>http://</code> or <code>https://</code>). WebSocket connections (<code>ws://</code> or <code>wss://</code>) are not supported.</td>
    </tr>
    <tr>
      <td><strong>Authentication</strong></td>
      <td>Choose the authentication scope:</td>
    </tr>
    <tr>
      <td><strong>Root</strong></td>
      <td>Full access to all namespaces and databases</td>
    </tr>
    <tr>
      <td><strong>Namespace</strong></td>
      <td>Access limited to a specific namespace</td>
    </tr>
    <tr>
      <td><strong>Database</strong></td>
      <td>Access limited to a specific database within a namespace</td>
    </tr>
    <tr>
      <td><strong>Username</strong></td>
      <td>Username for authentication</td>
    </tr>
    <tr>
      <td><strong>Password</strong></td>
      <td>Password for authentication</td>
    </tr>
    <tr>
      <td><strong>Namespace</strong></td>
      <td>Target namespace (required for Namespace and Database authentication)</td>
    </tr>
    <tr>
      <td><strong>Database</strong></td>
      <td>Target database (required for Database authentication)</td>
    </tr>
  </tbody>
</table>

The authentication type you choose affects how namespace and database information is handled. Depending on the authentication type, you will need to provide different information. See the table below for more details.

<table>
  <thead>
    <tr>
      <th>Authentication Type</th>
      <th>Access Scope</th>
      <th>Required Fields</th>
      <th>Optional Fields</th>
      <th>Override Capability</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Root Authentication</strong></td>
      <td>All namespaces and databases</td>
      <td>None</td>
      <td>Namespace, Database</td>
      <td>Can override namespace/database at node level</td>
    </tr>
    <tr>
      <td><strong>Namespace Authentication</strong></td>
      <td>All databases within a specific namespace</td>
      <td>Namespace</td>
      <td>Database</td>
      <td>Can override database at node level</td>
    </tr>
    <tr>
      <td><strong>Database Authentication</strong></td>
      <td>Specific database within a specific namespace</td>
      <td>Namespace, Database</td>
      <td>None</td>
      <td>Can override both at node level for specific operations</td>
    </tr>
  </tbody>
</table>

### Node-level namespace and database overrides

For most operations, you can override the namespace and database settings from your credentials:

1. In the node configuration, expand the **Options** section
2. Enter values in the **Namespace** and/or **Database** fields
3. These values will take precedence over the credential settings for that specific operation
4. You will be required to provide a namespace when using Namespace authentication
5. You will be required to provide both a namespace and database when using Database authentication

## Operations

The SurrealDB node provides operations organised by resource type. For anything not covered, you can use the **Execute Query** operation.

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th>Operation</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td rowspan="5"><strong>Record Operations</strong></td>
      <td><strong>Create Record</strong></td>
      <td>Create a single record in a table</td>
    </tr>
    <tr>
      <td><strong>Get Record</strong></td>
      <td>Retrieve a specific record by ID</td>
    </tr>
    <tr>
      <td><strong>Update Record</strong></td>
      <td>Update a specific record by ID</td>
    </tr>
    <tr>
      <td><strong>Upsert Record</strong></td>
      <td>Create or update a record (insert if not exists, update if exists)</td>
    </tr>
    <tr>
      <td><strong>Delete Record</strong></td>
      <td>Delete a specific record by ID</td>
    </tr>
    <tr>
      <td rowspan="9"><strong>Table Operations</strong></td>
      <td><strong>Get All Records</strong></td>
      <td>Retrieve all records from a table</td>
    </tr>
    <tr>
      <td><strong>Create Many</strong></td>
      <td>Create multiple records in a table</td>
    </tr>
    <tr>
      <td><strong>Get Many</strong></td>
      <td>Retrieve multiple records by IDs</td>
    </tr>
    <tr>
      <td><strong>Update All Records</strong></td>
      <td>Update all records in a table</td>
    </tr>
    <tr>
      <td><strong>Delete All Records</strong></td>
      <td>Delete all records from a table</td>
    </tr>
    <tr>
      <td><strong>Merge All Records</strong></td>
      <td>Merge the same data into all records in a table</td>
    </tr>
    <tr>
      <td><strong>Create Table</strong></td>
      <td>Define a new table with optional schema</td>
    </tr>
    <tr>
      <td><strong>Delete Table</strong></td>
      <td>Remove a table from the database</td>
    </tr>
    <tr>
      <td><strong>Get Table</strong></td>
      <td>Retrieve information about a table</td>
    </tr>
    <tr>
      <td rowspan="3"><strong>Field Operations</strong></td>
      <td><strong>List Fields</strong></td>
      <td>List all fields defined on a table</td>
    </tr>
    <tr>
      <td><strong>Create Field</strong></td>
      <td>Create a new field on a table</td>
    </tr>
    <tr>
      <td><strong>Delete Field</strong></td>
      <td>Delete a field from a table</td>
    </tr>
    <tr>
      <td rowspan="2"><strong>Index Operations</strong></td>
      <td><strong>Create Index</strong></td>
      <td>Create a new index on a table</td>
    </tr>
    <tr>
      <td><strong>Delete Index</strong></td>
      <td>Delete an index from a table</td>
    </tr>
    <tr>
      <td rowspan="3"><strong>Relationship Operations</strong></td>
      <td><strong>Create Relationship</strong></td>
      <td>Create a relationship between two records</td>
    </tr>
    <tr>
      <td><strong>Delete Relationship</strong></td>
      <td>Delete a relationship between records</td>
    </tr>
    <tr>
      <td><strong>Query Relationships</strong></td>
      <td>Query relationships between records</td>
    </tr>
    <tr>
      <td rowspan="2"><strong>Query Operations</strong></td>
      <td><strong>Execute Query</strong></td>
      <td>Execute a raw SurrealQL query with parameters</td>
    </tr>
    <tr>
      <td><strong>Build Select Query</strong></td>
      <td>Build SELECT queries using a visual interface with WHERE, ORDER BY, GROUP BY, and other clauses</td>
    </tr>
    <tr>
      <td rowspan="3"><strong>System Operations</strong></td>
      <td><strong>Health Check</strong></td>
      <td>Check if the database instance is responsive</td>
    </tr>
    <tr>
      <td><strong>Version</strong></td>
      <td>Get the version of the SurrealDB instance</td>
    </tr>
    <tr>
      <td><strong>Get Pool Statistics</strong></td>
      <td>Monitor connection pool performance and statistics</td>
    </tr>
  </tbody>
</table>

## Understanding SurrealDB and n8n integration

### Connection protocol

> [!IMPORTANT]
> Due to n8n's architecture, this node only supports HTTP/HTTPS connections to SurrealDB. WebSocket connections (WS/WSS) are not supported.

Your connection string must start with `http://` or `https://` (not `ws://` or `wss://`). This means that when configuring your SurrealDB instance, ensure it's accessible via `HTTP/HTTPS`.

If you're using SurrealDB Cloud or another instance that only offers WebSocket connections, you'll need to set up a [self-hosted SurrealDB instance](/pricing) with HTTP enabled. This limitation is due to how n8n handles connections and executes node operations. You can read more about this in the [n8n documentation](https://www.npmjs.com/package/n8n-nodes-surrealdb).

This node uses the HTTP/HTTPS protocol exclusively, which means that each operation creates a new connection to SurrealDB, the connection is closed after the operation completes, and no persistent connection is maintained between operations.

### Connection pooling

The SurrealDB node includes connection pooling to improve performance and resource management. Connection pooling allows the node to reuse database connections across multiple operations, reducing connection overhead and improving response times.

#### Pool configuration options

You can configure the connection pool through the "Connection Pooling" options in any node operation:

- **Max Connections** (default: 10): Maximum number of connections in the pool
- **Min Connections** (default: 2): Minimum number of connections to keep in the pool
- **Acquire Timeout** (default: 30000ms): Maximum time to wait for a connection from the pool
- **Health Check Interval** (default: 60000ms): Interval between health checks for pool connections
- **Max Idle Time** (default: 300000ms): Maximum time a connection can remain idle before being closed
- **Retry Attempts** (default: 3): Number of retry attempts for failed connection acquisitions
- **Retry Delay** (default: 1000ms): Delay between retry attempts

#### Pool monitoring

Use the **System > Get Pool Statistics** operation to monitor pool performance:

```json
{
  "poolStatistics": {
    "totalConnections": 5,
    "activeConnections": 2,
    "idleConnections": 3,
    "waitingRequests": 0,
    "totalRequests": 150,
    "failedRequests": 2,
    "averageResponseTime": 45,
    "successRate": 99
  },
  "performance": {
    "averageResponseTimeMs": 45,
    "requestsPerSecond": 2,
    "errorRate": 1
  },
  "poolHealth": {
    "utilizationRate": 40,
    "availableConnections": 3,
    "waitingRequests": 0
  }
}
```

### SurrealDB result handling

SurrealDB operations often return empty results rather than errors when no matching data is found. This behaviour differs from many other databases and can be important to understand when building workflows:

- **Empty Results vs. Errors**: A query for a non-existent record returns an empty result, not an error
- **Always Output Data**: The "Always Output Data" option is particularly useful with SurrealDB to ensure your workflow continues even when no results are found

### Working with SurrealDB data types

SurrealDB supports rich data types that map well to n8n's JSON handling:

- **Records and IDs**: SurrealDB record IDs use the format `table:id`
- **Relationships**: Relationships are first-class citizens in SurrealDB
- **Arrays and Objects**: Nested data structures are fully supported

## Error handling

The SurrealDB node includes an error handling and recovery system that automatically manages common database issues:

### Automatic error classification

The system automatically categorizes errors into different types:
<table>
  <thead>
    <tr>
      <th>Error Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Connection Errors</strong></td>
      <td>Network issues, timeouts, connection refused</td>
    </tr>
    <tr>
      <td><strong>Authentication Errors</strong></td>
      <td>Invalid credentials, unauthorised access</td>
    </tr>
    <tr>
      <td><strong>Query Errors</strong></td>
      <td>Syntax errors, malformed queries</td>
    </tr>
    <tr>
      <td><strong>Validation Errors</strong></td>
      <td>Invalid data, missing required fields</td>
    </tr>
    <tr>
      <td><strong>System Errors</strong></td>
      <td>Database server issues, internal errors</td>
    </tr>
  </tbody>
</table>

### Intelligent retry logic

- **Exponential Backoff**: Automatic retry with increasing delays
- **Operation-Specific Retries**: Different retry strategies for read vs write operations
- **Configurable Limits**: Adjustable retry counts and delays
- **Smart Error Filtering**: Only retry on recoverable errors

### Connection recovery

- **Automatic Reconnection**: Reconnects to SurrealDB on connection failures
- **Re-authentication**: Automatically re-authenticates after reconnection
- **Connection Validation**: Verifies connection health before retrying operations

### Enhanced error reporting

When `Continue on Fail` is enabled, errors include detailed information:
```json
{
  "error": {
    "message": "Connection timeout",
    "category": "TIMEOUT_ERROR",
    "severity": "MEDIUM",
    "retryable": true,
    "context": {
      "operation": "executeQuery",
      "itemIndex": 0,
      "timestamp": "2024-01-15T10:30:00Z",
      "recoveryStrategy": "CONNECTION_RECOVERY"
    }
  }
}
```

### Error handling strategies

Different operation types use different error handling strategies:

- **Read Operations**: Faster retries, continue on low/medium errors
- **Write Operations**: More retries, stop on medium+ errors
- **Critical Operations**: Minimal retries, stop on any error
- **Bulk Operations**: Moderate retries, handle rate limiting

For detailed information about the error handling system, see [Error Handling Documentation](https://github.com/surrealdb/n8n-nodes-surrealdb/blob/main/docs/ERROR_HANDLING.md).

## Resources

- [n8n Community Nodes Documentation](https://docs.n8n.io/integrations/community-nodes/)
- [SurrealDB Documentation](/docs)
- [SurrealQL Reference](/docs/reference/query-language.md)

---

Source: https://surrealdb.com/docs/build/integrations/data-management/overview

# Data management integrations

This section contains information about the different data management features of SurrealDB.

SurrealDB integrates with data platforms that help you store, organise, and transform your data. With the integrations below, you can build ETL pipelines, ingest data from different sources, and maintain data quality at scale, all on SurrealDB's multi-model architecture.

<table>
    <thead>
        <tr>
            <th>Integration</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><a href="/docs/build/integrations/data-management/airbyte.md">Airbyte</a></td>
            <td>Data integration platform specialising in ELT pipelines.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/data-management/fivetran.md">Fivetran</a></td>
            <td>Data integration platform to manage data transfer between different sources and destination systems.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/data-management/n8n.md">n8n</a></td>
            <td>Workflow automation platform that lets you build custom event-driven automations.</td>
        </tr>
        <tr>
            <td><a href="/docs/build/integrations/data-management/qyrus.md">Qyrus</a></td>
            <td>Data quality assurance platform for cross-source comparisons and single-source validations.</td>
        </tr>
    </tbody>
</table>

---

Source: https://surrealdb.com/docs/build/integrations/data-management/qyrus

# Qyrus

Qyrus Data Testing integrates with SurrealDB for data quality assurance. Use Compare Jobs for cross-source validation (migrations, sync) and Evaluate Jobs for single-source checks (schema, profiling). Configure with Host URL, Port, Namespace, Database, and SurrealQL queries.

**Qyrus Data Testing** is a data quality assurance platform that integrates with SurrealDB. Use it to validate data, compare datasets across sources, and run quality checks on SurrealDB tables using SurrealQL queries.

## Overview

- **Integration type**: SurrealDB multi-model database connector for Qyrus Data Testing
- **Primary use**: Data quality assurance, cross-source comparison, and single-source validation
- **Query language**: SurrealQL for fetching and validating data
- **Documentation**: [Qyrus Data Testing](https://docs.qyrus.com/data-testing)

## Use cases

Qyrus supports two job types when using SurrealDB as a data source:

| Job Type | Description | Example Scenarios |
|----------|-------------|-------------------|
| **Compare Job** | Validates differences between two data sources | Data migrations, synchronization checks, production vs. staging comparison, source vs. target validation |
| **Evaluate Job** | Validates a single SurrealDB data source | Schema checks, constraint validation, data profiling, quality rules, custom business logic |

## Configuration parameters

When configuring SurrealDB as a data source in Qyrus, use the following parameters:

<table>
  <thead>
    <tr>
      <th>Parameter</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Host URL</strong></td>
      <td>SurrealDB server URL (HTTP / HTTPS / WebSocket)</td>
    </tr>
    <tr>
      <td><strong>Port</strong></td>
      <td>Port on which the SurrealDB service is running</td>
    </tr>
    <tr>
      <td><strong>Namespace</strong></td>
      <td>SurrealDB namespace to connect to</td>
    </tr>
    <tr>
      <td><strong>Database Name</strong></td>
      <td>Target database inside the namespace</td>
    </tr>
    <tr>
      <td><strong>Username</strong></td>
      <td>Login username (if authentication is enabled)</td>
    </tr>
    <tr>
      <td><strong>Password</strong></td>
      <td>Login password (if authentication is enabled)</td>
    </tr>
    <tr>
      <td><strong>Query</strong></td>
      <td>SurrealQL query that fetches data from the selected table</td>
    </tr>
    <tr>
      <td><strong>Enable Limit &amp; Offset</strong> (Optional)</td>
      <td>Enables pagination controls for query execution</td>
    </tr>
  </tbody>
</table>

## Getting started

To set up SurrealDB with Qyrus Data Testing:

1. Visit the [Qyrus Data Testing documentation](https://docs.qyrus.com/data-testing) for an overview of the platform
2. Configure SurrealDB as a data source using the [SurrealDB connector guide](https://docs.qyrus.com)
3. Create and run [Compare](https://docs.qyrus.com) or [Evaluate](https://docs.qyrus.com) jobs as needed
4. View validation results in [Job History](https://docs.qyrus.com/data-testing) and detailed reports for completed jobs

## Frequently Asked Questions

### What is Qyrus data testing?

Qyrus Data Testing is a data quality assurance tool that enables cross-source comparisons (Compare Jobs) and internal validations (Evaluate Jobs) to maintain reliable and accurate data across databases and systems.

### How does SurrealDB integrate with Qyrus?

SurrealDB is available as a connector in Qyrus. You configure the connection using Host URL, Port, Namespace, Database Name, and optionally Username and Password. Data is fetched from SurrealDB using SurrealQL queries that you define in Qyrus.

### When should I use a Compare Job vs. an Evaluate Job?

- **Compare Job**: Use when you need to validate that two data sources match (e.g., after a migration, during sync checks, or when comparing production and staging).
- **Evaluate Job**: Use when you need to validate a single SurrealDB data source (e.g., schema checks, data profiling, quality rules, or custom logic).

### What SurrealQL queries can I use in Qyrus?

You can use any valid SurrealQL query that fetches data from your SurrealDB tables. The query runs against the configured Namespace and Database. Examples: `SELECT * FROM table`, `SELECT * FROM table LIMIT 100`, or more complex queries with filters and joins.

### Does Qyrus support SurrealDB authentication?

Yes. If your SurrealDB instance has authentication enabled, provide the **Username** and **Password** in the Qyrus connector configuration.

### Where can I find more help?

- [Qyrus Data Testing - SurrealDB Connector](https://docs.qyrus.com)
- [Manage Jobs](https://docs.qyrus.com)
- [Data Testing Overview](https://docs.qyrus.com/data-testing)

---

Source: https://surrealdb.com/docs/build/integrations/database-clients/beekeeper-studio

# Beekeeper Studio

Connect Beekeeper Studio to SurrealDB to browse a namespace and database, inspect field types and run SurrealQL from a desktop editor.

[Beekeeper Studio](https://www.beekeeperstudio.io/) is a cross-platform desktop SQL editor and database manager. It has a SurrealDB connection type of its own, so a namespace and database can be browsed and queried without anything in between.

> [!NOTE]
> Beekeeper Studio describes its SurrealDB support as alpha, and repeats that in the connection dialog with a link to the features it currently covers. Read that list before depending on it for more than browsing and querying.

## Before you start

- A running SurrealDB instance. See [installation](/docs/running/installation.md) if you do not have one yet.
- [Beekeeper Studio](https://www.beekeeperstudio.io/db/surrealdb-client/) installed.

## Connect

1. Open Beekeeper Studio and select **New Connection**.
2. Set **Connection Type** to **SurrealDB**.
3. Fill in the connection. The address is three separate fields rather than one URL:
   - **Protocol**, such as `http`
   - **Host**, such as `localhost`
   - **Port**, `8000` by default
4. Choose an **Authentication Method** for the level the account belongs to, such as **Root**, then give the **User** and **Password**.
5. Enter the **Namespace** and **Database** to open.
6. Select **Test** to check the details, then **Connect**.

![The Beekeeper Studio connection dialog with SurrealDB chosen as the connection type, showing separate protocol, host and port fields above the authentication method, user, password, namespace and database.](~/assets/img/integrations/beekeeper_studio_connection.png)

A connection can be saved into a folder and given a colour, which helps where several instances are open at once.

## Browse and query

Connecting opens a query tab and lists the database's entities in the sidebar. Expanding a table shows its fields with the types SurrealDB holds them as, so a `record<person>` link or an `array<string>` reads as itself rather than as a foreign key.

Queries are written in SurrealQL and run with **Run All**, and results appear in a sortable grid below the editor:

```surql
SELECT product_name, quantity, price FROM cart LIMIT 10;
```

![Beekeeper Studio connected to SurrealDB, with the entity sidebar showing a cart table and its field types, a SurrealQL query in the editor, and ten result rows in the grid below.](~/assets/img/integrations/beekeeper_studio_query.png)

The screenshots here use the [Surreal Deal Store](/docs/explore/tutorials/demos/surreal-deal-store.md) sample dataset.

---

Source: https://surrealdb.com/docs/build/integrations/embeddings-providers/fastembed

# Fastembed

This section contains information about using Fastembed to retrieve embeddings to store in SurrealDB

Fastembed is a library that allows you to generate vector embeddings locally, without needing an API key or calling into an external service.

Fastembed uses the included ONNX runtime to run its embedding models, downloading each model once, the first time it is used.

Fastembed libraries are available for the following languages:

* [Python](https://github.com/qdrant/fastembed)
* [Rust](https://crates.io/crates/fastembed)
* [Go](https://github.com/Anush008/fastembed-go)
* [JavaScript](https://github.com/Anush008/fastembed-js)

## Overview of available models

The following is an overview of most of the models available for Fastembed. General use cases are:

* Prototyping, development speed, cost savings: models with small vector embedding sizes tend to take up a relatively small amount of disk size as well (generally a few hundred MB) and can be quickly downloaded and run locally.
* Small or edge devices: devices with no internet access can still take advantage of vector embeddings.
* Particular use cases: some models are specialised for certain use cases such as images, certain languages, and contexts.

### MiniLM series

Fast general-purpose embeddings. Choose L6 for speed, L12 for quality. Ideal for semantic search, clustering, and similarity tasks.

A "quantized" model means that it is optimised for faster inference and lower memory usage, often with minimal quality loss.

| Model name      | Embedding size | Description                                         |
| --------------- | -------------- | --------------------------------------------------- |
| AllMiniLML6V2   | 384            | Sentence Transformer model, MiniLM-L6-v2            |
| AllMiniLML6V2Q  | 384            | Quantized Sentence Transformer model, MiniLM-L6-v2  |
| AllMiniLML12V2  | 384            | Sentence Transformer model, MiniLM-L12-v2           |
| AllMiniLML12V2Q | 384            | Quantized Sentence Transformer model, MiniLM-L12-v2 |

### BGE series

Used for dense retrieval and semantic similarity. BGESmallENV15 is optimised for speed and tends to be the default choice for many applications.

| Model name     | Embedding size | Description                                                  |
| -------------- | -------------- | ------------------------------------------------------------ |
| BGEBaseENV15   | 768            | v1.5 release of the base English model                       |
| BGEBaseENV15Q  | 768            | Quantized v1.5 release of the base English model             |
| BGELargeENV15  | 1024           | v1.5 release of the large English model                      |
| BGELargeENV15Q | 1024           | Quantized v1.5 release of the large English model            |
| BGESmallENV15  | 384            | v1.5 release of the fast and default English model           |
| BGESmallENV15Q | 384            | Quantized v1.5 release of the fast and default English model |

### Nomic embed text

Used for large context window embeddings.

Optimised for long-context English text (8K tokens). v1.5 improves quality over v1.

| Model name         | Embedding size | Description                                                     |
| ------------------ | -------------- | --------------------------------------------------------------- |
| NomicEmbedTextV1   | 768            | 8192 context length english model                               |
| NomicEmbedTextV15  | 768            | v1.5 release of the 8192 context length english model           |
| NomicEmbedTextV15Q | 768            | Quantized v1.5 release of the 8192 context length english model |

### Paraphrase models

Used for paraphrase detection and multilingual similarity. Ideal for sentence equivalence and semantic matching tasks.

| Model name               | Embedding size | Description                                                              |
| ------------------------ | -------------- | ------------------------------------------------------------------------ |
| ParaphraseMLMiniLML12V2  | 384            | Multi-lingual model                                                      |
| ParaphraseMLMiniLML12V2Q | 384            | Quantized Multi-lingual model                                            |
| ParaphraseMLMpnetBaseV2  | 768            | Sentence-transformers model for tasks like clustering or semantic search, based on the MPNet architecture. |

### Chinese BGE models

| Model name    | Embedding size | Description                             |
| ------------- | -------------- | --------------------------------------- |
| BGESmallZHV15 | 512            | v1.5 release of the small Chinese model |
| BGELargeZHV15 | 1024           | v1.5 release of the large Chinese model |

### Modernbert and multilingual e5

Used for context-rich multilingual embeddings. Great for cross-language retrieval and nuanced contextual understanding.

| Model name           | Embedding size | Description                                    |
| -------------------- | -------------- | ---------------------------------------------- |
| ModernBertEmbedLarge | 1024           | Large model of ModernBert Text Embeddings      |
| MultilingualE5Small  | 384            | Small model of multilingual E5 Text Embeddings |
| MultilingualE5Base   | 768            | Base model of multilingual E5 Text Embeddings  |
| MultilingualE5Large  | 1024           | Large model of multilingual E5 Text Embeddings |

### Mxbai and GTE

Used for high-quality English/multilingual embeddings.

| Model name         | Embedding size | Description                                                |
| ------------------ | -------------- | ---------------------------------------------------------- |
| MxbaiEmbedLargeV1  | 1024           | Large English embedding model from MixedBreed.ai           |
| MxbaiEmbedLargeV1Q | 1024           | Quantized Large English embedding model from MixedBreed.ai |
| GTEBaseENV15       | 768            | Base multilingual embedding model from Alibaba            |
| GTEBaseENV15Q      | 768            | Quantized base multilingual embedding model from Alibaba  |
| GTELargeENV15      | 1024           | Large multilingual embedding model from Alibaba            |
| GTELargeENV15Q     | 1024           | Quantized large multilingual embedding model from Alibaba  |

### CLIP and code models

Use CLIP for image-text matching, Jina for code search and retrieval. JinaEmbeddingsV2BaseCode is optimised for embedding code snippets.

| Model name               | Embedding size | Description                         |
| ------------------------ | -------------- | ----------------------------------- |
| ClipVitB32               | 512            | CLIP text encoder based on ViT-B/32 |
| JinaEmbeddingsV2BaseCode | 768            | Jina embeddings v2 base code        |

## Language-specific example

The following example in Rust demonstrates how SurrealDB can be used to store the embeddings from the default language model for a number of phrases, after which it can be prompted to return the three closest results to a certain prompt.

First add a few crates to Cargo.toml with the following command:

```bash
cargo add anyhow fastembed serde tokio surrealdb --features surrealdb/kv-mem
```

Then use the following code.

```rust
use anyhow::Error;
use fastembed::{EmbeddingModel, InitOptions, TextEmbedding};
use serde::Serialize;
use surrealdb::{
    Surreal, Value,
    engine::any::{Any, connect},
};

const SCHEMA: &str = "DEFINE TABLE document;
        DEFINE FIELD text ON document TYPE string;
        DEFINE FIELD embedding ON document TYPE array<float>;
        // Uncomment this to use HNSW index, ensure that number after DIMENSION matches size of embedding
        // DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 384 DIST COSINE";

const INSERT_QUERY: &str = "INSERT INTO document $docs";

const VECTOR_QUERY: &str = "SELECT 
        text, 
        vector::distance::knn() AS distance 
        FROM document
        WHERE embedding <|3,COSINE|> $embeds
        ORDER BY distance";

#[derive(Serialize)]
struct DocumentInput {
    text: String,
    embedding: Vec<f32>,
}

async fn store_docs(
    input: Vec<&str>,
    db: &Surreal<Any>,
    model: &mut TextEmbedding,
) -> Result<(), Error> {
    let docs = model
        .embed(input.clone(), None)?
        .into_iter()
        .zip(input.into_iter())
        .map(|(embedding, text)| DocumentInput {
            text: text.to_string(),
            embedding,
        })
        .collect::<Vec<DocumentInput>>();

    db.query(INSERT_QUERY).bind(("docs", docs)).await?;
    Ok(())
}

async fn test_embed(
    input: &str,
    db: &Surreal<Any>,
    model: &mut TextEmbedding,
) -> Result<(), Error> {
    let Some(embeds) = model.embed(vec![input], None)?.into_iter().next() else {
        return Err(anyhow::anyhow!("Nothing found at index 0"));
    };

    let val = db
        .query(VECTOR_QUERY)
        .bind(("embeds", embeds.clone()))
        .await?
        .take::<Value>(0)?;
    println!("{val}\n");
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Default model
    let mut model = TextEmbedding::try_new(InitOptions::new(EmbeddingModel::BGESmallENV15))?;

    let db = connect("memory").await?;

    db.use_ns("ns").use_db("db").await?;

    db.query(SCHEMA).await?;

    let input = vec![
        // Cities
        "Calgary is a city in the Canadian province of Alberta.",
        "Ljubljana is the capital and largest city of Slovenia.",
        // Historical / mythological figures
        "Xenophon of Athens was a Greek military leader, philosopher, and historian.",
        "King Arthur was a mythical king in the mythology of Great Britain.",
        // Planets
        "Venus is the second planet from the Sun.",
        "Ceres is a dwarf planet in the middle main asteroid belt between the orbits of Mars and Jupiter.",
        // Languages
        "Manx is a Gaelic language of the insular Celtic branch of the Celtic language family",
        "Interlingue, originally Occidental, is an international auxiliary language created in 1922.",
        // Sea animals
        "Octopuses have a complex nervous system and are among the most intelligent and behaviourally diverse invertebrates.",
        "Clams have no central nervous system at all and are near to plants in intelligence.",
    ];
    store_docs(input, &db, &mut model).await?;

    println!("Edmonton is closest to:");
    test_embed("Edmonton", &db, &mut model).await?;

    println!("Merlin is closest to:");
    test_embed("Merlin", &db, &mut model).await?;

    println!("Earth is closest to:");
    test_embed("Earth", &db, &mut model).await?;

    println!("Irish is closest to:");
    test_embed("Irish language", &db, &mut model).await?;

    println!("Squid are closest to:");
    test_embed("Squid", &db, &mut model).await?;

    Ok(())
}
```

Output of the example with the default model:

```text
Edmonton is closest to:
[
    { distance: 0.2596421358215669f, text: 'Calgary is a city in the Canadian province of Alberta.' },
    { distance: 0.5010449624435647f, text: 'Ljubljana is the capital and largest city of Slovenia.' },
    { distance: 0.5242241576926254f, text: 'Interlingue, originally Occidental, is an international auxiliary language created in 1922.' }
]

Merlin is closest to:
[
    { distance: 0.3653307924860497f, text: 'King Arthur was a mythical king in the mythology of Great Britain.' },
    { distance: 0.4515194174120666f, text: 'Manx is a Gaelic language of the insular Celtic branch of the Celtic language family' },
    { distance: 0.5317039966149415f, text: 'Calgary is a city in the Canadian province of Alberta.' }
]

Earth is closest to:
[
    { distance: 0.3380429615054925f, text: 'Venus is the second planet from the Sun.' },
    { distance: 0.3764237673020161f, text: 'Ceres is a dwarf planet in the middle main asteroid belt between the orbits of Mars and Jupiter.' },
    { distance: 0.444087039462282f, text: 'Calgary is a city in the Canadian province of Alberta.' }
]

Irish is closest to:
[
    { distance: 0.27517683002655635f, text: 'Manx is a Gaelic language of the insular Celtic branch of the Celtic language family' },
    { distance: 0.34080671701374754f, text: 'Interlingue, originally Occidental, is an international auxiliary language created in 1922.' },
    { distance: 0.5113325799682362f, text: 'King Arthur was a mythical king in the mythology of Great Britain.' }
]

Squid are closest to:
[
    { distance: 0.3439891425642231f, text: 'Octopuses have a complex nervous system and are among the most intelligent and behaviourally diverse invertebrates.' },
    { distance: 0.4707156750207915f, text: 'Manx is a Gaelic language of the insular Celtic branch of the Celtic language family' },
    { distance: 0.517311424260043f, text: 'Clams have no central nervous system at all and are near to plants in intelligence.' }
]
```

---

Source: https://surrealdb.com/docs/build/integrations/embeddings-providers/mistral

# Mistral

End-to-end guide for building a fast semantic-search stack with Mistral-Embed vectors stored in SurrealDB’s native HNSW index.

**Python**

Modern open-source RAG pipelines need two things:

1. **High-quality embeddings** - *Mistral-Embed* (`mistral-embed`) returns 1 024-dimensional float vectors that rival OpenAI + Cohere.
2. **Fast vector storage** - **SurrealDB** (≥ v1.5) ships an in-memory HNSW index, queried with the `<|K,EF|>` operator in SurrealQL.

The sections below connect the two, from installation through ingestion and search to a complete script.

## Prerequisites

```bash
pip install mistralai surrealdb
````

Set two environment variables (or hard-code them if you must):

```bash
export SDB_URL="http://localhost:8000/rpc"   # ← SurrealDB RPC endpoint
export MISTRAL_API_KEY="sk-…"                # ← your Mistral key
```

## Connect and create the schema

```python
from mistralai.client import MistralClient
from surrealdb import Surreal
import os, asyncio

# ----- 1.1 · Config -----------------------------------------------------------------
SDB_URL  = os.getenv("SDB_URL", "http://localhost:8000/rpc")
SDB_USER = os.getenv("SDB_USER", "root")
SDB_PASS = os.getenv("SDB_PASS", "secret")
NS, DB   = "demo", "demo"
TABLE    = "mistral_docs"
MODEL    = "mistral-embed"

# ----- 1.2 · Clients ----------------------------------------------------------------
sdb   = Surreal(SDB_URL)
mistr = MistralClient(api_key=os.environ["MISTRAL_API_KEY"])

async def init_db():
    await sdb.signin({"user": SDB_USER, "pass": SDB_PASS})
    await sdb.use(NS, DB)

    # one quick embedding → get true vector dimension
    dim = len(mistr.embeddings(model=MODEL, input=["ping"]).data[0].embedding)

    schema = """
    DEFINE TABLE $tb SCHEMALESS PERMISSIONS NONE;
    DEFINE FIELD text      ON $tb TYPE string;
    DEFINE FIELD embedding ON $tb TYPE array;

    DEFINE INDEX hnsw_idx ON $tb
      FIELDS embedding
      HNSW DIMENSION {dim}
      DIST   COSINE;
    """
    await sdb.query(schema, {"tb": TABLE})

asyncio.run(init_db())
```

### Why detect the dimension dynamically?

*Future-proofing:* if Mistral introduces a **small** or **large** Embed model with a different dimension, the code auto-adapts.

## Embed and bulk-insert documents

```python
DOCS = [
    "SurrealDB offers an in-memory HNSW vector index for low-latency search.",
    "Mistral-Embed produces 1 024-dimensional embeddings.",
    "You can build a completely open-source RAG stack with these two tools.",
]

async def insert_docs(docs, batch=64):
    rows = []
    for i in range(0, len(docs), batch):
        chunk = docs[i : i + batch]
        vecs  = mistr.embeddings(model=MODEL, input=chunk).data
        rows += [
            {
                "id":        f"{TABLE}:{i+j}",
                "text":      chunk[j],
                "embedding": vec.embedding,
            }
            for j, vec in enumerate(vecs)
        ]
    await sdb.query(f"INSERT INTO {TABLE} $data", {"data": rows})

asyncio.run(insert_docs(DOCS))
```

Bulk-inserting sends every document in one SurrealQL call and one network round trip, which is **much faster** than inserting records one at a time.

## Search with a natural-language query

```python
async def search(query: str, k: int = 3, ef: int = 64):
    q_vec = mistr.embeddings(model=MODEL, input=[query]).data[0].embedding
    surql = """
    LET $q := $vec;
    SELECT id, text, vector::distance::knn() AS score
    FROM $tb
    WHERE embedding <|{k},{ef}|> $q
    ORDER BY score;
    """
    res = await sdb.query(surql, {"vec": q_vec, "tb": TABLE})
    return res[0].result

hits = asyncio.run(search("Which database supports native vector search?"))
for h in hits:
    print(f"⭐ {h['text']}  (score={h['score']:.4f})")
```

`<|K,EF|>` activates the HNSW **K-nearest-neighbour** operator (`K=3`, `efSearch=64`).
`vector::distance::knn()` exposes the cosine distance already computed inside the index, no post-processing needed.

## Full script (ready to run)

```python
# mistral_surreal_demo.py
from __future__ import annotations
import os, asyncio
from mistralai.client import MistralClient
from surrealdb import Surreal

SDB_URL  = os.getenv("SDB_URL", "http://localhost:8000/rpc")
SDB_USER = os.getenv("SDB_USER", "root")
SDB_PASS = os.getenv("SDB_PASS", "secret")
NS, DB, TABLE = "demo", "demo", "mistral_docs"
MODEL   = "mistral-embed"
KEY     = os.environ["MISTRAL_API_KEY"]  # export first!

sdb, mistr = Surreal(SDB_URL), MistralClient(api_key=KEY)

DOCS = [
    "SurrealDB's vector index is built on HNSW.",
    "Mistral-Embed vectors offer strong semantic quality.",
    "Together they form a fast, open-source search stack.",
]

async def main():
    await sdb.signin({"user": SDB_USER, "pass": SDB_PASS})
    await sdb.use(NS, DB)

    dim = len(mistr.embeddings(model=MODEL, input=["x"]).data[0].embedding)
    await sdb.query("""
        DEFINE TABLE $tb SCHEMALESS PERMISSIONS NONE;
        DEFINE FIELD text ON $tb TYPE string;
        DEFINE FIELD embedding ON $tb TYPE array;
        DEFINE INDEX hnsw_idx ON $tb FIELDS embedding
               HNSW DIMENSION {dim} DIST COSINE;
    """, {"tb": TABLE})

    # ingest if empty
    if (await sdb.query(f"SELECT count() FROM {TABLE};"))[0].result[0]["count"] == 0:
        rows = []
        vecs = mistr.embeddings(model=MODEL, input=DOCS).data
        rows = [
            {"id": f"{TABLE}:{i}", "text": DOCS[i], "embedding": v.embedding}
            for i, v in enumerate(vecs)
        ]
        await sdb.query(f"INSERT INTO {TABLE} $data", {"data": rows})

    # search
    q_vec = mistr.embeddings(model=MODEL,
                             input=["open-source vector database"] ).data[0].embedding
    res = await sdb.query("""
        LET $q := $vec;
        SELECT text, vector::distance::knn() AS score
        FROM {TABLE}
        WHERE embedding <|3,64|> $q
        ORDER BY score;
    """, {"vec": q_vec})
    print(res[0].result)

if __name__ == "__main__":
    asyncio.run(main())
```

## About quantisation

SurrealDB currently stores vectors as `float32` / `float64` arrays and does **not** ship built-in binary or int8 quantisation.
If memory is critical you can:

1. Quantise offline to int8 (e.g. with *faiss* or *sentence-transformers*).
2. Store the int8 arrays in **another field** (SurrealQL’s array type is agnostic).
3. Execute a **two-stage search**: coarse K-NN on the int8 field, then rescore on the full-precision field.

### You’re done 🚀

You now have a clean, fully-async SurrealDB setup that stores **Mistral-Embed** vectors, supports **fast HNSW search**, and can be dropped into any RAG or semantic-search workflow.

**Rust**

## Setup

Create a new Cargo project with `cargo new project_name` and go into the project folder, then add the following dependencies inside `Cargo.toml`:

```toml
anyhow = "1.0.98"
mistralai-client = "0.14.0"
serde = "1.0.228"
surrealdb = { version = "2.3", features = ["kv-mem"] }
tokio = "1.49.0"
```
<br />

You can add the same dependencies on the command line through a single command:

```bash
cargo add anyhow mistralai-client serde tokio surrealdb --features surrealdb/kv-mem
```
<br />

Connect to a database using "memory" for an embedded instance:

```rust
use anyhow::Error;
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() -> Result<(), Error> {
    let db = connect("memory").await?;
    Ok(())
}
```
<br />

Or another address if accessing a Cloud or local instance, such as:

```rust
// Cloud address
let db = connect("wss://myinstance-06a4h41t12rtj7lsg45m3prm1k.aws-use1.surreal.cloud").await?;

// Local address
let db = connect("ws://localhost:8000").await?;
```
<br />

Then select a namespace and database name.

```rust
db.use_ns("ns").use_db("db").await?;
```
<br />

## Create a vector table and index

Create a table called `document` to store documents and embeddings. The `HNSW` index is one way to [maintain performance](/docs/reference/query-language/language-primitives/operators.md#hnsw-method) if the dataset becomes quite large; otherwise, it can be left out.

```surql
DEFINE TABLE document;
DEFINE FIELD text ON document TYPE string;
DEFINE FIELD embedding ON document TYPE array<float>;
DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1024 DIST COSINE;
```
<br />

The size of the vector (1024 here) represents the number of dimensions in the embedding. This is to match Mistral AI's `mistral-embed` model, which uses [1024 as its length](https://docs.mistral.ai/getting-started/models/models_overview/#premier-models).

These statements can all be put inside a single `.query()` call in the Rust SDK, followed by a line to check for any errors.

```rust
let mut res = db
    .query(
        "DEFINE TABLE document;
DEFINE FIELD text ON document TYPE string;
DEFINE FIELD embedding ON document TYPE array<float>;
DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1024 DIST COSINE;",
    )
    .await?;
for (index, error) in res.take_errors() {
    println!("Error in query {index}: {error}");
}
```
<br />

## Generate Mistral AI embeddings

At this point, you will need a [key](https://console.mistral.ai/api-keys) to interact with Mistral AI's platform. They offer a free tier for experimentation, after which you will be able to create a key to interact with it via the code below.

The best way to set the key is as an environment variable, which we will set to be a static called `KEY`. The client will look for one called `MISTRAL_API_KEY`, though you can change this when setting up the Mistral AI Rust client if you like.

```rust
// Looks for MISTRAL_API_KEY
let client = Client::new(Some(KEY.to_string()), None, None, None)?;
// Looks for OTHER_ENV_VAR
let client = Client::new(Some(KEY.to_string()), Some("OTHER_ENV_VAR".to_string()), None, None)?;
```

Using a `LazyLock` will let us call it via `std::env::var()` function the first time it is accessed. You can of course simply put it into a `const` for simplicity when first testing, but always remember to never hard-code API keys in your code in production.

```rust
static KEY: LazyLock<String> = LazyLock::new(|| {
    std::env::var("MISTRAL_API_KEY").unwrap()
});
```
<br />

And then run the code like this:

```bash
MISTRAL_API_KEY=whateverthekeyis cargo run
```
<br />

Or like this if you are using PowerShell on Windows.

```powershell
$env:MISTRAL_API_KEY = "whateverthekeyis"
cargo run
```
<br />

We will also create a `const MODEL` to hold the Mistral AI model used, which in this case is an `EmbedModel::MistralEmbed`.

```rust
const MODEL: EmbedModel = EmbedModel::MistralEmbed;
```

Inside `main()`, [create a client](https://docs.rs/mistralai-client/0.14.0/mistralai_client/v1/client/struct.Client.html#method.new) from the `mistralai-client` crate.

```rust
let client = Client::new(Some(KEY.to_string()), None, None, None)?;
```
<br />

The client can be used to generate a Mistral AI embedding using the [`mistral-embed`](https://docs.mistral.ai/getting-started/models/models_overview/#premier-models) model. Since SurrealDB uses the tokio runtime, the async `.embeddings_async()` method will be used.

```rust
let input = vec!["Joram is the main character in the Darksword Trilogy.".to_string()];

let result = client.embeddings_async(MODEL, input, None).await?;
println!("{:?}", result);
```
<br />

The output in your console should include an embedding 1024 floats in length.

## Store embeddings in database

The embeddings returned from Mistral AI can now be stored in the database. The [response](https://docs.rs/mistralai-client/0.14.0/mistralai_client/v1/embedding/struct.EmbeddingResponse.html) returned from the `mistralai-client` crate looks like this, with a `Vec` of `EmbeddingResponseDataItem` structs that hold a `Vec<f32>`.

```rust
pub struct EmbeddingResponse {
    pub id: String,
    pub object: String,
    pub model: EmbedModel,
    pub data: Vec<EmbeddingResponseDataItem>,
    pub usage: ResponseUsage,
}

pub struct EmbeddingResponseDataItem {
    pub index: u32,
    pub embedding: Vec<f32>,
    pub object: String,
}
```
<br />

Using `.remove(0)` will allow us to get the raw embeddings here. In a more complex response you might opt for a match on `.get(0)` to handle any possible errors.

```rust
let embeds = result.data.remove(0).embedding;
```
<br />

There are a [number of ways](/docs/reference/rust/concepts/flexible-typing.md) to work with or avoid structs when using the Rust SDK, including creating structs: one to represent the input into a `.create()` statement, which will implement `Serialize`, and another that implements `Deserialize` to show the result.

```rust
#[derive(Serialize)]
struct DocumentInput {
    text: String,
    embedding: Vec<f32>,
}

#[derive(Debug, Deserialize)]
struct Document {
    id: RecordId,
    embedding: Vec<f32>,
    text: String,
}
```
<br />

This can be tested by printing out the created documents as a `Document` struct.

```rust
let input = "Octopuses solve puzzles and escape enclosures, showing advanced intelligence.";

let mut result = client
    .embeddings_async(MODEL, vec![input.to_string()], None)
    .await?;
let embeds = result.data.remove(0).embedding;
let in_db = db
    .create::<Option<Document>>("document")
    .content(DocumentInput {
        text: input.into(),
        embedding: embeds.to_vec(),
    })
    .await?;
println!("{in_db:?}");
```
<br />

We will now move the logic to create the embeddings into a function of its own. Since the `embeddings_async()` method takes a single `Vec<String>`, we'll first clone it to keep the original `Vec<String>`, then zip it together with the embeddings returned so that they can be put into the database along with the original input.

```rust
async fn create_embeds(
    input: Vec<String>,
    db: &Surreal<Any>,
    client: &Client,
) -> Result<(), Error> {
    let cloned = input.clone();
    let embeds = client.embeddings_async(MODEL, input, None).await?;
    let zipped = cloned
        .into_iter()
        .zip(embeds.data.into_iter().map(|item| item.embedding));

    for (text, embeds) in zipped {
        let _in_db = db
            .create::<Option<Document>>("document")
            .content(DocumentInput {
                text,
                embedding: embeds,
            })
            .await?;
    }
    Ok(())
}
```
<br />

Then we'll create four facts for each of four topics: sea creatures, Korean and Japanese cities, historical figures, and planets of the Solar System.

```rust
let embeds = [
    "Octopuses solve puzzles and escape enclosures, showing advanced intelligence.",
    "Sharks exhibit learning behavior, but their intelligence is instinct-driven.",
    "Sea cucumbers lack a brain and show minimal cognitive response.",
    "Clams have simple nervous systems with no known intelligent behavior.",
    //
    "Seoul is South Korea’s capital and a global tech hub.",
    "Sejong is South Korea’s planned administrative capital.",
    "Busan a major South Korean port located in the far southeast.",
    "Tokyo is Japan’s capital, known for innovation and dense population.",
    //
    "Wilhelm II was Germany’s last Kaiser before World War I.",
    "Cyrus the Great founded the Persian Empire with tolerant rule.",
    "Napoleon Bonaparte was a French emperor and brilliant military strategist.",
    "Aristotle was a Greek philosopher who shaped Western intellectual thought.",
    //
    "Venus’s atmosphere ranges from scorching surface to Earth-like upper clouds.",
    "Mars has a thin, cold atmosphere with seasonal dust storms.",
    "Ceres has a tenuous exosphere with sporadic water vapor traces.",
    "Saturn’s atmosphere spans cold outer layers to a deep metallic hydrogen interior",
]
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>();

create_embeds(embeds, &db, &client).await?;
```
<br />

## Semantic search

Finally let's perform semantic search over the embeddings in our database. We'll go with this query that uses the KNN operator to return the closest four matches to an embedding.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|4,COSINE|> $embeds
    ORDER BY distance;
```
<br />

To use the HNSW index instead, just change the KNN operator from `<|4,COSINE|>` to a number like `<|4,40|>`. The 40 here represents the size of the dynamic candidate list used during the search.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|4,40|> $embeds
    ORDER BY distance;
```
<br />

You can customise this [with other algorithms](/docs/learn/data-models/vector-search/similarity-search.md#computation-on-vectors-vector-package-of-functions) such as Euclidean, Hamming, and so on.

We will then put this into a separate function called `ask_question()`, which first prints out its input and then uses its embedding retrieved from Mistral AI to query the database against existing documents.

```rust
async fn ask_question(input: &str, db: &Surreal<Any>, client: &Client) -> Result<(), Error> {
    println!("{input}");
    let embeds = client
        .embeddings_async(MODEL, vec![input.to_string()], None)
        .await?
        .data
        .remove(0)
        .embedding;

    let mut response = db.query("SELECT text, vector::distance::knn() AS distance FROM document WHERE embedding <|4,COSINE|> $embeds ORDER BY distance;").bind(("embeds", embeds)).await?;
    let as_val: Value = response.take(0)?;
    println!("{as_val}\n");
    Ok(())
}
```
<br />

This function can now be called inside `main()` to confirm that the results match with our expectations.

```rust
ask_question("Which Korean city is just across the sea from Japan?", &db, &client).await?;
ask_question("Who was Germany's last Kaiser?", &db, &client).await?;
ask_question("Which sea animal is most intelligent?", &db, &client).await?;
ask_question("Which planet's atmosphere has a part with the same temperature as Earth?", &db, &client).await?;
```
<br />

```text
Which Korean city is just across the sea from Japan?
[{ distance: 0.19170371029549582f, text: 'Busan is a major South Korean port located in the far southeast.' }, { distance: 0.2399314515762122f, text: 'Tokyo is Japan’s capital, known for innovation and dense population.' }, { distance: 0.2443623703771407f, text: 'Sejong is South Korea’s planned administrative capital.' }, { distance: 0.24488082839731895f, text: 'Seoul is South Korea’s capital and a global tech hub.' }]

Who was Germany's last Kaiser?
[{ distance: 0.11228576780228805f, text: 'Wilhelm II was Germany’s last Kaiser before World War I.' }, { distance: 0.2957177300085634f, text: 'Napoleon Bonaparte was a French emperor and brilliant military strategist.' }, { distance: 0.34394473621670896f, text: 'Cyrus the Great founded the Persian Empire with tolerant rule.' }, { distance: 0.34911517400935843f, text: 'Sejong is South Korea’s planned administrative capital.' }]

Which sea animal is most intelligent?
[{ distance: 0.2342596053829904f, text: 'Octopuses solve puzzles and escape enclosures, showing advanced intelligence.' }, { distance: 0.24131327939924785f, text: 'Sharks exhibit learning behavior, but their intelligence is instinct-driven.' }, { distance: 0.2426242772516931f, text: 'Clams have simple nervous systems with no known intelligent behavior.' }, { distance: 0.24474598154128135f, text: 'Sea cucumbers lack a brain and show minimal cognitive response.' }]

Which planet's atmosphere has a part with the same temperature as Earth?
[{ distance: 0.20653440713083582f, text: 'Venus’s atmosphere ranges from scorching surface to Earth-like upper clouds.' }, { distance: 0.23354208810464594f, text: 'Mars has a thin, cold atmosphere with seasonal dust storms.' }, { distance: 0.24560810032473468f, text: 'Saturn’s atmosphere spans cold outer layers to a deep metallic hydrogen interior' }, { distance: 0.2761595357544341f, text: 'Ceres has a tenuous exosphere with sporadic water vapor traces.' }]
```
<br />

Here is the entire code:

```rust
use std::sync::LazyLock;

use anyhow::Error;
use mistralai_client::v1::{client::Client, constants::EmbedModel};
use serde::{Deserialize, Serialize};
use surrealdb::{
    RecordId, Surreal, Value,
    engine::any::{Any, connect},
};

static KEY: LazyLock<String> = LazyLock::new(|| std::env::var("MISTRAL_API_KEY").unwrap());

// Experiment plan
const MODEL: EmbedModel = EmbedModel::MistralEmbed;

#[derive(Serialize)]
struct DocumentInput {
    text: String,
    embedding: Vec<f32>,
}

#[derive(Debug, Deserialize)]
struct Document {
    id: RecordId,
    embedding: Vec<f32>,
    text: String,
}

async fn create_embeds(
    input: Vec<String>,
    db: &Surreal<Any>,
    client: &Client,
) -> Result<(), Error> {
    let cloned = input.clone();
    let embeds = client.embeddings_async(MODEL, input, None).await?;
    let zipped = cloned
        .into_iter()
        .zip(embeds.data.into_iter().map(|item| item.embedding));

    for (text, embeds) in zipped {
        let _in_db = db
            .create::<Option<Document>>("document")
            .content(DocumentInput {
                text,
                embedding: embeds,
            })
            .await?;
    }
    Ok(())
}

async fn ask_question(input: &str, db: &Surreal<Any>, client: &Client) -> Result<(), Error> {
    println!("{input}");
    let embeds = client
        .embeddings_async(MODEL, vec![input.to_string()], None)
        .await?
        .data
        .remove(0)
        .embedding;

    let mut response = db.query("SELECT text, vector::distance::knn() AS distance FROM document WHERE embedding <|4,COSINE|> $embeds ORDER BY distance;").bind(("embeds", embeds)).await?;
    let as_val: Value = response.take(0)?;
    println!("{as_val}\n");
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let db = connect("memory").await.unwrap();

    db.use_ns("ns").use_db("db").await.unwrap();

    let mut res = db
        .query(
            "DEFINE TABLE document;
             DEFINE FIELD text ON document TYPE string;
             DEFINE FIELD embedding ON document TYPE array<float>;
             DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1024 DIST COSINE;",
        )
        .await
        .unwrap();
    for (index, error) in res.take_errors() {
        println!("Error in query {index}: {error}");
    }

    let client = Client::new(Some(KEY.to_string()), None, None, None)?;

    let embeds = [
        "Octopuses solve puzzles and escape enclosures, showing advanced intelligence.",
        "Sharks exhibit learning behavior, but their intelligence is instinct-driven.",
        "Sea cucumbers lack a brain and show minimal cognitive response.",
        "Clams have simple nervous systems with no known intelligent behavior.",
        //
        "Seoul is South Korea’s capital and a global tech hub.",
        "Sejong is South Korea’s planned administrative capital.",
        "Busan is a major South Korean port located in the far southeast.",
        "Tokyo is Japan’s capital, known for innovation and dense population.",
        //
        "Wilhelm II was Germany’s last Kaiser before World War I.",
        "Cyrus the Great founded the Persian Empire with tolerant rule.",
        "Napoleon Bonaparte was a French emperor and brilliant military strategist.",
        "Aristotle was a Greek philosopher who shaped Western intellectual thought.",
        //
        "Venus’s atmosphere ranges from scorching surface to Earth-like upper clouds.",
        "Mars has a thin, cold atmosphere with seasonal dust storms.",
        "Ceres has a tenuous exosphere with sporadic water vapor traces.",
        "Saturn’s atmosphere spans cold outer layers to a deep metallic hydrogen interior",
    ]
    .into_iter()
    .map(|s| s.to_string())
    .collect::<Vec<String>>();

    create_embeds(embeds, &db, &client).await?;

    ask_question("Which Korean city is just across the sea from Japan?", &db, &client).await?;
    ask_question("Who was Germany's last Kaiser?", &db, &client).await?;
    ask_question("Which sea animal is most intelligent?", &db, &client).await?;
    ask_question("Which planet's atmosphere has a part with the same temperature as Earth?", &db, &client).await?;

    Ok(())
}
```

---

Source: https://surrealdb.com/docs/build/integrations/embeddings-providers/openai

# OpenAI

This section contains information about the OpenAI embeddings feature of SurrealDB.

**Python**

SurrealDB provides a number of different embeddings features that can be used to manage your data. This walkthrough shows how to embed your text with OpenAI, store the vectors in SurrealDB, and run fast k‑nearest‑neighbour (KNN) searches, all from Python. It follows the same flow you might have seen for Qdrant, but swaps in SurrealDB’s native vector‑search features so you can keep documents, graphs, and embeddings in one place.

## Install the two clients

OpenAI calls the API that turns text into a 1 536‑dimensional vector, while SurrealDB lets your Python code connect over WebSocket or HTTP to any SurrealDB server.

SurrealDB must be running somewhere. If you do not have SurrealDB running already you can spin up an in‑memory node in one line:

```bash
pip install openai surrealdb   
```

For local testing you can spin it up quickly:

```bash
docker run -p 8000:8000 surrealdb/surrealdb:latest start --user root --pass secret
```

## Set up the OpenAI and SurrealDB clients

```python
import asyncio
import openai
from surrealdb import Surreal

openai_client = openai.Client(api_key="YOUR_OPENAI_KEY")

TEXTS = [
    "SurrealDB is a multi-model database you can embed anywhere!",
    "Loved by devs who need graphs, documents **and** vector search in one box.",
]

async def init_db() -> Surreal:
    db = Surreal("ws://localhost:8000/rpc")          # change URL if remote
    await db.signin({"username": "root", "password": "secret"})
    await db.use("demo_ns", "demo_db")
    return db
```

## Create the embeddings with OpenAI

`text‑embedding‑3‑small` is compact, cheap, and surprisingly accurate for semantic search. If you need more nuance (or plan to quantise heavily) you can upgrade to `text‑embedding‑3‑large` (3 072 dimensions) at roughly twice the cost.

```python
EMBED_MODEL = "text-embedding-3-small"

emb_resp = openai_client.embeddings.create(
    input=TEXTS,
    model=EMBED_MODEL
)
emb_vectors = [row.embedding for row in emb_resp.data]   # list[list[float]]
```

## Insert the documents into SurrealDB

SurrealDB is schemaless by default, so you can drop JSON directly into a table named article:

*(table `article` with a vector field called `embedding`)*

```python
async def load_documents(db: Surreal):
    docs = [
        {"text": txt, "embedding": vec}
        for txt, vec in zip(TEXTS, emb_vectors)
    ]
    await db.create("article", docs)

asyncio.run(load_documents(asyncio.run(init_db())))
```

## Add a vector index (HNSW)

The Hierarchical Navigable Small World (HNSW) index trades a tiny bit of accuracy for dramatic speed‑ups compared with brute-force scanning; ideal once your table grows beyond a few thousand records. Because HNSW lives in RAM today, index creation is instant but counts against your memory budget.

SurrealQL lets you add the index once; after that inserts are indexed automatically.

```python
async def add_index(db: Surreal):
    await db.query("""
        DEFINE INDEX article_embedding_hnsw 
        ON article
        FIELDS embedding
        HNSW DIMENSION 1536;
    """)

asyncio.run(add_index(asyncio.run(init_db())))
```

The `DIMENSION` must match the length of the OpenAI vectors (1536 in this model). ([SurrealDB][1])

## Search for the most relevant documents

```python
async def semantic_search(question: str, k: int = 3):
    query_vec = openai_client.embeddings.create(
        input=[question],
        model=EMBED_MODEL
    ).data[0].embedding

    db = await init_db()

    # KNN search: <|k, ef|> - ef is “search breadth”. 100 is a decent default.
    result = await db.query(`
        LET $q := {query_vec};

        SELECT id, text,
               vector::distance::knn() AS distance
        FROM article
        WHERE embedding <|{k},100|> $q
        ORDER BY distance;
    """)
    return result[0]['result']

hits = asyncio.run(semantic_search("What’s the best database for unified search?"))
for hit in hits:
    print(hit["distance"], hit["text"])
```

The `vector::distance::knn()` helper returns the exact distance that the KNN operator just computed, so SurrealDB can immediately sort or filter without recalculating.

Two knobs to remember:

- `k` - the number of neighbours you want back.
- `ef` (the second number) - search breadth. Higher values spend more CPU for slightly better accuracy; 100-200 is a safe default.

## (Optional) Binary-quantise the vectors

SurrealDB stores vectors as arrays of floats (`F32` by default) and doesn’t yet expose built-in binary quantisation the way Qdrant does.
If you need ultra-compact storage you can:

1. **Quantise offline** (e.g. convert to `int32` with `numpy`).
2. Store the compressed list in a separate field (`embedding_q32`) and build an index on that instead:

```sql
DEFINE INDEX article_embedding_q32_mt
ON article
FIELDS embedding_q8
HNSW DIMENSION 1536 TYPE I32;
```

This keeps the SurrealDB side simple while you experiment with different quantisers. For more information about vector search in SurrealDB, see the [Vector Search Reference guides](/docs/learn/data-models/vector-search/overview.md).

**Rust**

## Semantic search using SurrealDB

## Intro

This guide demonstrates how to store OpenAI embeddings as [SurrealDB vectors](/docs/learn/data-models/vector-search/overview.md) via the Rust SDK for the purposes of semantic search.

## Setup

First set up a new Cargo project with `cargo new project_name` and add the following dependencies to `Cargo.toml`:

```toml
anyhow = "1.0.98"
async-openai = "0.28.3"
serde = "1.0.228"
surrealdb = { version = "2.3", features = ["kv-mem"] }
tokio = "1.49.0"
```
<br />

Inside `main()`, call the `connect` function with `"memory"` to instantiate an embedded database in memory.

```rust
use anyhow::Error;
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() -> Result<(), Error> {
    let db = connect("memory").await?;
    Ok(())
}
```
<br />

If you have a Cloud or local instance to connect to, you can pass that path into the connect function instead.

```rust
// Cloud address
let db = connect("wss://myinstance-06a4h41t12rtj7lsg45m3prm1k.aws-use1.surreal.cloud").await?;

// Local address
let db = connect("ws://localhost:8000").await?;
```
<br />

After connecting, select a namespace and database name, such as `ns` and `db`.

```rust
db.use_ns("ns").use_db("db").await?;
```
<br />

## Create a vector table

Create a table to store documents and embeddings, along with an index for the embeddings:

```surql
DEFINE TABLE document;
DEFINE FIELD text ON document TYPE string;
DEFINE FIELD embedding ON document TYPE array<float>;
DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1536;
```
<br />

These can be called via a single `.query()` method.

```rust
let mut res = db
    .query(
        "DEFINE TABLE document;
DEFINE FIELD text ON document TYPE string;
DEFINE FIELD embedding ON document TYPE array<float>;
DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1536;",
    )
    .await?;
```
<br />

The size of the vector (1536 here) represents the number of dimensions in the embedding. Since OpenAI's `text-embedding-3-small` model in this example uses [1536 as its default length](https://platform.openai.com/docs/guides/embeddings), the vector size must be set to 1536.

The [HNSW index](/docs/learn/data-models/vector-search/vector-indexes.md) is not strictly necessary to use the KNN operator (`<||>`) to find an embedding's closest neighbours, and for our small sample code we will use the simple [brute force method](/docs/reference/query-language/language-primitives/operators.md#brute-force-method) which chooses [an algorithm](/docs/learn/data-models/vector-search/similarity-search.md#computation-on-vectors-vector-package-of-functions) such as Euclidean, Hamming, and so on. The following is the code that we will use, which uses the cosine of an embedding to find the four closest neighbours.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|2,COSINE|> $embeds
    ORDER BY distance;
```

As the dataset grows, if some loss of accuracy is acceptable then the syntax can be changed to use [the HNSW index](/docs/reference/query-language/language-primitives/operators.md#hnsw-method), by replacing an algorithm with a number that represents the size of the dynamic candidate list.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|2,40|> $embeds
    ORDER BY distance;
```

## Generate OpenAI embeddings

At this point, you will need an [OpenAI API key](https://platform.openai.com/api-keys) to interact with the OpenAI API.

<br />

The best way to set the key is as an environment variable, `OPENAI_API_KEY` in this case. Using a `LazyLock` will let us call it via `std::env::var()` function the first time it is accessed. You can of course simply put it into a `const` for simplicity when first testing, but always remember to never hard-code API keys in your code in production.

```rust
static KEY: LazyLock<String> = LazyLock::new(|| {
    std::env::var("OPENAI_API_KEY").unwrap()
});
```
<br />

And then run the code like this:

```bash
OPENAI_API_KEY=whateverthekeyis cargo run
```
<br />

Or like this if you are using PowerShell on Windows.

```powershell
$env:OPENAI_API_KEY = "whateverthekeyis"
cargo run
```
<br />

Inside `main()`, [create a client](https://docs.rs/async-openai/0.28.3/async_openai/struct.Client.html) from the async-openai crate holding this config inside `main()`.

```rust
let config = OpenAIConfig::new().with_api_key(KEY);
let client = Client::with_config(config);
```
<br />

Then use it to generate an OpenAI embedding with [`text-embedding-3-small`](https://platform.openai.com/docs/guides/embeddings/embedding-models), and print it with a `println!` statement.

```rust
let input = "Octopuses solve puzzles and escape enclosures, showing advanced intelligence.";

let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
let result = client.embeddings().create(request).await?;
println!("{result:?}");
```
<br />

## Store embeddings in database

With the embeddings returned from the OpenAI client, they can be stored in the database. The [response](https://docs.rs/async-openai/0.28.3/async_openai/types/struct.CreateEmbeddingResponse.html) returned from the async-openai crate looks like this, with a `Vec` of `Embedding` structs that hold a `Vec<f32>`.

```rust
pub struct CreateEmbeddingResponse {
    pub object: String,
    pub model: String,
    pub data: Vec<Embedding>,
    pub usage: EmbeddingUsage,
}

pub struct Embedding {
    pub index: u32,
    pub object: String,
    pub embedding: Vec<f32>,
}
```
<br />

This simple request only returned a single embedding, so `.remove(0)` will do the job. In a more complex codebase you would probably opt for a match on `.get(0)` to handle any possible errors.

```rust
let embeds = result.data.remove(0).embedding;
```
<br />

Two structs can be put together here: one that implements `Serialize` to serve as the input put in a `.create()` statement, and another that implements `Deserialize` to show the result with the `RecordId` included.

```rust
#[derive(Serialize)]
struct DocumentInput {
    text: String,
    embedding: Vec<f32>,
}

#[derive(Debug, Deserialize)]
struct Document {
    id: RecordId,
    embedding: Vec<f32>,
    text: String,
}
```
<br />

Once that is done, we can print out the created documents as a `Document` struct.

```rust
let in_db = db
    .create::<Option<Document>>("document")
    .content(DocumentInput {
        text: input.into(),
        embedding: embeds.to_vec()
    })
    .await?;
println!("{in_db:?}");
```
<br />

We should now add some more `document` records. To do this, we'll move the logic to create them inside a function of its own:

```rust
async fn create_embed(
    input: &str,
    db: &Surreal<Any>,
    client: &Client<OpenAIConfig>,
) -> Result<(), Error> {
    let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
    let result = client.embeddings().create(request).await?;

    let embeds = &result.data.get(0).unwrap().embedding;

    let _in_db = db
        .create::<Option<Document>>("document")
        .content(DocumentInput {
            text: input.into(),
            embedding: embeds.to_vec(),
        })
        .await?;
    Ok(())
}
```
<br />

And then call it a few times inside `main()`. See if you can guess the answers yourself!

```rust
for input in [
    "Octopuses solve puzzles and escape enclosures, showing advanced intelligence.",
    "Sharks are primarily driven by instinct, but are capable of learning.",
    "Sea cucumbers lack a brain and show minimal cognitive response.",
    "Clams have simple nervous systems with no known intelligent behavior.",
    //
    "Seoul is South Korea’s capital and a global tech hub.",
    "Sejong is South Korea’s planned administrative capital.",
    "Busan a major South Korean port located in the far southeast.",
    "Tokyo is Japan’s capital, known for innovation and dense population.",
    //
    "Wilhelm II was Germany’s last Kaiser before World War I.",
    "Cyrus the Great founded the Persian Empire with tolerant rule.",
    "Napoleon Bonaparte was a French emperor and brilliant military strategist.",
    "Aristotle was a Greek philosopher who shaped Western intellectual thought.",
    //
    "Venus’s atmosphere ranges from scorching surface to Earth-like upper clouds.",
    "Mars has a thin, cold atmosphere with seasonal dust storms.",
    "Ceres has a tenuous exosphere with sporadic water vapor traces.",
    "Saturn’s atmosphere spans cold outer layers to a deep metallic hydrogen interior",
] {
    create_embed(input, &db, &client).await?
}
```
<br />

## Semantic search

Finally, let's perform semantic search over the embeddings in our database. Here is the query again:

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|2,COSINE|> $embeds
    ORDER BY distance;
```
<br />

We will then put this into a separate function called `ask_question()` which uses the embedding retrieved from OpenAI to query the database against existing documents.

```rust
async fn ask_question(
    input: &str,
    db: &Surreal<Any>,
    client: &Client<OpenAIConfig>,
) -> Result<(), Error> {
    println!("{input}");
    let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
    let mut result = client.embeddings().create(request).await?;

    let embeds = result.data.remove(0).embedding;

    let mut response = db.query("SELECT text, vector::distance::knn() AS distance FROM document WHERE embedding <|2,COSINE|> $embeds ORDER BY distance;").bind(("embeds", embeds)).await?;
    let as_val: Value = response.take(0)?;
    println!("{as_val}\n");
    Ok(())
}
```
<br />

You can now call this function a few times inside `main()` to confirm that the results are what we expect them to be.

```rust
ask_question("Which Korean city is just across the sea from Japan?", &db, &client).await?;
ask_question("Who was Germany's last Kaiser?", &db, &client).await?;
ask_question("Which sea animal is most intelligent?", &db, &client).await?;
ask_question("Which planet's atmosphere has a part with the same temperature as Earth?", &db, &client).await?;
```
<br />

The output shows that the closest documents to our question do indeed show up first.

```text
Which Korean city is just across the sea from Japan?
[{ distance: 0.4879310782198243f, text: 'Busan a major South Korean port located in the far southeast.' }, { distance: 0.572999190509329f, text: 'Seoul is South Korea’s capital and a global tech hub.' }]

Who was Germany's last Kaiser?
[{ distance: 0.3236345624131668f, text: 'Wilhelm II was Germany’s last Kaiser before World War I.' }, { distance: 0.7554141523606017f, text: 'Napoleon Bonaparte was a French emperor and brilliant military strategist.' }]

Which sea animal is most intelligent?
[{ distance: 0.45382501257446206f, text: 'Octopuses solve puzzles and escape enclosures, showing advanced intelligence.' }, { distance: 0.4951347026545868f, text: 'Clams have simple nervous systems with no known intelligent behavior.' }]

Which planet's atmosphere has a part with the same temperature as Earth?
[{ distance: 0.4445578407153489f, text: 'Venus’s atmosphere ranges from scorching surface to Earth-like upper clouds.' }, { distance: 0.5039940919211086f, text: 'Saturn’s atmosphere spans cold outer layers to a deep metallic hydrogen interior' }]
```
<br />

At this point, you could give the HNSW index a try by changing the `<|2,COSINE|>` in the query to something like `<|2,40|>`. The distance numbers will end up looking quite different, but the ordering of the closest neighbours will probably be the same in this small example.

<br />

Here is the final code:

```rust
use std::sync::LazyLock;

use anyhow::Error;
use async_openai::{Client, config::OpenAIConfig, types::CreateEmbeddingRequestArgs};
use serde::{Deserialize, Serialize};
use surrealdb::{
    RecordId, Surreal, Value,
    engine::any::{Any, connect},
};

static KEY: LazyLock<String> = LazyLock::new(|| std::env::var("OPENAI_API_KEY").unwrap());

#[derive(Serialize)]
struct DocumentInput {
    text: String,
    embedding: Vec<f32>,
}

#[derive(Debug, Deserialize)]
struct Document {
    id: RecordId,
    embedding: Vec<f32>,
    text: String,
}

async fn create_embed(
    input: &str,
    db: &Surreal<Any>,
    client: &Client<OpenAIConfig>,
) -> Result<(), Error> {
    let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
    let mut result = client.embeddings().create(request).await?;

    let embeds = result.data.remove(0).embedding;

    let _in_db = db
        .create::<Option<Document>>("document")
        .content(DocumentInput {
            text: input.into(),
            embedding: embeds.to_vec(),
        })
        .await?;
    Ok(())
}

async fn ask_question(
    input: &str,
    db: &Surreal<Any>,
    client: &Client<OpenAIConfig>,
) -> Result<(), Error> {
    println!("{input}");
    let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
    let mut result = client.embeddings().create(request).await?;

    let embeds = result.data.remove(0).embedding;

    let mut response = db.query("SELECT text, vector::distance::knn() AS distance FROM document WHERE embedding <|2,COSINE|> $embeds ORDER BY distance;").bind(("embeds", embeds)).await?;
    let as_val: Value = response.take(0)?;
    println!("{as_val}\n");
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let db = connect("memory").await?;

    db.use_ns("ns").use_db("db").await?;

    let mut res = db
        .query(
            "DEFINE TABLE document;
             DEFINE FIELD text ON document TYPE string;
             DEFINE FIELD embedding ON document TYPE array<float>;
             DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1536 DIST COSINE;",
        )
        .await?;
    for (index, error) in res.take_errors() {
        println!("Error in query {index}: {error}");
    }

    let config = OpenAIConfig::new().with_api_key(&*KEY);

    let client = Client::with_config(config);

    for input in [
        "Octopuses solve puzzles and escape enclosures, showing advanced intelligence.",
        "Sharks are primarily driven by instinct, but are capable of learning.",
        "Sea cucumbers lack a brain and show minimal cognitive response.",
        "Clams have simple nervous systems with no known intelligent behavior.",
        //
        "Seoul is South Korea’s capital and a global tech hub.",
        "Sejong is South Korea’s planned administrative capital.",
        "Busan a major South Korean port located in the far southeast.",
        "Tokyo is Japan’s capital, known for innovation and dense population.",
        //
        "Wilhelm II was Germany’s last Kaiser before World War I.",
        "Cyrus the Great founded the Persian Empire with tolerant rule.",
        "Napoleon Bonaparte was a French emperor and brilliant military strategist.",
        "Aristotle was a Greek philosopher who shaped Western intellectual thought.",
        //
        "Venus’s atmosphere ranges from scorching surface to Earth-like upper clouds.",
        "Mars has a thin, cold atmosphere with seasonal dust storms.",
        "Ceres has a tenuous exosphere with sporadic water vapor traces.",
        "Saturn’s atmosphere spans cold outer layers to a deep metallic hydrogen interior",
    ] {
        create_embed(input, &db, &client).await?
    }

    ask_question(
        "Which Korean city is just across the sea from Japan?",
        &db,
        &client,
    )
    .await?;

    ask_question("Which Korean city is just across the sea from Japan?", &db, &client).await?;
    ask_question("Who was Germany's last Kaiser?", &db, &client).await?;
    ask_question("Which sea animal is most intelligent?", &db, &client).await?;
    ask_question("Which planet's atmosphere has a part with the same temperature as Earth?", &db, &client).await?;

    Ok(())
}
```

---

Source: https://surrealdb.com/docs/build/integrations/embeddings-providers/overview

# Embeddings provider integrations

This section contains information about different LLM models you can use with SurrealDB.

SurrealDB supports vector embeddings for semantic search and machine learning across your data. With integrations for the main embedding providers, you can store, index and query high-dimensional vectors alongside your regular data.

- [Quick start with Python](/docs/build/integrations/embeddings-providers/python-quickstart.md) - LangChain, Ollama, Mistral, and more

- [Quick start with Rust](/docs/build/integrations/embeddings-providers/rust-quickstart.md) - Mistral, Ollama, and more

## Full examples

<table>
  <tbody>
    <tr>
      <td><a href="/docs/build/integrations/embeddings-providers/mistral.md">Mistral</a></td>
      <td>Mistral AI's embedding model and SurrealDB vector search (Python and Rust)</td>
    </tr>
    <tr>
      <td><a href="/docs/build/integrations/embeddings-providers/openai.md">OpenAI</a></td>
      <td>OpenAI's embedding service and SurrealDB vector search (Python and Rust)</td>
    </tr>
  </tbody>
</table>

---

Source: https://surrealdb.com/docs/build/integrations/embeddings-providers/python-quickstart

# Python quickstart

This section contains information about different embedding models you can use with SurrealDB.

SurrealDB supports vector embeddings for semantic search and machine learning across your data. With integrations for the main embedding providers, you can store, index and query high-dimensional vectors alongside your regular data.

**LangChain**

More details and providers in [LangChain Embedding models](https://python.langchain.com/docs/integrations/text_embedding/) documentation.

**Ollama**

## Ollama

```python
from langchain_ollama import OllamaEmbeddings

vector_store = SurrealDBVectorStore(
    OllamaEmbeddings(model="all-minilm:22m"),
    conn
)
```

More [Ollama embedding models](https://ollama.com/search?c=embedding) in their documentation.

**OpenAI**

## OpenAI

Requires `OPENAI_API_KEY` environment variable.

```python
from langchain_openai import OpenAIEmbeddings

vector_store = SurrealDBVectorStore(
    OpenAIEmbeddings(model="text-embedding-3-large"),
    conn
)
```

**Mistral**

## Mistral

Requires `MISTRALAI_API_KEY` environment variable.

```python
from langchain_mistralai import MistralAIEmbeddings

vector_store = SurrealDBVectorStore(
    MistralAIEmbeddings(model="mistral-embed"),
    conn
)
```

**SentenceTransformer**

## SentenceTransformer

```python
from langchain_huggingface import HuggingFaceEmbeddings

vector_store = SurrealDBVectorStore(
    HuggingFaceEmbeddings(
        model_name="sentence-transformers/all-MiniLM-L6-v2"
    ),
    conn
)
```

More [SentenceTransformer models](https://www.sbert.net/docs/sentence_transformer/pretrained_models.html) in their documentation.

**AWS Bedrock**

## AWS Bedrock

```python
from langchain_aws import BedrockEmbeddings

vector_store = SurrealDBVectorStore(
    BedrockEmbeddings(model_id="amazon.titan-embed-text-v2:0"),
    conn
)
```

**Gemini**

## Gemini

Requires `GOOGLE_API_KEY` environment variable.

```python
from langchain_google_genai import GoogleGenerativeAIEmbeddings

vector_store = SurrealDBVectorStore(
    GoogleGenerativeAIEmbeddings(model="models/embedding-001"),
    conn
)
```

<br />

Then, to query the vector store using similarity search:

```python
doc1 = Document(
    page_content="SurrealDB is the ultimate multi-model database for AI applications",
    metadata={"key": "sdb"},
)
doc2 = Document(
    page_content="Surrealism is an artistic and cultural movement that emerged in the early 20th century",
    metadata={"key": "surrealism"},
)
vector_store.add_documents(documents=[doc1, doc2], ids=["1", "2"])

results = vector_store.similarity_search_with_score(query=q, k=2)
for doc, score in results:
    print(f"• [{score:.0%}]: {doc.page_content}")
top_match = results[0][0]
```

Find an example in [Minimal LangChain chatbot example with vector and graph](/blog/minimal-langchain-chatbot-example-with-vector-and-graph).

**Vanilla**

**Ollama**

## Ollama

```python
import ollama

embedding = ollama.embed(model="all-minilm:22m", input=text)
conn.create("documents", { "content": text, "embedding": embedding })
```

More [Ollama embedding models](https://ollama.com/search?c=embedding) in their documentation.

**OpenAI**

## OpenAI

```python
from openai import OpenAI
client = OpenAI()

text = "Your text string"
response = client.embeddings.create(
    input=text,
    model="text-embedding-3-small"
)

conn.create("documents", { "content": text, "embedding": response.data[0].embedding })
```

More info in [OpenAI embeddings](https://platform.openai.com/docs/guides/embeddings?lang=python) documentation.

**Sentence Transformers**

## Sentence Transformers

```python
from sentence_transformers import SentenceTransformer

st = SentenceTransformer("all-MiniLM-L6-v2")
embedding = st.encode(text).tolist()
conn.create("documents", { "content": text, "embedding": embedding })
```

More [SentenceTransformer models](https://www.sbert.net/docs/sentence_transformer/pretrained_models.html) in their documentation.

To query the vector store using similarity search:

```python
k = 5
query_embedding = st.encode("this is your query text")
res = conn.query(
    f"""
    SELECT
        *,
        vector::distance::knn() AS dist
    FROM documents
    WHERE embedding <|{k}|> $vector;
    """,
    {
        "vector": query_embedding.tolist(),
    },
)
```

This requires an index to be created beforehand. Refer to the [vector search cheat sheet](/docs/learn/data-models/vector-search/vector-indexes.md#vector-search-cheat-sheet).

<br />

Examples above assume you have a DB connection like this:

```python
conn = Surreal("localhost")
conn.signin({"username": "root", "password": "secret"})
conn.use("test_ns", "test_db")
```

---

Source: https://surrealdb.com/docs/build/integrations/embeddings-providers/rust-quickstart

# Rust quickstart

This section contains information about different embedding models you can use with SurrealDB.

SurrealDB supports vector embeddings for semantic search and machine learning across your data. With integrations for the main embedding providers, you can store, index and query high-dimensional vectors alongside your regular data.

**Mistral**

## Mistral

```rust
use mistralai_client::v1::{client::Client, constants::EmbedModel};

static KEY = std::env::var("MISTRAL_API_KEY").unwrap();

// ...

let client = Client::new(Some(KEY.to_string()), None, None, None)?;
let input = vec!["Joram is the main character in the Darksword Trilogy.".to_string()];

let result = client.embeddings_async(MODEL, input, None).await?;
println!("{:?}", result);
```

Find a full example in [Semantic search in Rust with SurrealDB and Mistral AI](/blog/semantic-search-in-rust-with-surrealdb-and-mistral-ai#generate-mistral-ai-embeddings).

**Ollama**

## Ollama

```rust
use ollama_rs::{Ollama, generation::embeddings::GenerateEmbeddingsRequest};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ollama = Ollama::default();

    let model = "all-minilm:22m".to_string()
    let prompt = "this is your input text".to_string();

    let request = GenerateEmbeddingsRequest::new(model, prompt);
    let response = ollama.generate_embeddings(request).await?;

    println!("Generated embeddings (first 5): {:?}", &response.embeddings[..5]);
    println!("Embedding vector length: {}", response.embeddings.len());

    Ok(())
}
```

**SentenceTransformer**

## SentenceTransformer

```rust
use rust_bert::sentence_embeddings::{
    SentenceEmbeddingsBuilder, SentenceEmbeddingsModelType,
};

fn main() -> anyhow::Result<()> {
    // Set up the model builder, specifying the model type
    let model = SentenceEmbeddingsBuilder::remote(
        SentenceEmbeddingsModelType::AllMiniLmL6V2
    ).create_model()?;

    // Define the sentences to embed
    let sentences = [
        "this is your text",
        "you can encode more than one in batch"
    ];

    // Generate the embeddings
    let embeddings = model.encode(&sentences)?;

    // Print the results
    for (i, embedding) in embeddings.iter().enumerate() {
        // Truncate for display purposes
        let truncated_embedding: Vec<_> = embedding.iter().take(5).cloned().collect();

        println!("\nSentence: '{}'", sentences[i]);
        println!("Embedding (first 5 values): {:?}", truncated_embedding);
        println!("Embedding dimensions: {}", embedding.len());
    }

    Ok(())
}
```

---

Source: https://surrealdb.com/docs/build/integrations/vercel

# Vercel

Use SurrealDB with Vercel - Next.js apps, the AI SDK, eve agents and Vercel Sandbox - with connection, credential and runtime guidance that applies to all four.

These pages show how to use SurrealDB with Vercel's frameworks and compute: Next.js applications, the AI SDK, agents built with eve, and Vercel Sandbox. Each page uses the [`surrealdb`](/docs/reference/javascript.md) JavaScript SDK directly, so no adapter package is needed.

> [!NOTE]
> This section is about running your own SurrealDB database. For managed long-term memory for agents, see [SurrealDB Agent Memory](/docs/agent-memory.md), which has its own [AI SDK](/docs/agent-memory/integrations/ai-sdks/vercel-ai-sdk.md) and [eve](/docs/agent-memory/integrations/frameworks/eve.md) integrations.

## Choose a page

| Page | Use it to |
| --- | --- |
| [Next.js](/docs/build/integrations/vercel/nextjs.md) | Read and write data from Server Components, Server Actions and Route Handlers, and stream live updates to the browser. |
| [AI SDK](/docs/build/integrations/vercel/ai-sdk.md) | Store embeddings, give a model search and graph tools, and keep chat history in SurrealDB. |
| [Eve](/docs/build/integrations/vercel/eve.md) | Give an eve agent tools that query your data, and back an eve memory slot with SurrealDB. |
| [Vercel Sandbox](/docs/build/integrations/vercel/vercel-sandbox.md) | Start a temporary SurrealDB server inside a sandbox, or let sandboxed code query your database with limited access. |

## Where the database runs

Vercel Functions and sandboxes run your code, but they do not keep data between invocations. Run SurrealDB as a separate server, either on [SurrealDB Cloud](/docs/running/cloud.md) or on infrastructure that you manage, and connect to it from Vercel. Place the database in or near the region where your functions run, because every query is a network round trip.

A sandbox can run a SurrealDB server for a short time, which is useful for tests and for scratch data that an agent creates. The [Vercel Sandbox](/docs/build/integrations/vercel/vercel-sandbox.md) page describes when that is appropriate.

## Store credentials as environment variables

The examples in this section read the connection details from these environment variables:

| Variable | Example value |
| --- | --- |
| `SURREALDB_URL` | `https://<your-instance>.aws.surrealdb.cloud` |
| `SURREALDB_NAMESPACE` | `app` |
| `SURREALDB_DATABASE` | `main` |
| `SURREALDB_USER` | `app_server` |
| `SURREALDB_PASS` | The password of that user |

Add them to the Vercel project under **Settings** > **Environment Variables**, or with the Vercel CLI:

```sh
vercel env add SURREALDB_PASS
```

To use the same values on your computer, link the project and pull the variables into `.env.local`:

```sh
vercel link
vercel env pull
```

Give the application its own [database-level user](/docs/learn/security/authentication/users.md#system-users) rather than the root user. A user defined `ON DATABASE` can only reach that one database. The `EDITOR` role lets it read and write data in that database, but not manage users or access methods:

```surql
DEFINE USER app_server ON DATABASE PASSWORD 'strongPassword' ROLES EDITOR;
```

A user defined on the database signs in with the namespace and database as well as the username and password. The connection examples in this section pass both.

## Choose HTTP or WebSocket

The JavaScript SDK connects over HTTP when the URL starts with `http://` or `https://`, and over WebSocket when it starts with `ws://` or `wss://`.

- **HTTP** sends each query as its own request. Nothing has to stay open between invocations, which suits functions that can be paused or stopped at any time. Use it for server-side code on Vercel.
- **WebSocket** keeps one connection open and is required for [live queries](/docs/reference/javascript/concepts/live-queries.md). Use it in the browser, where the page stays open, or in a long-running process.

[Connecting from serverless and edge](/docs/learn/querying/concepts-and-guides/connecting-from-serverless-and-edge.md) explains the trade-offs in more detail, including token authentication and batching.

## Create the namespace and database

SurrealDB returns an error such as `The namespace 'app' does not exist` when a client selects a namespace or database that has not been defined. Start a schema file with the definitions, so the file works on an empty server:

```surql title="schema.surql"
DEFINE NAMESPACE IF NOT EXISTS app;
USE NS app;
DEFINE DATABASE IF NOT EXISTS main;
USE DB main;

-- Tables, fields and indexes follow
```

Send the file to the `/sql` endpoint of the server. The pages in this section use this command to apply their schemas:

```sh
curl -X POST -u "root:secret" -H "Accept: application/json" \
  --data-binary @schema.surql http://localhost:8000/sql
```

## See also

- [JavaScript SDK](/docs/reference/javascript.md)
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md)
- [SurrealDB Cloud](/docs/running/cloud.md)

---

Source: https://surrealdb.com/docs/build/integrations/vercel/ai-sdk

# AI SDK

Use SurrealDB with the Vercel AI SDK 7 - store embeddings from embedMany, give models vector search and graph traversal tools, and persist chat history in a Next.js route.

The [AI SDK](https://ai-sdk.dev) is Vercel's TypeScript library for calling language and embedding models. SurrealDB stores the data that those calls need: embeddings in a vector index, the relations between documents as graph edges, and the chat history of each conversation. This page builds a question-answering chat route in which the model searches a knowledge base through tools that query SurrealDB.

> [!NOTE]
> This page stores data in a SurrealDB database that you run. For managed long-term memory that the AI SDK reads and writes through middleware, see the [Agent Memory AI SDK integration](/docs/agent-memory/integrations/ai-sdks/vercel-ai-sdk.md).

## Requirements

- [SurrealDB v3](/docs/running/installation.md), local or on [SurrealDB Cloud](/docs/running/cloud.md)
- AI SDK 7 (the `ai` package) and Node.js 22 or later
- Access to [Vercel AI Gateway](https://vercel.com/docs/ai-gateway), through the `AI_GATEWAY_API_KEY` environment variable locally or automatically on Vercel. The examples pass models as `provider/model` strings, which go through the gateway.

## Setup

Start SurrealDB locally:

```sh
surreal start --user root --pass secret memory
```

Save the schema below as `schema.surql`. The `HNSW` index uses 1536 dimensions, because that is the size of the vectors that `openai/text-embedding-3-small` returns. If you use another embedding model, set `DIMENSION` to its output size.

```surql title="schema.surql"
DEFINE NAMESPACE IF NOT EXISTS app;
USE NS app;
DEFINE DATABASE IF NOT EXISTS main;
USE DB main;

DEFINE USER app_server ON DATABASE PASSWORD 'strongPassword' ROLES EDITOR;

-- Documents and their embeddings
DEFINE TABLE document SCHEMAFULL;
DEFINE FIELD title ON document TYPE string;
DEFINE FIELD content ON document TYPE string;
DEFINE FIELD embedding ON document TYPE array<float>;
DEFINE INDEX document_embedding ON document FIELDS embedding HNSW DIMENSION 1536 DIST COSINE;

-- One document cites another
DEFINE TABLE cites SCHEMAFULL TYPE RELATION FROM document TO document;

-- Chat history, one record per conversation
DEFINE TABLE conversation SCHEMALESS;
```

Apply it:

```sh
curl -X POST -u "root:secret" -H "Accept: application/json" \
  --data-binary @schema.surql http://localhost:8000/sql
```

The `conversation` table is schemaless because the AI SDK message format has many part types, and the application reads the history back in the same shape it wrote it.

## Install dependencies

```sh
npm install ai @ai-sdk/react zod surrealdb
```

The examples use the `getDb()` helper from the Next.js page, which signs in once and shares the client between requests. Create `lib/surreal.ts` as shown in [Create one shared client](/docs/build/integrations/vercel/nextjs.md#create-one-shared-client), and set the `SURREALDB_*` environment variables listed there.

## Store documents with embeddings

`embedMany` returns one embedding for each input value, in the same order. Combine each embedding with its document and insert them all with one `INSERT` statement:

```ts title="lib/ingest.ts"
import { embedMany } from "ai";
import { getDb } from "@/lib/surreal";

export async function ingest(documents: { title: string; content: string }[]) {
    const { embeddings } = await embedMany({
        model: "openai/text-embedding-3-small",
        values: documents.map((document) => document.content),
    });

    const db = await getDb();
    await db.query("INSERT INTO document $documents", {
        documents: documents.map((document, index) => ({
            ...document,
            embedding: embeddings[index],
        })),
    });
}
```

When one document refers to another, record it as an edge so that a tool can follow it later:

```surql
RELATE document:knn->cites->document:hnsw;
```

## Give the model a search tool

A tool is a function that the model can decide to call. This one embeds the question with the same model that embedded the documents, and runs a nearest-neighbour search against the `HNSW` index:

```ts title="lib/tools.ts"
import { embed, tool } from "ai";
import { z } from "zod";
import { getDb } from "@/lib/surreal";

export const searchDocuments = tool({
    description: "Search the knowledge base for passages that answer a question.",
    inputSchema: z.object({
        query: z.string().describe("The question or topic to search for"),
    }),
    execute: async ({ query }) => {
        const { embedding } = await embed({
            model: "openai/text-embedding-3-small",
            value: query,
        });

        const db = await getDb();
        const [results] = await db
            .query(
                `SELECT id, title, content, vector::distance::knn() AS distance
                FROM document
                WHERE embedding <|5,100|> $embedding
                ORDER BY distance`,
                { embedding },
            )
            .json();

        return results;
    },
});
```

In `<|5,100|>`, `5` is the number of documents to return and `100` is the size of the candidate list that the `HNSW` search keeps while it looks for them. A larger second number finds closer matches more reliably and takes longer. `vector::distance::knn()` returns the distance that the search computed, so the query does not calculate it twice. [Similarity search](/docs/learn/data-models/vector-search/similarity-search.md) explains the operator and how to combine it with a `WHERE` filter.

`.json()` turns each record ID into a string such as `document:knn`, which the model can read and pass back to another tool.

## Give the model a graph tool

Vector search finds passages that use similar words. The `cites` edges add something that similarity cannot find: which documents depend on a passage, and which ones it depends on. A second tool lets the model follow those edges from a document that the search returned:

```ts title="lib/tools.ts"
export const relatedDocuments = tool({
    description: "List the documents that a document cites and the documents that cite it.",
    inputSchema: z.object({
        id: z.string().describe("A document id returned by searchDocuments, such as document:abc"),
    }),
    execute: async ({ id }) => {
        const db = await getDb();
        const [result] = await db
            .query(
                `SELECT
                    ->cites->document.{ id, title } AS cites,
                    <-cites<-document.{ id, title } AS cited_by
                FROM ONLY <record<document>> $id`,
                { id },
            )
            .json();

        return result;
    },
});
```

The model supplies `id` as a string. The cast `<record<document>> $id` turns it into a record ID, and the query fails unless the string is a record ID in the `document` table. The model therefore cannot use this tool to read from any other table. For `document:knn`, the tool returns:

```json title="Output"
{
    "cited_by": [],
    "cites": [{ "id": "document:hnsw", "title": "HNSW indexes" }]
}
```

## Keep chat history in SurrealDB

The route below loads the stored messages of a conversation, adds the new message from the client, and streams the answer. When the stream ends, `onEnd` receives the complete list of messages, including the new answer and its tool calls, and the route saves it:

```ts title="app/api/chat/route.ts"
import { convertToModelMessages, isStepCount, streamText, type UIMessage } from "ai";
import { RecordId } from "surrealdb";
import { relatedDocuments, searchDocuments } from "@/lib/tools";
import { getDb } from "@/lib/surreal";

export async function POST(request: Request) {
    const { id, message }: { id: string; message: UIMessage } = await request.json();

    const db = await getDb();
    const conversation = new RecordId("conversation", id);

    const [stored] = await db.query<[UIMessage[] | undefined]>(
        "SELECT VALUE messages FROM ONLY $conversation",
        { conversation },
    );
    const messages = [...(stored ?? []), message];

    const result = streamText({
        model: "anthropic/claude-sonnet-5.5",
        instructions: "Answer from the knowledge base. Call searchDocuments before answering.",
        messages: await convertToModelMessages(messages),
        tools: { searchDocuments, relatedDocuments },
        stopWhen: isStepCount(5),
    });

    return result.toUIMessageStreamResponse({
        originalMessages: messages,
        onEnd: async ({ messages }) => {
            await db.query(
                "UPSERT $conversation CONTENT { messages: $messages, updated_at: time::now() }",
                { conversation, messages },
            );
        },
    });
}
```

Some details of this route:

- The client sends only the new message, and the server owns the history. Configure `useChat` on the client to send `{ id, message }` instead of the full list, as shown below.
- `SELECT VALUE messages FROM ONLY $conversation` returns `undefined` for a new conversation, so the first request starts with an empty history.
- `stopWhen: isStepCount(5)` lets the model call tools and read their results for up to five steps before it must answer.
- The conversation ID comes from the client. If conversations belong to signed-in users, check that the user owns the conversation before you load it.

The client component gives `useChat` a transport that sends the conversation ID and the last message only:

```tsx title="components/chat.tsx"
"use client";

import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";

export function Chat({ id }: { id: string }) {
    const { messages, sendMessage } = useChat({
        id,
        transport: new DefaultChatTransport({
            api: "/api/chat",
            prepareSendMessagesRequest: ({ id, messages }) => ({
                body: { id, message: messages.at(-1) },
            }),
        }),
    });

    // Render messages and call sendMessage({ text }) from a form
}
```

## See also

- [Next.js](/docs/build/integrations/vercel/nextjs.md)
- [Eve](/docs/build/integrations/vercel/eve.md), for agents that run as durable workflows
- [Vector indexes](/docs/learn/data-models/vector-search/vector-indexes.md)
- [Hybrid search](/docs/learn/data-models/vector-search/hybrid-search.md), to combine vector and full-text results
- [AI SDK documentation](https://ai-sdk.dev/docs)

---

Source: https://surrealdb.com/docs/build/integrations/vercel/eve

# Eve

Connect eve agents to SurrealDB - tools that query your records with parameters and approval, and a custom memory provider with scoped vector recall and idempotent capture.

[eve](https://eve.dev) is Vercel's open-source framework for building agents in TypeScript. An eve agent is a directory of files: the model and instructions, one file for each tool, and optional memory slots, channels and schedules. eve runs each conversation as a durable workflow, so a session continues after a crash or a redeploy. This page connects an eve agent to SurrealDB in two ways: as tools that read and change your application's data, and as a memory provider that stores what the agent learns about each caller.

> [!NOTE]
> This page uses a SurrealDB database that you run. For a managed memory service with an eve adapter, see the [Agent Memory eve integration](/docs/agent-memory/integrations/frameworks/eve.md).

## Requirements

- [SurrealDB v3](/docs/running/installation.md), local or on [SurrealDB Cloud](/docs/running/cloud.md)
- An eve project and Node.js 24 or later. To create a project, run `npx eve@latest init my-agent`.
- An embedding model for the memory provider. The examples use `openai/text-embedding-3-small` through [Vercel AI Gateway](https://vercel.com/docs/ai-gateway).

## What SurrealDB stores

eve keeps its own state: the session history, the workflow steps and the values from `defineState`. SurrealDB holds the data that exists outside any single session:

| Data | Where it lives |
| --- | --- |
| Conversation history and workflow progress | eve |
| Orders, customers and the other records the agent works with | SurrealDB, reached through tools |
| Facts the agent remembers about a caller across sessions | SurrealDB, through a memory provider |

## Setup

Start SurrealDB locally:

```sh
surreal start --user root --pass secret memory
```

Save the schema below as `schema.surql`. It defines the store's data, the `memory` table for the memory provider, and a database user for the agent:

```surql title="schema.surql"
DEFINE NAMESPACE IF NOT EXISTS app;
USE NS app;
DEFINE DATABASE IF NOT EXISTS main;
USE DB main;

DEFINE USER agent ON DATABASE PASSWORD 'strongPassword' ROLES EDITOR;

DEFINE TABLE customer SCHEMAFULL;
DEFINE FIELD name ON customer TYPE string;
DEFINE FIELD email ON customer TYPE string;

DEFINE TABLE product SCHEMAFULL;
DEFINE FIELD name ON product TYPE string;
DEFINE FIELD price ON product TYPE decimal;

DEFINE TABLE order SCHEMAFULL;
DEFINE FIELD customer ON order TYPE record<customer>;
DEFINE FIELD items ON order TYPE array<record<product>>;
DEFINE FIELD total ON order TYPE decimal;
DEFINE FIELD status ON order TYPE 'pending' | 'shipped' | 'cancelled' DEFAULT 'pending';
DEFINE FIELD created_at ON order TYPE datetime DEFAULT time::now() READONLY;

DEFINE TABLE memory SCHEMAFULL;
DEFINE FIELD scope ON memory TYPE string;
DEFINE FIELD content ON memory TYPE string;
DEFINE FIELD embedding ON memory TYPE array<float>;
DEFINE FIELD created_at ON memory TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX memory_embedding ON memory FIELDS embedding HNSW DIMENSION 1536 DIST COSINE;

CREATE customer:ada CONTENT { name: 'Ada Lovelace', email: 'ada@example.com' };
CREATE product:lamp CONTENT { name: 'Desk lamp', price: 39.90dec };
CREATE order:a1b2c3 CONTENT { customer: customer:ada, items: [product:lamp], total: 39.90dec };
```

Apply it:

```sh
curl -X POST -u "root:secret" -H "Accept: application/json" \
  --data-binary @schema.surql http://localhost:8000/sql
```

## Install dependencies

From the root of the eve project:

```sh
npm install surrealdb
```

eve projects already depend on `ai` and `zod`, which the examples also use.

Add the connection details to `.env.local`, and to the Vercel project's environment variables before you deploy:

```sh title=".env.local"
SURREALDB_URL=http://localhost:8000
SURREALDB_NAMESPACE=app
SURREALDB_DATABASE=main
SURREALDB_USER=agent
SURREALDB_PASS=strongPassword
```

## Connect to SurrealDB

Tools and memory providers run in your application's runtime, not in eve's sandbox, so they can read `process.env` and share one client. Put the client in `agent/lib/`:

```ts title="agent/lib/surreal.ts"
import { Surreal } from "surrealdb";

let connection: Promise<Surreal> | undefined;

export function getDb(): Promise<Surreal> {
    connection ??= connect().catch((error) => {
        connection = undefined;
        throw error;
    });
    return connection;
}

async function connect(): Promise<Surreal> {
    const db = new Surreal();
    const namespace = process.env.SURREALDB_NAMESPACE!;
    const database = process.env.SURREALDB_DATABASE!;

    await db.connect(process.env.SURREALDB_URL!, {
        namespace,
        database,
        authentication: {
            namespace,
            database,
            username: process.env.SURREALDB_USER!,
            password: process.env.SURREALDB_PASS!,
        },
    });
    return db;
}
```

`getDb()` stores the connection promise, so tool calls that start at the same time share one sign-in. The [Next.js page](/docs/build/integrations/vercel/nextjs.md#create-one-shared-client) explains this pattern in more detail.

## Query data from a tool

eve names each tool after its file, so `agent/tools/get_order.ts` becomes the `get_order` tool. The model chooses the input, and the tool passes it to a fixed query as a parameter:

```ts title="agent/tools/get_order.ts"
import { defineTool } from "eve/tools";
import { z } from "zod";
import { getDb } from "../lib/surreal";

export default defineTool({
    description: "Look up an order by its id, with the customer and the items in it.",
    inputSchema: z.object({
        order_id: z.string().describe("The order id, such as order:a1b2c3"),
    }),
    async execute({ order_id }) {
        const db = await getDb();
        const [order] = await db
            .query(
                `SELECT id, status, total, created_at,
                    customer.{ name, email } AS customer,
                    items.{ name, price } AS items
                FROM ONLY <record<order>> $order_id`,
                { order_id },
            )
            .json();

        return order ?? { error: `No order ${order_id}` };
    },
});
```

The record links in `customer` and `items` are followed inside the query, so one call returns the order with its customer and products. The cast `<record<order>> $order_id` makes the query fail unless the input is a record ID in the `order` table. For `order:a1b2c3`, the tool returns:

```json title="Output"
{
    "created_at": "2026-10-15T05:07:01.376601Z",
    "customer": { "email": "ada@example.com", "name": "Ada Lovelace" },
    "id": "order:a1b2c3",
    "items": [{ "name": "Desk lamp", "price": "39.9" }],
    "status": "pending",
    "total": "39.9"
}
```

> [!IMPORTANT]
> Do not give the model a tool that runs SurrealQL queries written by the model. A fixed query with parameters limits each tool to one operation, and the agent's database user limits what any query can reach.

## Change data from a tool

A tool that changes data needs two more safeguards. eve can run a tool call again if the step was interrupted before it finished, so the change must give the same result when it runs twice. A person may also need to approve the change before it happens.

```ts title="agent/tools/cancel_order.ts"
import { defineTool } from "eve/tools";
import { always } from "eve/tools/approval";
import { z } from "zod";
import { getDb } from "../lib/surreal";

export default defineTool({
    description: "Cancel an order that has not shipped yet.",
    inputSchema: z.object({
        order_id: z.string().describe("The order id, such as order:a1b2c3"),
    }),
    approval: always(),
    async execute({ order_id }) {
        const db = await getDb();
        const [updated] = await db.query<[unknown[]]>(
            `UPDATE <record<order>> $order_id
                SET status = 'cancelled'
                WHERE status = 'pending'`,
            { order_id },
        );

        return updated.length > 0
            ? { cancelled: true }
            : { cancelled: false, reason: "The order is not pending" };
    },
});
```

The `WHERE status = 'pending'` condition makes the update safe to repeat: the first run cancels the order, and a second run matches no record and changes nothing. `approval: always()` pauses the call until a person approves it. The pause is durable, so the session continues from the same point after the approval arrives.

## Back a memory slot with SurrealDB

An eve memory slot binds a provider to a scope, such as the signed-in caller. Before each turn, eve calls the provider's `recall` handler and adds the messages it returns to the model's context. After the turn, it calls `capture` so that the provider can store what happened. The provider below stores each user message with its embedding, and recalls the five closest memories at the start of the next turn:

```ts title="agent/lib/surreal-memory.ts"
import { embed, type ModelMessage } from "ai";
import { defineMemoryProvider } from "eve/memory";
import { defineTool } from "eve/tools";
import { RecordId } from "surrealdb";
import { z } from "zod";
import { getDb } from "./surreal";

const embeddingModel = "openai/text-embedding-3-small";

function userText(messages: readonly ModelMessage[]): string {
    return messages
        .filter((message) => message.role === "user")
        .flatMap((message) =>
            typeof message.content === "string"
                ? [message.content]
                : message.content.flatMap((part) => (part.type === "text" ? [part.text] : [])),
        )
        .join("\n")
        .trim();
}

export function surrealMemory() {
    return defineMemoryProvider({
        recall: {
            async "turn.started"(ctx) {
                const text = userText(ctx.turn.input);
                if (!text) return null;

                const { embedding } = await embed({ model: embeddingModel, value: text });
                const db = await getDb();
                const [memories] = await db.query<[{ id: RecordId; content: string }[]]>(
                    `SELECT id, content FROM memory
                    WHERE scope = $scope AND embedding <|5,100|> $embedding`,
                    { scope: ctx.memory.scope.key, embedding },
                );

                return {
                    messages: memories.map((memory) => ({
                        id: memory.id.toString(),
                        content: memory.content,
                    })),
                };
            },
        },
        capture: {
            async "turn.completed"(ctx) {
                const text = userText(ctx.turn.input);
                if (!text) return;

                const { embedding } = await embed({ model: embeddingModel, value: text });
                const db = await getDb();
                await db.query("INSERT IGNORE INTO memory $memory", {
                    memory: {
                        id: new RecordId("memory", ctx.operationId),
                        scope: ctx.memory.scope.key,
                        content: text,
                        embedding,
                    },
                });
            },
        },
        async tools(ctx) {
            return {
                forget: defineTool({
                    description: "Delete a remembered note by its id.",
                    inputSchema: z.object({ id: z.string() }),
                    async execute({ id }) {
                        const db = await getDb();
                        await db.query("DELETE <record<memory>> $id WHERE scope = $scope", {
                            id,
                            scope: ctx.memory.scope.key,
                        });
                        return { deleted: true };
                    },
                }),
            };
        },
    });
}
```

The provider follows the rules that eve sets for every memory provider:

- **Every read and write is limited to `ctx.memory.scope.key`.** The `scope` condition is part of the vector search itself, so the `HNSW` search skips other callers' memories while it collects the five results, rather than filtering them out afterwards. The `forget` tool has the same condition, so the model cannot delete another caller's memory even with a valid record ID.
- **Capture is idempotent.** eve can call a handler again with the same `operationId`. The memory record's ID is the `operationId`, and `INSERT IGNORE` leaves an existing record unchanged, so a repeated call stores nothing new.
- **Recalled memories have stable IDs.** eve uses the `id` of each recalled message to replace an earlier copy of the same memory instead of adding a duplicate.

Declare the slot in `agent/memory/`. The file name becomes the slot name, so the model sees the tool as `notes__forget`:

```ts title="agent/memory/notes.ts"
import { defineMemory } from "eve/memory";
import { byPrincipal } from "eve/memory/scope";
import { surrealMemory } from "../lib/surreal-memory";

export default defineMemory({
    description: "Facts and preferences the caller has shared in earlier conversations.",
    provider: surrealMemory(),
    scope: byPrincipal,
});
```

`byPrincipal` gives each authenticated caller their own scope. eve's [memory documentation](https://eve.dev/docs/memory) describes the other scopes, and [Build a memory provider](https://eve.dev/docs/memory/custom-provider) describes the full provider contract.

This provider stores every user message, which keeps the example short. A production provider usually stores less: for example, it can ask a model to extract durable facts from the turn and store only those, or it can store nothing in `capture` and offer the model a `remember` tool instead.

## Limit what the agent can reach

The `agent` user in the schema has the `EDITOR` role on one database. To limit the agent further, give it a [record user](/docs/learn/security/authentication/users.md#record-users) and define table permissions, or split the work between two users: a `VIEWER` user for tools that only read, and an `EDITOR` user for the tools that need approval. [DEFINE USER](/docs/reference/query-language/statements/define/user.md) lists the roles.

## See also

- [AI SDK](/docs/build/integrations/vercel/ai-sdk.md), for search and graph tools outside an eve agent
- [Similarity search](/docs/learn/data-models/vector-search/similarity-search.md)
- [Record links and graph relations](/docs/learn/data-models/graph/record-links-vs-graph-relations.md)
- [eve documentation](https://eve.dev/docs/getting-started)

---

Source: https://surrealdb.com/docs/build/integrations/vercel/nextjs

# Next.js

Connect a Next.js App Router application on Vercel to SurrealDB - one shared client, Server Components, Server Actions, Route Handlers and browser live queries with record users.

[Next.js](https://nextjs.org) renders React on the server, so most of your SurrealDB queries run in server code: Server Components read data, Server Actions change it, and Route Handlers serve it as JSON. This page builds a small task list that does all three, then adds live updates in the browser. The same code runs locally and on Vercel.

## Requirements

- [SurrealDB v3](/docs/running/installation.md), local or on [SurrealDB Cloud](/docs/running/cloud.md)
- Next.js 16 with the App Router
- Node.js 20.9 or later

## Setup

Start SurrealDB locally, either [installed directly](/docs/running/installation.md):

```sh
surreal start --user root --pass secret memory
```

or [with Docker](/docs/running/docker.md):

```sh
docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest start --user root --pass secret
```

Save the schema below as `schema.surql`. It defines the namespace and database, a `task` table, and a database-level user for the application to sign in as:

```surql title="schema.surql"
DEFINE NAMESPACE IF NOT EXISTS app;
USE NS app;
DEFINE DATABASE IF NOT EXISTS main;
USE DB main;

DEFINE USER app_server ON DATABASE PASSWORD 'strongPassword' ROLES EDITOR;

DEFINE TABLE task SCHEMAFULL;
DEFINE FIELD title ON task TYPE string ASSERT $value.len() > 0;
DEFINE FIELD done ON task TYPE bool DEFAULT false;
DEFINE FIELD created_at ON task TYPE datetime DEFAULT time::now() READONLY;
```

Apply it through the `/sql` endpoint:

```sh
curl -X POST -u "root:secret" -H "Accept: application/json" \
  --data-binary @schema.surql http://localhost:8000/sql
```

## Install dependencies

In an existing Next.js project, add the SDK and the `server-only` package:

```sh
npm install surrealdb server-only
```

Add the connection details to `.env.local`. On Vercel, set the same variables in the project settings, as described in [Store credentials as environment variables](/docs/build/integrations/vercel.md#store-credentials-as-environment-variables):

```sh title=".env.local"
SURREALDB_URL=http://localhost:8000
SURREALDB_NAMESPACE=app
SURREALDB_DATABASE=main
SURREALDB_USER=app_server
SURREALDB_PASS=strongPassword
```

## Create one shared client

Signing in with a password takes a few hundred milliseconds, because SurrealDB hashes the password with a function that is slow on purpose, so that an attacker can only test a few guesses per second. See [Passwords](/docs/learn/security/best-practices/security-best-practices.md#passwords) in the security best practices, and [A bit of cryptography](/learn/book/chapter-15#a-bit-of-cryptography) in chapter 15 of Aeon's Surreal Renaissance for how these functions work. Sign in once per server instance and reuse the client for every request. On Vercel's Fluid compute, one instance serves many requests, so a client stored at module scope is shared between them.

```ts title="lib/surreal.ts"
import "server-only";
import { Surreal } from "surrealdb";

let connection: Promise<Surreal> | undefined;

export function getDb(): Promise<Surreal> {
    connection ??= connect().catch((error) => {
        connection = undefined;
        throw error;
    });
    return connection;
}

async function connect(): Promise<Surreal> {
    const db = new Surreal();
    const namespace = process.env.SURREALDB_NAMESPACE!;
    const database = process.env.SURREALDB_DATABASE!;

    await db.connect(process.env.SURREALDB_URL!, {
        namespace,
        database,
        authentication: {
            namespace,
            database,
            username: process.env.SURREALDB_USER!,
            password: process.env.SURREALDB_PASS!,
        },
    });
    return db;
}
```

Three details in this file matter:

- `getDb()` stores the promise rather than the client. Requests that arrive while the first connection is still opening wait for the same promise, so the instance signs in only once. If the connection fails, the next call tries again.
- `import "server-only"` makes the build fail if a Client Component imports this file, which keeps the credentials out of the browser bundle.
- An `http://` or `https://` URL makes the SDK send each query as its own HTTP request. Nothing has to stay open while Vercel pauses the instance between requests.

## Read data in a Server Component

A Server Component can be `async` and query the database directly. The result type is passed as a generic, with one element for each statement in the query:

```tsx title="app/page.tsx"
import type { RecordId } from "surrealdb";
import { getDb } from "@/lib/surreal";
import { addTask } from "./actions";

type Task = {
    id: RecordId<"task">;
    title: string;
    done: boolean;
};

export default async function Page() {
    const db = await getDb();
    const [tasks] = await db.query<[Task[]]>(
        "SELECT id, title, done, created_at FROM task ORDER BY created_at DESC LIMIT 50",
    );

    return (
        <main>
            <form action={addTask}>
                <input name="title" required />
                <button type="submit">Add task</button>
            </form>
            <ul>
                {tasks.map((task) => (
                    <li key={task.id.toString()}>{task.title}</li>
                ))}
            </ul>
        </main>
    );
}
```

`created_at` is in the selection because SurrealDB requires a field used in `ORDER BY` to be selected.

Record IDs arrive as `RecordId` objects, which keep the table and the key separate. React cannot pass a class instance such as `RecordId` from a Server Component to a Client Component, so convert it with `.toString()`, or call `.json()` on the query, before you pass a result down as a prop.

## Change data in a Server Action

A Server Action runs on the server when the form is submitted. Pass user input as a query parameter, never by building the query string, and then revalidate the page so that it shows the new record:

```ts title="app/actions.ts"
"use server";

import { revalidatePath } from "next/cache";
import { getDb } from "@/lib/surreal";

export async function addTask(formData: FormData) {
    const title = String(formData.get("title") ?? "").trim();
    if (!title) return;

    const db = await getDb();
    await db.query("CREATE task CONTENT { title: $title }", { title });

    revalidatePath("/");
}
```

The `ASSERT` on the `title` field rejects an empty title in the database as well, so the rule holds for every client, not only for this form.

## Serve JSON from a Route Handler

A Route Handler returns a `Response`. The `.json()` method on a query converts SurrealDB values such as record IDs and datetimes to their JSON forms, so the result can go straight into `Response.json()`:

```ts title="app/api/tasks/route.ts"
import { getDb } from "@/lib/surreal";

export async function GET() {
    const db = await getDb();
    const [tasks] = await db
        .query("SELECT id, title, done, created_at FROM task ORDER BY created_at DESC")
        .json();

    return Response.json(tasks);
}
```

A request to `/api/tasks` returns the tasks:

```json title="Sample output"
[
    {
        "created_at": "2026-10-15T05:05:43.7823Z",
        "done": false,
        "id": "task:s97vxaoriwi01cuf7d5p",
        "title": "Write docs"
    }
]
```

## Stream live updates to the browser

A [live query](/docs/reference/javascript/concepts/live-queries.md) sends a message to the client each time a matching record changes. It needs a WebSocket connection that stays open, so it belongs in the browser rather than in a Vercel Function.

The browser must never receive the `app_server` password. Instead, each person signs in as a [record user](/docs/learn/security/authentication/users.md#record-users), and table permissions decide what that user can see. Add a `user` table, a [record access method](/docs/reference/query-language/statements/define/access/record.md), and permissions on `task` to the schema:

```surql title="schema.surql"
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD email ON user TYPE string;
DEFINE FIELD password ON user TYPE string;
DEFINE INDEX user_email ON user FIELDS email UNIQUE;

DEFINE ACCESS account ON DATABASE TYPE RECORD
    SIGNUP (CREATE user CONTENT { email: $email, password: crypto::argon2::generate($password) })
    SIGNIN (SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password))
    DURATION FOR SESSION 12h;

DEFINE TABLE OVERWRITE task SCHEMAFULL
    PERMISSIONS
        FOR select FULL
        FOR create, update, delete NONE;
```

`DEFINE TABLE OVERWRITE` replaces the earlier table definition and keeps its fields. With these permissions, a signed-in record user can read every task but cannot change one. The permissions apply only to record users, so the `app_server` user in the Server Action can still create tasks. Your sign-in flow calls `signin` or `signup` with `access: "account"` and receives an access token.

The Client Component below connects with that token and keeps its list in step with the database:

```tsx title="components/live-tasks.tsx"
"use client";

import { useEffect, useState } from "react";
import { Surreal, Table } from "surrealdb";

type Task = { id: string; title: string; done: boolean };

export function LiveTasks({ token, initial }: { token: string; initial: Task[] }) {
    const [tasks, setTasks] = useState(initial);

    useEffect(() => {
        const db = new Surreal();

        async function subscribe() {
            await db.connect(process.env.NEXT_PUBLIC_SURREALDB_URL!, {
                namespace: process.env.NEXT_PUBLIC_SURREALDB_NAMESPACE,
                database: process.env.NEXT_PUBLIC_SURREALDB_DATABASE,
                authentication: token,
            });

            const live = await db.live<Omit<Task, "id">>(new Table("task"));
            live.subscribe((message) => {
                if (message.action === "KILLED") return;
                const id = message.recordId.toString();
                setTasks((current) => {
                    const others = current.filter((task) => task.id !== id);
                    if (message.action === "DELETE") return others;
                    return [{ ...message.value, id }, ...others];
                });
            });
        }

        subscribe().catch(console.error);
        return () => {
            db.close();
        };
    }, [token]);

    return (
        <ul>
            {tasks.map((task) => (
                <li key={task.id}>{task.title}</li>
            ))}
        </ul>
    );
}
```

Set `NEXT_PUBLIC_SURREALDB_URL` to the WebSocket address of the server, such as `wss://<your-instance>.aws.surrealdb.cloud`. Variables with the `NEXT_PUBLIC_` prefix are included in the browser bundle, so put only the address, namespace and database in them, never a password.

## Deploy to Vercel

1. Make sure that Vercel can reach the database. A server on `localhost` is not reachable from a deployment, so use a SurrealDB Cloud instance or another server with a public address.
2. Add `SURREALDB_URL`, `SURREALDB_NAMESPACE`, `SURREALDB_DATABASE`, `SURREALDB_USER` and `SURREALDB_PASS` to the project's environment variables, and the `NEXT_PUBLIC_` variables if you use live queries.
3. Deploy:

```sh
vercel deploy
```

Routes that use the shared client run on the Node.js runtime, which is the default in Next.js and the runtime that Vercel recommends.

## See also

- [AI SDK](/docs/build/integrations/vercel/ai-sdk.md), which adds a chat route to an application like this one
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md)
- [Live queries](/docs/reference/javascript/concepts/live-queries.md)
- [Record users](/docs/learn/security/authentication/users.md#record-users)

---

Source: https://surrealdb.com/docs/build/integrations/vercel/vercel-sandbox

# Vercel Sandbox

Run a temporary SurrealDB server inside a Vercel Sandbox microVM for tests and agent scratch data, or give sandboxed code read-only access to a SurrealDB database.

[Vercel Sandbox](https://vercel.com/docs/vercel-sandbox) creates isolated Linux virtual machines on demand, controlled from TypeScript with the `@vercel/sandbox` package. The virtual machines run Linux on Vercel's infrastructure, while the code that controls them runs on any system that has Node.js, including Windows. Agents use sandboxes to run code they have generated, and test suites use them to start clean environments. This page shows two ways to use SurrealDB with a sandbox: start a temporary SurrealDB server inside one, or let code inside a sandbox query a database that runs elsewhere.

This page is about Vercel's product. The [SurrealDB Studio Sandbox](/docs/running/sandbox.md) is a separate, browser-based environment for trying SurrealQL.

## Requirements

- A Vercel account and a project linked with the Vercel CLI
- Node.js 22 or later
- For the second pattern, a SurrealDB server that the sandbox can reach, such as a [SurrealDB Cloud](/docs/running/cloud.md) instance

## Install dependencies

```sh
npm install @vercel/sandbox surrealdb
```

The SDK authenticates with a Vercel OIDC token. On Vercel it is available automatically. On your computer, link the project and pull the token into `.env.local`:

```sh
vercel link
vercel env pull
```

The pulled token expires after a few hours, so run `vercel env pull` again when the SDK reports that it cannot get credentials.

## Choose a pattern

| Pattern | Use it for | Where the data lives |
| --- | --- | --- |
| SurrealDB inside a sandbox | Integration tests, preview environments, and scratch databases that an agent creates and then discards | In the sandbox, until it stops |
| Sandboxed code that connects out | Running generated code, such as a report or a data check, against your real data | In your SurrealDB server |

> [!WARNING]
> A sandbox is not a place to host a database that other systems depend on. It stops when its timeout ends, and each session has a maximum length that depends on your Vercel plan. Keep data that must last in [SurrealDB Cloud](/docs/running/cloud.md) or on a server that you manage.

## Run SurrealDB inside a sandbox

The script below creates a sandbox, installs SurrealDB, starts it with the `memory` storage engine, and queries it from your computer through the sandbox's public URL:

```ts title="surreal-in-sandbox.ts"
import { Sandbox } from "@vercel/sandbox";
import { Surreal } from "surrealdb";

const password = crypto.randomUUID();

await using sandbox = await Sandbox.create({
    ports: [8000],
    timeout: 10 * 60 * 1000,
});

await sandbox.runCommand({
    cmd: "sh",
    args: ["-c", "curl -sSf https://install.surrealdb.com | sh"],
    sudo: true,
});

await sandbox.runCommand({
    cmd: "surreal",
    args: ["start", "--bind", "0.0.0.0:8000", "--user", "root", "--pass", password, "memory"],
    detached: true,
});

const url = sandbox.domain(8000);
await waitForHealth(url);

const db = new Surreal();
await db.connect(url, {
    authentication: { username: "root", password },
});

await db.query("DEFINE NAMESPACE app; USE NS app; DEFINE DATABASE main;");
await db.use({ namespace: "app", database: "main" });

await db.query("CREATE person CONTENT { name: 'Ada' }");
const [people] = await db.query("SELECT * FROM person").json();
console.log(people);

await db.close();

async function waitForHealth(url: string) {
    for (let attempt = 0; attempt < 30; attempt++) {
        const response = await fetch(`${url}/health`).catch(() => undefined);
        if (response?.ok) return;
        await new Promise((resolve) => setTimeout(resolve, 1000));
    }
    throw new Error("SurrealDB did not become healthy");
}
```

Run it with the token from `.env.local`:

```sh
npx tsx --env-file=.env.local surreal-in-sandbox.ts
```

The script prints the new record:

```text title="Sample output"
[ { id: 'person:si33pkkzl1fh2rs000a7', name: 'Ada' } ]
```

Each step in the script has a purpose:

- **`ports: [8000]`** exposes the port, and `sandbox.domain(8000)` returns its public HTTPS address.
- **`sudo: true`** lets the install script write the `surreal` binary to `/usr/local/bin`.
- **`detached: true`** returns as soon as the server process starts, instead of waiting for it to exit.
- **`waitForHealth`** polls the `/health` endpoint, because the server takes a moment to start listening.
- **`DEFINE NAMESPACE` and `DEFINE DATABASE`** create the namespace and database, because SurrealDB does not create them when a client first selects them.
- **`await using`** stops the sandbox when the script ends, even after an error. Without it, call `await sandbox.stop()` yourself.

> [!IMPORTANT]
> The address from `sandbox.domain()` is public. Anyone who finds it can try to sign in, so the script creates a random root password for each sandbox instead of a fixed one, and never logs it.

The example connects through the public URL over HTTPS. Vercel does not document WebSocket support for sandbox ports, so do not depend on WebSocket connections or [live queries](/docs/reference/javascript/concepts/live-queries.md) through that URL.

With the `memory` storage engine, the data is deleted when the process stops. That is usually what a test or a scratch database needs: every run starts from an empty database.

## Let sandboxed code connect to your database

To run code inside a sandbox against a database that already exists, give the sandbox credentials for a user that can do only what the code needs. A database-level user with the `VIEWER` role can read every table in one database and cannot change anything:

```surql
DEFINE USER sandbox ON DATABASE PASSWORD 'strongPassword' ROLES VIEWER;
```

A write from this user does not raise an error. The statement returns no records and leaves the data unchanged.

The script below passes the credentials as environment variables and limits the sandbox's network access to the npm registry and your SurrealDB host. It then writes a small program into the sandbox and runs it:

```ts title="connect-out.ts"
import { Sandbox } from "@vercel/sandbox";

const surrealHost = new URL(process.env.SURREALDB_URL!).hostname;

await using sandbox = await Sandbox.create({
    timeout: 5 * 60 * 1000,
    env: {
        SURREALDB_URL: process.env.SURREALDB_URL!,
        SURREALDB_USER: "sandbox",
        SURREALDB_PASS: process.env.SURREALDB_SANDBOX_PASS!,
    },
    networkPolicy: {
        allow: ["registry.npmjs.org", surrealHost],
    },
});

await sandbox.runCommand("npm", ["install", "surrealdb"]);

await sandbox.writeFiles([
    {
        path: "report.mjs",
        content: `
import { Surreal } from "surrealdb";

const db = new Surreal();
await db.connect(process.env.SURREALDB_URL, {
    namespace: "app",
    database: "main",
    authentication: {
        namespace: "app",
        database: "main",
        username: process.env.SURREALDB_USER,
        password: process.env.SURREALDB_PASS,
    },
});

const [result] = await db.query("SELECT count() AS orders FROM order GROUP ALL").json();
console.log(JSON.stringify(result));
await db.close();
`,
    },
]);

const run = await sandbox.runCommand("node", ["report.mjs"]);
console.log(await run.stdout());
```

When an agent writes the program instead of you, the same two limits apply: the `VIEWER` role stops the program from changing data, and the network policy stops it from sending the data to any other host.

## See also

- [Vercel](/docs/build/integrations/vercel.md), for credentials and connection guidance that apply to all Vercel products
- [Eve](/docs/build/integrations/vercel/eve.md), whose agents run generated code in Vercel Sandbox
- [System users](/docs/learn/security/authentication/users.md#system-users)
- [Vercel Sandbox SDK reference](https://vercel.com/docs/vercel-sandbox/sdk-reference)

---

Source: https://surrealdb.com/docs/build/migrating

# Migrating to SurrealDB

Moving data into SurrealDB from other databases, or older releases. Also from files and streams.

This section describes how to move data into SurrealDB: from other databases, from older SurrealDB versions, and from files or streams (for example CSV, JSON Lines, or Kafka). Use the subsections for the path that matches your source.

## In this section

- [1.x to 2.x](/docs/build/migrating/from-old-surrealdb-versions/1x-to-2x.md) - what changed between the 1.x and 2.x releases
- [2.x to 3.x](/docs/build/migrating/from-old-surrealdb-versions/2x-to-3x.md) - what changed between the 2.x and 3.x releases
- [From files and streams](/docs/build/migrating/from-files-and-streams.md) - load data held in files or arriving as a stream
- [From other databases](/docs/build/migrating/from-other-databases/overview.md) - move across from another database engine

## Moving between SurrealDB Cloud instances

A Free instance moves to a paid instance type in place, from the **Compute** tab of its settings, with no migration. See [Scaling](/docs/manage/instances/scaling.md#scaling-a-start-instance).

Moving from Start to Scale, or to another region, needs a new instance. Deploy it, then [restore a backup](/docs/manage/instances/backups.md) or [import an export](/docs/manage/instances/import-and-export.md) into it.

---

Source: https://surrealdb.com/docs/build/migrating/from-files-and-streams

# Migrating

Import CSV, JSON Lines and Kafka data into SurrealDB.

This section details how to map data, queries and concepts you may know from other databases and datatypes into SurrealDB.

The sources in this section of the documentation can be automatically imported to SurrealDB using the [Surreal Sync](https://github.com/surrealdb/surreal-sync/) tool. In addition, [CSV data](/docs/build/migrating/from-files-and-streams/csv.md) can be automatically imported into SurrealDB Studio using its own [built-in functionality](/docs/explore/studio.md).

For other sources not yet supported, consider beginning by exporting the database as JSON which can be [imported on the command line](/docs/reference/cli/db/commands/import.md). Alternatively, you can use [one of the many available SDKs](/docs/languages.md) to access your existing database and transfer its content directly to a SurrealDB instance.

* [CSV data](/docs/build/migrating/from-files-and-streams/csv.md)
* [JSON lines](/docs/build/migrating/from-files-and-streams/json-lines.md)
* [Kafka](/docs/build/migrating/from-files-and-streams/kafka.md)

---

Source: https://surrealdb.com/docs/build/migrating/from-files-and-streams/csv

# Importing CSV data

SurrealDB Studio can be used to import CSV data to SurrealDB.

This page details some methods to import CSV data to SurrealDB.

## Using SurrealDB Studio

CSV data is the easiest external data type to import to SurrealDB, as it can be imported directly via the [SurrealDB Studio](/docs/explore/studio.md) UI.

Importing the data is done by going to the Explorer tab, clicking on Import database below, and following the prompts. For more details, see [this page](/docs/explore/studio.md) in the SurrealDB Studio documentation.

## Importing CSV data using Surreal Sync

The [Surreal Sync](https://github.com/surrealdb/surreal-sync) tool can be used to import CSV files in the local filesystem or S3 buckets into a SurrealDB table with automatic type detection and optional record ID generation.

For more on how this is done, please see the [CSV Import for Surreal Sync](https://github.com/surrealdb/surreal-sync/blob/main/docs/csv.md) page in its repo.

---

Source: https://surrealdb.com/docs/build/migrating/from-files-and-streams/json-lines

# Importing JSON Lines data

Import JSON Lines files with Surreal Sync, and the type mapping.

This page details JSON data types and their SurrealQL equivalents, followed by links to the [Surreal Sync](https://github.com/surrealdb/surreal-sync/) tool which allows this data to be automatically imported to SurrealDB.

## Data types

|      JSON Data Type      |           JSON Representation            | Recommended SurrealQL type    |
| ------------------------ | ---------------------------------------- | ----------------------------- |
| **String**               | `"text"`                                 | `string`                      |
| **Number**               | `42` or `3.14`                           | `int`, `float`, or `decimal`  |
| **Boolean**              | `true`/`false`                           | `bool`                        |
| **null**                 | `null`                                   | `none` or `null`              |
| **Array**                | `[1, 2, 3]`                              | `array` or `set`              |
| **Object**               | `{"key": "value"}`                       | `object`                      |
| **ISO 8601 Date String** | `"2024-01-15T14:30:00Z"`                 | `datetime` or `string`        |
| **UUID String**          | `"550e8400-e29b-41d4-a716-446655440000"` | `uuid` or `string`            |
| **Base64 String**        | `"SGVsbG8gV29ybGQ="`                     | `bytes` or `string`           |

## Importing JSON Lines data using Surreal Sync

The JSONL source in Surreal Sync allows you to import JSON Lines (JSONL) files into SurrealDB. Each JSONL file becomes a table in SurrealDB, and each line in the file becomes a document in that table.

For more on how to import JSON Lines data to SurrealDB, please see the following pages in the Surreal Sync repo.

* [JSONL Source Usage Guide](https://github.com/surrealdb/surreal-sync/blob/main/docs/jsonl.md)
* [JSONL Data Types Support in Surreal Sync](https://github.com/surrealdb/surreal-sync/blob/main/docs/jsonl.md)

---

Source: https://surrealdb.com/docs/build/migrating/from-files-and-streams/kafka

# Importing Kafka data

SurrealDB's Surreal Sync tool can be used to import Kafka data to SurrealDB.

Kafka data can be imported to SurrealDB using the Surreal Sync tool, as follows:

* The specified Kafka topic becomes a table in SurrealDB, where each message from the topic becomes a record in the table, with configurable deduplication.
* Each Kafka message must be encoded using Protobuf. The Kafka source decodes every message payload using Protobuf and converts it into SurrealDB Upsert queries, transforming the payloads into SurrealDB records.
* Message Key Strategy: Use Kafka message keys as record IDs (base64 encoded)
* Field Strategy: Extract IDs from a field in the message payload (default: "id" field)

For more on how to import Kafka data to SurrealDB, please see the [Kafka Source Usage Guide](https://github.com/surrealdb/surreal-sync/blob/main/docs/kafka.md) page in the Surreal Sync repo.

---

Source: https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/1x-to-2x

# 1.x to 2.x

This guide will help you upgrade your current SurrealDB installation to the latest `2.x` release.

The `2.0.0` release of SurrealDB includes [many new features, improvements, and bug fixes](/releases#v2-0-0). It also includes some breaking changes that you should be aware of when upgrading.

This guide will help you upgrade your current SurrealDB installation to the latest `2.x` release.

## Breaking changes

### Datastore
- The underlying approach for storing record IDs and ranges has changed
  - [Record IDs now support storing UUIDs instead of strings](https://github.com/surrealdb/surrealdb/pull/4491)
  - [Ranges are now their own value rather than being available only as record ID ranges](https://github.com/surrealdb/surrealdb/pull/4506)

### SurrealQL
- The `UPDATE` statement no longer creates records if these are missing. Instead, use the new [UPSERT](/docs/reference/query-language/statements/upsert.md) statement for this behaviour.
- The `file://` connection protocol has been deprecated in favour of the more explicit `rocksdb://` protocol.
- The `DEFINE SCOPE` statement has been dropped in favour of the new [`DEFINE ACCESS TYPE RECORD`](/docs/reference/query-language/statements/define/access/record.md) statement
  - `DEFINE TOKEN` definitions defined under scopes are now integrated into `DEFINE ACCESS TYPE RECORD`.
- Some functions have been renamed for clarity
  - `meta::tb()` -> [`record::tb()`](/docs/reference/query-language/functions/database-functions/record.md)
  - `meta::id()` -> [`record::id()`](/docs/reference/query-language/functions/database-functions/record.md)
  - `string::endsWith()` -> [`string::ends_with()`](/docs/reference/query-language/functions/database-functions/string.md)
  - `string::startsWith()` -> [`string::starts_with()`](/docs/reference/query-language/functions/database-functions/string.md)

### Authentication and headers
- Authentication is now enabled by default as you previously would with `--auth`. The [`--unauthenticated`](/docs/reference/cli/db/commands/start.md#unauthenticated-mode) flag is now required in order to provide the previous default behaviour.
- Specifying the level on which credentials will be authenticated is now required when connecting to SurrealDB. By default, this level will be root. This can be provided with the `--auth-level` flag in the CLI or the `surreal-auth-ns` and `surreal-auth-db` headers in the HTTP REST API.
- SurrealDB now listens only for connections from the local machine unless another interface (e.g. 0.0.0.0) is provided via the `--bind` command line argument.
- SurrealDB now does not print secrets in response to `INFO` statements. The values of the secrets will appear as `[REDACTED]` to prevent accidental leakage. The export functionality will still print the values of the secrets. An `UNREDACTED` clause will be added soon to provide the previous behaviour.
- Headers used to communicate via HTTP with SurrealDB now require the `surreal-` prefix. For example, the legacy `ns` and `db` headers are now `surreal-ns` and `surreal-db`.

## Upgrading your data

A new [`surreal db fix`](/docs/reference/cli/db/commands/fix.md) command has been implemented to automatically change the format of your stored data. The command is followed by a path to the data. For example:

```bash
# For SurrealKV
surreal db fix surrealkv://mydata

# For RocksDB
surreal db fix rocksdb:somedatabase
```

> [!IMPORTANT]
> Run `surreal db fix` with a 2.x binary. From SurrealDB 3.0.0 the command exits with `Fix is not implemented` and changes nothing, so data going from 1.x to 3.x is fixed with 2.x first and then moved to 3.x by following [Migrating from 2.x to 3.x](/docs/build/migrating/from-old-surrealdb-versions/2x-to-3x.md).

### Limitations of the surreal db fix command

Although the `surreal db fix` command is a quick way to migrate your data, it is not without its drawbacks:

-  If you have used the now deprecated [`DEFINE TOKEN`](/docs/reference/query-language/statements/define/token.md) command to define a token on a Scope with the also deprecated [`DEFINE SCOPE`](/docs/reference/query-language/statements/define/scope.md) command, you will have to update your access management rules to use the new [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md) which supports creating permissions using [TYPE JWT](/docs/reference/query-language/statements/define/access/jwt.md) and [TYPE RECORD](/docs/reference/query-language/statements/define/access/record.md) rules.

- If you were querying SurrealDB via the HTTP API, the `surreal db fix` command will not update the header format for you. You will need to manually update the header format  from `ns` and `db` to `surreal-ns` and `surreal-db` respectively before using the `surreal db fix` command. Learn more about this in the [HTTP documentation](/docs/reference/rest-api/http-protocol.md).

### Upgrading from 2.0.0-alpha

The `surreal db fix` command above has been created specifically for 1.x instances. However, data currently on a `2.0.0-alpha` instance can still be manually exported and then reimported into a project running on `2.0.0` via the following steps.

1. Export your current data as a `.surql` (SurrealQL) file. You can do this using the [`surreal db export`](/docs/reference/cli/db/commands/export.md) command in the terminal:

```bash
# Example export command to export data to a file called `export.surql` in the downloads directory.
surreal db export --conn http://localhost:8000 --user root --pass secret --ns main --db main downloads/export.surql
```

2. This will create a file called `export.surql` in the current directory.

3. You can now import this file back into your project running on `2.0.0`.

```bash
surreal db import --conn http://localhost:8000 --user root --pass secret --ns main --db main downloads/export.surql
```

## Troubleshooting

### Error when connecting to a `2.x` instance.

If you are trying to connect to a `2.x` instance, and get an error similar to the following, you are likely using an older version of SurrealDB.

```bash
error: Storage version is out-of-date.
```

## Read the full changelog

There have been major improvements to SurrealDB in `2.0.0` release both in alpha and beta. Check out the changes on the [release page](/releases).

---

Source: https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/26-to-27

# 2.6 to 2.7

What changes when a 2.6 deployment moves to 2.7, from namespace and database removal in the background and reusing a removed name to rolling back to 2.6, mixed-version clusters and the clean-up that runs after the upgrade.

`2.7` is a minor release on the `2.x` line, so a `2.6` datastore opens on `2.7.0` without an export and import. The main change is to `REMOVE NAMESPACE` and `REMOVE DATABASE`, which now return as soon as the definition is removed and leave the data to a background task. This page describes what that changes for a deployment and an application, including the one-way step it adds to the upgrade. New features and fixes are listed in the [release notes](/releases/2.7).

The [`surreal upgrade db`](/docs/reference/cli/db/commands/upgrade.md) command installs the new binary as usual.

## Before you upgrade

1. Take and verify a backup of the whole datastore if a return to `2.6` might be needed, because `2.6` cannot safely open a datastore that `2.7.0` has used. A single [`surreal db export`](/docs/reference/cli/db/commands/export.md) covers one database, so it is not a complete backup on its own. See [Rolling back to `2.6`](#rolling-back-to-26).
2. In a cluster, plan to upgrade every node before removing a namespace or database. See [Clusters and rolling upgrades](#clusters-and-rolling-upgrades).
3. Check any script or test suite that removes a namespace or database and then defines it again straight away. See [Reusing a removed name](#reusing-a-removed-name).

## Removal runs in the background

From `2.7.0`, `REMOVE NAMESPACE` and `REMOVE DATABASE` delete the definition and queue the data for a background task, in one transaction. The work the statement does no longer depends on how much data the namespace or database holds, so removing a large database returns at once. In `2.6`, the statement deleted every key in its own transaction, so on a large database it could run without finishing or fail with `Failed to commit transaction due to a read or write conflict`.

On `2.7.0`, the removed namespace or database is unreachable as soon as the statement commits. Queries find no records in it, and nobody can sign in as one of its users or through one of its access methods. In a cluster, this holds only once every node runs `2.7.0`, as described in [Clusters and rolling upgrades](#clusters-and-rolling-upgrades).

The background task then deletes the data in batches of 1,000 keys, and a run that stops part way resumes where it left off after a restart. Each run deletes up to 100,000 keys. The task runs every 60 seconds by default, and deletion starts on the run after the one that first finds the removal, so with the default interval it starts one to two minutes after the removal. [`SURREAL_RECLAIM_INTERVAL`](/docs/reference/cli/db/environment-variables.md#command-environment-variables) or `--reclaim-interval` changes the interval, and an embedded Rust application sets it with `Config::reclaim_interval`. A shorter interval deletes a large namespace or database sooner.

Two details differ from `2.6`:

- Every removal deletes all stored versions of the removed data, so on a versioned SurrealKV datastore the history of a removed database is deleted with it. In `2.6`, only `AND EXPUNGE` did this.
- `REMOVE NAMESPACE ... AND EXPUNGE` and `REMOVE DATABASE ... AND EXPUNGE` are still accepted, but they now return before the data is deleted, like every other removal.

`REMOVE TABLE` and `REMOVE INDEX` are unchanged, and still delete their data in the removing transaction.

## Reusing a removed name

When a namespace or database is defined under the name of one that is still being deleted, the statement first deletes what is left of the old one, within its own transaction. This applies to `DEFINE NAMESPACE` and `DEFINE DATABASE` and, when the server does not run with `--strict`, to the first write that creates a namespace or database implicitly. A namespace or database created under a reused name therefore always starts empty.

That clean-up deletes at most 10,000 keys per transaction. If more is left, the statement fails with the error below, and the same statement succeeds once the background task has deleted the rest:

```text title="Error output"
The name 'test/test' cannot be reused until the data of the removed namespace or database has been reclaimed
```

A script that removes and recreates a small database, such as a test fixture, stays within the limit. For a larger one, wait for the background task, use a different name, or set a shorter reclaim interval.

## Rolling back to `2.6`

> [!IMPORTANT]
> A datastore cannot safely return to `2.6` once `2.7.0` has run on it. A `2.6` build does not know that a removal can leave data on disk, so it serves any data that `2.7.0` has not yet deleted: the users of a removed database can sign in again, and a namespace or database defined under the same name receives the old records. `2.6` also never deletes that data. The way back to `2.6` is to restore a backup of the whole datastore taken before the upgrade.

The backup can take either of two forms:

- **A storage snapshot**, taken while the `2.6` server is stopped, such as a copy of the RocksDB or SurrealKV data directory or a TiKV cluster backup. Restoring the snapshot and starting `2.6` on it returns every namespace, database and user to its state before the upgrade.
- **An export of every database.** Each [`surreal db export`](/docs/reference/cli/db/commands/export.md) run covers one namespace and one database, and none of them contains what is defined above a database, such as root users or namespace-level users and access methods. To restore this way, create a new datastore with `2.6` and define each namespace and database in it, which a server started with `--strict` needs before an import can write to them. Then define the root and namespace-level users and access methods again, and import each export into its database.

From `2.7.0`, a build also checks a storage revision that later `2.x` releases can raise, and refuses to start on a datastore at a revision it cannot read, with `The data stored on disk was written by a newer version of SurrealDB (storage revision …) and cannot be read by this one`. `2.6` has no such check, which is why it starts on a datastore that `2.7.0` has used.

## Clusters and rolling upgrades

A cluster can be upgraded one node at a time. Until every node runs `2.7.0`, a `2.6` node can still serve the data of a namespace or database that a `2.7.0` node has removed, as described in [Rolling back to `2.6`](#rolling-back-to-26). Remove namespaces and databases only after the last node is upgraded.

## Background work after the upgrade

After the first `2.7.0` start, two background tasks work through data that earlier `2.x` releases left in place. Both commit their work in batches of 1,000 keys, so neither holds a large transaction open.

- **`COUNT` index compaction.** Each record created or deleted on a table with a [`COUNT` index](/docs/reference/query-language/statements/define/indexes.md#count-index) adds an entry to the index, and a compaction task combines those entries into one. From `2.4.0` to `2.6.5`, that task never found any work, so a count served by the index read one entry for every record ever created or deleted on the table. `2.7.0` compacts each `COUNT` index, and its first runs combine every entry recorded since the index was built.
- **Changefeed timestamps.** Earlier `2.x` releases recorded a timestamp for every database at each changefeed clean-up, every 10 seconds by default, and never deleted one. `2.7.0` records timestamps only for a database that defines a changefeed on itself or on one of its tables, and deletes the timestamps already recorded for every other database.

---

Source: https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/2x-to-3x

# 2.x to 3.x

This guide will help you upgrade your current SurrealDB installation to the latest `3.x` release.

This guide consolidates all breaking changes when upgrading from SurrealDB `2.x` to `3.x`, organised by severity level. If you are using SurrealDB Studio, you can use the [migration diagnostics](/docs/explore/studio.md) to check your data automatically. This will also provide you with a list of actions you need to take to migrate your data.

## Migration diagnostics in SurrealDB Studio

SurrealDB Studio provides a built-in migration diagnostics tool that checks your data automatically and gives you a list of actions you need to take to migrate your data.

>[!NOTE]
>The migration diagnostics tool is only available for SurrealDB version `2.6.1` and above.

<img src="~/assets/img/image/dark/migration-diagnostics.png" alt="SurrealDB Studio migration diagnostics" />

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).

<img src="~/assets/img/image/dark/surrealist-migration-report.png" alt="SurrealDB Studio migration report" />

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](#using-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:

1. 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]
> See the [complete function mapping table](#2-function-name-changes) below for the full list of function name updates.

2. `SEARCH ANALYZER` → `FULLTEXT ANALYZER`: Index definitions using `SEARCH ANALYZER` are automatically converted to `FULLTEXT ANALYZER`.
3. Parameter declarations: Automatically adds `LET` keyword where required for parameter declarations
4. `MTREE` → `HNSW` conversion: Vector search indexes using the deprecated `MTREE` type are automatically converted to use `HNSW`.
5. Future to COMPUTED field conversion: Where possible, `<future>` fields are automatically converted to `COMPUTED` fields.

## What requires manual migration

Some changes cannot be automatically converted and require manual intervention:

- Futures stored in records (using `DEFAULT <future>` or `CREATE ... SET field = <future>`)
- Nested fields with `<future>` values
- Queries using both GROUP and SPLIT clauses
- Code using removed operators (`~`, `!~`, `?~`, `*~`)
- Stored closures in records
- Record reference syntax
- `ANALYZE` statement usage

## Using V3 compatible export

After completing the migration diagnostics in SurrealDB Studio 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 SurrealDB Studio 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.

<img src="~/assets/img/image/dark/surrealist-deploy-instance.png" alt="SurrealDB Studio deploy instance" />

## Using the CLI

If you prefer to migrate using the command line rather than SurrealDB Studio, 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]
>The `v2` subcommand requires SurrealDB version `3.0.3` or later.

>[!IMPORTANT]
>Before exporting, ensure you have resolved any migration issues flagged by the [SurrealDB Studio migration diagnostics](#migration-diagnostics-in-surrealdb-studio). The v3-compatible export will handle automatic transformations (such as function renames and `SEARCH ANALYZER` → `FULLTEXT ANALYZER`), but issues that require manual intervention should be resolved first.

### 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`:

```bash
surreal v2 export --v3 --namespace <namespace> --database <database> \
  --token <token> v2_exported_for_v3.surql
```

The `--v3` flag ensures the export applies the same automatic transformations described in the [V3 compatible export](#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 db import` command:

```bash
surreal db import --namespace <namespace> --database <database> \
  --endpoint <endpoint> --token <token> v2_exported_for_v3.surql
```

For the full list of available options for each command, see the [export command](/docs/reference/cli/db/commands/export.md) and [import command](/docs/reference/cli/db/commands/import.md) documentation.

## Severity levels

This section describes the severity levels in the migration report and the actions you need to take to migrate your data. The 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**:

1. Use the migration tool to automatically convert futures where possible.
2. Manually replace `VALUE <future> { expression }` with `COMPUTED expression`.
3. Restructure code for cases where automatic conversion isn't possible (nested fields, `DEFAULT` futures).

**Before (2.x)**:
```surql
DEFINE FIELD age
  ON person VALUE <future> { time::year(time::now()) - time::year(born) };
CREATE foo SET field = <future> { expression };
```

**After (3.x)**:
```surql
DEFINE FIELD age
  ON person COMPUTED time::year(time::now()) - time::year(born);
-- Futures stored in records cannot be converted - requires redesign
```

>[!NOTE]
>For futures stored in records (using `DEFAULT <future>` or `CREATE ... SET field = <future>`), there is no direct replacement in `3.x`. Fixing these cases will require re-architecting your schema, as storing arbitrary queries in record data is no longer supported.

**COMPUTED restrictions**:

- Can only be used in `DEFINE FIELD` statements
- No nested fields allowed inside or under `COMPUTED` fields
- Cannot be used on ID fields
- Cannot combine with: `VALUE`, `DEFAULT`, `READONLY`, `ASSERT`, `REFERENCE`, `FLEXIBLE`
- Only works on top-level fields, not nested fields

**Example - nested field workaround**:

```surql
-- 2.x version
DEFINE FIELD name.full
  ON person VALUE <future> { name.first + ' ' + name.last };

-- 3.x version - must rename to avoid nesting
DEFINE FIELD full_name
  ON person COMPUTED name.first + ' ' + name.last;
```

### 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**:

<table>
    <thead>
        <tr>
            <th scope="col">New Function Name</th>
            <th scope="col">Previous name</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="New function name"><code>duration::from_days</code></td>
            <td scope="row" data-label="Previous name"><code>duration::from::days</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>duration::from_hours</code></td>
            <td scope="row" data-label="Previous name"><code>duration::from::hours</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>duration::from_micros</code></td>
            <td scope="row" data-label="Previous name"><code>duration::from::micros</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>duration::from_millis</code></td>
            <td scope="row" data-label="Previous name"><code>duration::from::millis</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>duration::from_mins</code></td>
            <td scope="row" data-label="Previous name"><code>duration::from::mins</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>duration::from_nanos</code></td>
            <td scope="row" data-label="Previous name"><code>duration::from::nanos</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>duration::from_secs</code></td>
            <td scope="row" data-label="Previous name"><code>duration::from::secs</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>duration::from_weeks</code></td>
            <td scope="row" data-label="Previous name"><code>duration::from::weeks</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>geo::is_valid</code></td>
            <td scope="row" data-label="Previous name"><code>geo::is::valid</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::distance::osa</code></td>
            <td scope="row" data-label="Previous name"><code>string::distance::osa_distance</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_alphanum</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::alphanum</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_alpha</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::alpha</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_ascii</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::ascii</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_datetime</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::datetime</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_domain</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::domain</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_email</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::email</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_hexadecimal</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::hexadecimal</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_ip</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::ip</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_ipv4</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::ipv4</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_ipv6</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::ipv6</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_latitude</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::latitude</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_longitude</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::longitude</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_numeric</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::numeric</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_record</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::record</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_semver</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::semver</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_url</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::url</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_ulid</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::ulid</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>string::is_uuid</code></td>
            <td scope="row" data-label="Previous name"><code>string::is::uuid</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>time::is_leap_year</code></td>
            <td scope="row" data-label="Previous name"><code>time::is::leap_year</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>time::from_nanos</code></td>
            <td scope="row" data-label="Previous name"><code>time::from::nanos</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>time::from_micros</code></td>
            <td scope="row" data-label="Previous name"><code>time::from::micros</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>time::from_millis</code></td>
            <td scope="row" data-label="Previous name"><code>time::from::millis</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>time::from_secs</code></td>
            <td scope="row" data-label="Previous name"><code>time::from::secs</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>time::from_ulid</code></td>
            <td scope="row" data-label="Previous name"><code>time::from::ulid</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>time::from_unix</code></td>
            <td scope="row" data-label="Previous name"><code>time::from::unix</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>time::from_uuid</code></td>
            <td scope="row" data-label="Previous name"><code>time::from::uuid</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_array</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::array</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_bool</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::bool</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_bytes</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::bytes</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_collection</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::collection</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_datetime</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::datetime</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_decimal</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::decimal</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_duration</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::duration</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_float</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::float</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_geometry</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::geometry</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_int</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::int</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_line</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::line</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_none</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::none</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_null</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::null</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_multiline</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::multiline</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_multipoint</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::multipoint</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_multipolygon</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::multipolygon</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_number</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::number</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_object</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::object</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_point</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::point</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_polygon</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::polygon</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_range</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::range</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_record</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::record</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_string</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::string</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::is_uuid</code></td>
            <td scope="row" data-label="Previous name"><code>type::is::uuid</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="New function name"><code>type::record</code></td>
            <td scope="row" data-label="Previous name"><code>type::thing</code></td>
        </tr>
    </tbody>
</table>

Learn more about the database functions in the [SurrealQL functions](/docs/reference/query-language/functions/database-functions.md) 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)**:
```surql
array::range(0, 5)   // returns [0,1,2,3,4]
array::range(-1, 5)  // returns [-1,0,1,2,3]
array::range(-5, 5)  // returns [-5,-4,-3,-2,-1]
```

**After (3.x)**:
```surql
array::range(0, 5)   // returns [0,1,2,3,4]
array::range(-1, 5)  // returns [-1,0,1,2,3,4]  ← different!
array::range(-5,
  5)  // returns [-5,-4,-3,-2,-1,0,1,2,3,4]  ← different!
array::range(0..=1)  // returns [0,1]
```

**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)**:
```surql
$val = 10;  // This was allowed
```

**After (3.x)**:
```surql
LET $val = 10;  // LET is now required
```

**Error Message**:
```text
Parse error: Parameter declarations without `let` are deprecated.
Replace with `let $val = ...` to keep the previous behavior.
```

### 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)**:
```surql
SELECT age, emails FROM user SPLIT emails GROUP BY age;
-- SPLIT had no effect.
```

**After (3.x) - Option 1 (split then group)**:
```surql
SELECT age,
  emails FROM (SELECT * FROM user SPLIT emails) GROUP BY age;
```

**After (3.x) - Option 2 (group then split)**:
```surql
SELECT * FROM (SELECT age,
  emails FROM user GROUP BY age,
  emails) SPLIT emails;
```

### 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)**:
```surql
"Mario" ~ "mario";  // returns true
```

**After (3.x)**:
```surql
string::similarity::jaro("Mario", "mario") > 0.8;  // returns true

-- Create reusable function
DEFINE FUNCTION fn::similar($one: string, $two: string) -> bool {
    string::similarity::jaro($one, $two) > 0.8
};

fn::similar("Mario", "mario");  // returns true
```

**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)**:
```surql
DEFINE INDEX userNameIndex ON TABLE user 
COLUMNS name SEARCH ANALYZER example_ascii BM25 HIGHLIGHTS;
```

**After (3.x)**:
```surql
DEFINE INDEX userNameIndex ON TABLE user 
COLUMNS name FULLTEXT ANALYZER example_ascii BM25 HIGHLIGHTS;
```

### 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)**:
```bash
surreal db start --strict
```

**After (3.x)**:
```surql
DEFINE DATABASE mydb STRICT;
```

**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)**:
```surql
DEFINE INDEX vec_idx ON table FIELDS embedding MTREE DIMENSION 768;
```

**After (3.x)**:
```surql
DEFINE INDEX vec_idx ON table FIELDS embedding HNSW DIMENSION 768;
```

### 10. Stored closures

**Severity**: Will break

**What changed**: Closures can no longer be stored as part of a record.

**Action**: Closures stored inside a record have to be removed. There is currently no new feature that can replace them.

**Before (2.x)**:
```surql
CREATE record SET closure = |$a| $a + 1
```

**After (3.x)**:
```surql
-- This will now throw an error
CREATE record SET closure = |$a| $a + 1
```

### 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**: Remove any use of the `ANALYZE` statement.

## Can break - likely issues

### 13. All `.*` idiom behaviour

**Severity**: Can break

**What changed**: The `.*` (`all` idiom) behaviour changed for arrays and objects.

**Breaks when**: Used to dereference record IDs in arrays or get object values.

**Before (2.x)**:
```surql
[a:1, a:2].*       // returns [a:1, a:2]
[a:1, a:2].*.*     // dereferences records
{ a: 1, b: "foo" }.* // returns [1, "foo"]
```

**After (3.x)**:
```surql
[a:1, a:2].*       // dereferences records directly
{ a: 1, b: "foo" }.* // returns { a: 1, b: "foo" }
```

**Migration**:
- For arrays: Replace `.*.*` with `.*`
- For objects: Replace `.*` with `object::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)**:
```surql
[{ a: ["a","b"]}, {a: [1,2]}].a[0]
-- returns ["a","b"]
-- Evaluated as: ([...].a)[0]
```

**After (3.x)**:
```surql
[{ a: ["a","b"]}, {a: [1,2]}].a[0]
-- returns ["a",1]
-- Evaluated on each element: [(...).a[0], (...).a[0]]
```

**Migration**: Swap idiom parts if old behaviour needed.
- Old: `.field[0]`
- New: `[0].field`

### 15. Idiom fetching changes

**Severity**: Can break

**What changed**: Multiple improvements to idiom fetching behaviour.

**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)**:
```surql
["string", NONE].map(|$val| $val?.len());
```

**After (3.x)**:
```surql
["string", NONE].map(|$val| $val.?.len());
```

**Reason**: Distinguishes between `??` operator and optional chaining on `option<option<value>>`.

### 17. Parsing changes

**Severity**: Can break

**Record ID parsing**:
```surql
-- 2.x
r"a:b[r"c:d"]"  // unescaped " was allowed

-- 3.x
r"a:b[r\"c:d\"]"  // must escape "
```

**Unicode parsing**:
```surql
-- 2.x
"\uD83D\uDF15"  // surrogate pairs

-- 3.x
"\u{1F715}"  // single escape sequence
```

**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)**:
```surql
<set>[2,3,1,1];  // returns [2, 3, 1]
```

**After (3.x)**:
```surql
<set>[2,3,1,1];  // returns {1, 2, 3}
```

**Migration Options**:
1. Use new set type (recommended)
2. Maintain old behaviour: Define as `array` adding `VALUE $value.distinct()` to `DEFINE FIELD` definition

### 19. Schema strictness changes

**Severity**: Can break

**Non-existing tables**:
```surql
-- 3.x returns errors instead of empty arrays
SELECT * FROM doesnt_exist;
//- Error: "The table 'doesnt_exist' does not exist"
```

**SCHEMAFULL Tables**:
```surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON user TYPE string;

-- 2.x: extra fields silently filtered
-- 3.x: extra fields cause error
CREATE user CONTENT { name: "Billy", other: "value" };
//- Error: "Found field 'other', but no such field exists"

-- Use destructuring to select only defined fields
CREATE user CONTENT { name: "Billy", other: "value" }.{ name };
```

### 20. Numeric record ID ordering

**Severity**: Can break

**What changed**: Numeric values in record IDs 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)**:
```surql
CREATE t:[1];
CREATE t:[1f];
SELECT * FROM t; // returns `[{ id: [1] }, { id: [1f] }]`
```

**After (3.x)**:
```surql
CREATE t:[1];
CREATE t:[1f];
-- returns an error, record with key `t:[1]` alread exisits.
SELECT * FROM t; // returns `[{ id: [1] }]`
```

### 21. `SET` expressions read the record from before the statement

**Severity**: Can break

**What changed**: In 2.x, the assignments in a `SET` clause ran one after another in the order they were written, and each one saw the values set before it. In 3.x, every expression in the clause reads the record as it was before the statement, so an assignment never sees another assignment in the same statement.

**Breaks when**: A later assignment reads a field that an earlier assignment in the same `SET` clause changes, or a `CREATE` or `UPSERT` reads a field it sets itself. The `UPDATE` case gives a different value without an error.

**Before (2.x)**:
```surql
CREATE t:1 SET a = 1;
UPDATE t:1 SET a = 10, c = a;
//- [{ a: 10, c: 10, id: t:1 }]

CREATE t:2 SET a = 1, b = a + 1;
//- [{ a: 1, b: 2, id: t:2 }]
```

**After (3.x)**:
```surql
CREATE t:1 SET a = 1;
UPDATE t:1 SET a = 10, c = a;
//- [{ a: 10, c: 1, id: t:1 }]

CREATE t:2 SET a = 1, b = a + 1;
//- "Cannot perform addition with 'none' and 'int'"
```

To use a new value in another field, compute it once with `LET` and use the parameter in both assignments, as in `LET $a = 10; UPDATE t:1 SET a = $a, c = $a;`. A value that must always follow another field can be a [`COMPUTED` field](/docs/reference/query-language/statements/define/field.md) instead.

## Unlikely break - edge cases

### 22. `math::sqrt` returns NaN

**Severity**: Unlikely break

**What changed**: Returns `NaN` instead of `NONE` for negative numbers.

**Action**: Change checks from `NONE` to `NaN`.

```surql
-- 2.x
math::sqrt(-1);  // returns NONE

-- 3.x
math::sqrt(-1);  // returns NaN
```

### 23. `math::min` returns `Infinity`

**Severity**: Unlikely break

**What changed**: Returns `Infinity` instead of `NONE` for empty arrays.

**Action**: Change checks from `NONE` to `Infinity`.

```surql
-- 2.x
math::min([]);  // returns NONE

-- 3.x
math::min([]);  // returns Infinity
```

### 24. `math::max` returns `-Infinity`

**Severity**: Unlikely break

**What changed**: Returns `-Infinity` instead of `NONE` for empty arrays.

**Action**: Change checks from `NONE` to `-Infinity`.

```surql
-- 2.x
math::max([]);  // returns NONE

-- 3.x
math::max([]);  // returns -Infinity
```

### 25. `array::logical_and` behaviour

**Severity**: Unlikely break

**What changed**: Function is now consistent with `&&` operator.

**Breaks when**: Relying on specific values rather than truthiness.

**Before (2.x)**:
```surql
array::logical_and(["a"],[true]);  // returns ["a"]
array::logical_and([""],[false]);  // returns [""]
array::logical_and([true],[]);     // returns [NULL]
```

**After (3.x)**:
```surql
array::logical_and(["a"],[true]);  // returns [true]
array::logical_and([""],[false]);  // returns [""]
array::logical_and([true],[]);     // returns [NONE]
```

**Action**: Update if relying on specific return values; no change needed if only checking truthiness.

### 26. `array::logical_or` behaviour

**Severity**: Unlikely break

**What changed**: Function now consistent with `||` operator.

**Breaks when**: Relying on specific values rather than truthiness.

**Before (2.x)**:
```surql
array::logical_or(["a"],[true]);  // returns ["a"]
array::logical_or([""],[false]);  // returns [""]
array::logical_or([],[false]);    // returns [NULL]
```

**After (3.x)**:
```surql
array::logical_or(["a"],[true]);  // returns ["a"]
array::logical_or([""],[false]);  // returns [false]
array::logical_or([false],[]);    // returns [NONE]
```

**Action**: Update if relying on specific return values; no change needed if only checking truthiness.

### 27. 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)**:
```surql
|a:1..2|;  // returns |a:1..2| (mock type)
type::is_array(|a:1..2|);  // returns false
```

**After (3.x)**:
```surql
|a:1..=2|;  // returns [a:1, a:2] (array)
type::is_array(|a:1..=2|);  // returns true
```

>[!NOTE]
>Mock ranges are no longer inclusive by default - use `..=` for inclusive ranges.

### 28. `Id` field special behaviour.

**Severity**: Unlikely break

**What changed**: Special behaviour 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 on the special behaviour that `.id` idioms had.

**Before (2.x)**:
```surql
record:{ key_field: "value" }.id.key_field // returns "value"
```

**After (3.x)**:
```surql
record:{ key_field: "value" }.id.key_field // returns whatever value
  is at .id.key_field in the record with key `record:{ key_field:
  "value" }`
record:{ key_field: "value" }.id().key_field // returns "value" 
```

### 29. 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:

```surql
DEFINE INDEX select ...
```

This must now be written with backticks, or renamed.

```surql
DEFINE INDEX `select` ...
```

The statements which had this change from an identifier to allowing a general expression are the following:

- The `ident` after `DEFINE TABLE ident ...`
- The `ident` after `DEFINE NAMESPACE ident ...`
- The `ident` after `DEFINE DATABASE ident ...`
- The `ident` after `DEFINE USER ident ...`
- The `ident` after `DEFINE ACCESS ident ...`
- Both `ident` and `table` after `DEFINE EVENT ident ON table ...`
- Both `ident` and `table` after `DEFINE FIELD ident ON table ...`
- The `ident` after `DEFINE ANALYZER ident ...`
- The `ident` after `DEFINE BUCKET ident ...`
- The `ident` after `DEFINE SEQUENCE ident ...`
- The `ident` after `INFO FOR TABLE ident ...`
- The `ident` after `INFO FOR USER ident ...`
- Both `ident` and `table` after `INFO FOR INDEX ident ON table ...`
- The `ident` after `REMOVE TABLE ident ...`
- The `ident` after `REMOVE NAMESPACE ident ...`
- The `ident` after `REMOVE DATABASE ident ...`
- The `ident` after `REMOVE USER ident ...`
- The `ident` after `REMOVE ACCESS ident ...`
- Both `ident` and `table` after `REMOVE EVENT ident ON table ...`
- Both `ident` and `table` after `REMOVE FIELD ident ON table ...`
- Both `ident` and `table` after `REMOVE INDEX ident ON table ...`
- The `ident` after `REMOVE ANALYZER ident ...`
- The `ident` after `REMOVE BUCKET ident ...`
- The `ident` after `REMOVE SEQUENCE ident ...`

### 30. `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 that used an `ASSERT $value().len()` check for an exact number of items can now put 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.

```surql
-- Assert exact length of 640 bytes in SurrealDB 2.x
DEFINE FIELD bytes ON data TYPE array<int> ASSERT $value.all(|$val| $val
  IN 0..=255) AND $value.len() = 640;

-- Assert the same in SurrealDB 3.x
DEFINE FIELD bytes ON data TYPE array<int,
  640> ASSERT $value.all(|$val| $val IN 0..=255);

-- Assert a maximum array size in SurrealDB 3.x
DEFINE FIELD latest
  ON observation TYPE array<object> ASSERT $value.len() <= 1000;
```

---

Source: https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/32-to-33

# 3.2 to 3.3

What changes when a 3.2 deployment moves to 3.3, from the data migrations that run at first start and the optional index rebuilds to rolling-upgrade limits and behaviour changes in SurrealQL, permissions, security, configuration and the SDKs.

`3.3` is a minor release, so a `3.2` datastore opens on `3.3` without an export and import. The first `3.3.0` node to start applies a few data migrations automatically, and the rest of the upgrade consists of behaviour changes that a deployment or an application may need to account for. This page describes each one briefly and links to the page that documents it in full. New features are listed in the [release notes](/releases), and the [upgrade overview](/docs/build/migrating/from-old-surrealdb-versions/overview.md) covers upgrades in general.

The [`surreal upgrade db`](/docs/reference/cli/db/commands/upgrade.md) command installs the new binary as usual.

## Before you upgrade

These steps apply before the first `3.3.0` node starts against a `3.2` datastore:

1. Take and verify a [`surreal db export`](/docs/reference/cli/db/commands/export.md) if a return to `3.2` might be needed, because `3.2` cannot read a datastore correctly once `3.3.0` has started against it. See [Rolling back to `3.2`](#rolling-back-to-32).
2. In a cluster, let running index builds finish, and plan to upgrade every node before creating sequences, relying on silo modules, changing the schema or writing to HNSW and DiskANN indexes. See [Clusters and rolling upgrades](#clusters-and-rolling-upgrades).
3. Remove `versioned=true` and any non-zero `retention`, or the matching `SURREAL_DATASTORE_VERSIONED` and `SURREAL_DATASTORE_RETENTION` values, from any server started on `mem://`, or move it to SurrealKV or RocksDB, because `3.3.0` refuses them on the in-memory backend. See [In-memory storage no longer supports `VERSION`](#in-memory-storage-no-longer-supports-version).
4. On a deployment configured for a `3.3.0` beta, remove `mutable_permissions` from `--allow-experimental`, `--deny-experimental`, `SURREAL_CAPS_ALLOW_EXPERIMENTAL` and `SURREAL_CAPS_DENY_EXPERIMENTAL`, since `3.3.0` does not recognise that value and the server does not start with it.
5. For SurrealDB Enterprise, configure a licence key and let the first start reach `api.keygen.sh`. See [SurrealDB Enterprise](#surrealdb-enterprise).

## Data migrations at first start

The first `3.3.0` node to start against a `3.2` datastore applies the pending data migrations before it serves queries, and no flag is needed. Each applied migration is recorded in the datastore, so it runs once per datastore however many nodes share it, and a migration that an interrupted start did not finish runs again on the next start.

| Migration | What it changes | Work on a `3.2` datastore |
| --- | --- | --- |
| 1 | Copies each sequence definition out of the table keyspace | Proportional to the number of tables whose names begin with `sq` |
| 2 | Moves each silo module definition to the key its name derives | Proportional to the number of module definitions |
| 3 | Gives each table's doc-ID mappings an injective key | None, because only a `3.3.0` pre-release writes those mappings |

The migrations run within the startup operation timeout, which is set with `--startup-operation-timeout` or [`SURREAL_STARTUP_OPERATION_TIMEOUT`](/docs/reference/cli/db/environment-variables.md#command-environment-variables) and is 60 seconds by default. The timeout matters mainly on a datastore written by a `3.3.0` pre-release, where migration 3 copies one mapping for each record that a full-text, HNSW or DiskANN index holds. A restart keeps what an earlier attempt copied but still pays for the rest, so a start that times out during a migration needs a higher timeout rather than another attempt with the same one.

While the migrations run, the server starts accepting connections after 5 seconds at most and answers most requests with `503` until startup finishes, as described in [Startup and health checks](#startup-and-health-checks).

### Sequence definitions move out of the table keyspace

Up to `3.2`, a sequence definition was stored at the same key position as a table name. On a database holding a table whose name begins with `sq`, listing sequences therefore read that table's records and failed, which broke `INFO FOR DB`, `surreal db export`, `REMOVE DATABASE` and `REMOVE NAMESPACE`. Migration 1 copies each [sequence](/docs/reference/query-language/statements/define/sequence.md) definition to a location of its own and leaves the original where it was, so existing sequences keep working on nodes of either version and [`sequence::nextval`](/docs/reference/query-language/functions/database-functions/sequence.md) continues from its previous value.

Two effects remain after the migration:

- A `DEFINE SEQUENCE` run while `3.2` nodes are still serving is visible to `3.3.0` nodes only, because only they read the new location. Running the statement once every node is upgraded, or running it again then, makes the sequence visible everywhere.
- On a versioned backend, `INFO FOR DB VERSION <t>` and `sequence::nextval` resolved at a timestamp before the migration committed find no sequences. Reads at the present time are unaffected.

### Silo module definitions are rekeyed

`3.3.0` adds `::` before the version segment of a silo module path, so a definition that `3.2.4` or earlier stored as `silo::acme::widgets<1.2.3>` is looked up as `silo::acme::widgets::<1.2.3>`. Until the definition moves, `INFO FOR DB` lists it while a call to one of its exports, `REMOVE MODULE` and `ALTER MODULE` report that it does not exist. Migration 2 moves each such definition to the key its name derives, and leaves its permissions, comment and `UNSIGNED` flag as they were. Where a `DEFINE MODULE … OVERWRITE` has already written a second copy, the reachable copy is kept and the other is removed.

`mod::` modules are unaffected. Because the migration moves the definition rather than copying it, a node older than `3.3.0` stops resolving silo modules once the migration has run. On a versioned backend, a silo module call resolved at a timestamp before the migration committed does not find the module. The new path syntax is covered in [Surrealism modules](#surrealism-modules-experimental).

> [!IMPORTANT]
> The migration reached its final form in `3.3.0-beta.4`. On `3.3.0-beta.3` and earlier it was registered against a later version than the running server, so it never ran and the affected definitions stayed unreachable. Upgrading straight to `3.3.0` runs it.

### Rolling back to `3.2`

> [!IMPORTANT]
> A datastore cannot return to `3.2` once a `3.3.0` node has started against it. A `3.2` build checks only the datastore's major version, which the migrations leave unchanged, so it starts on a migrated datastore and reads it wrongly: silo modules moved by migration 2 cannot be reached, sequences defined on `3.3.0` are missing, and definitions written by `3.3.0` cannot be decoded. The way back to `3.2` is to restore an export taken before the upgrade into a new datastore created by `3.2`.

From `3.3.0`, a build records the migrations it applies, and a build that lacks one of the recorded migrations, such as `3.3.0` on a datastore that a later release has migrated, refuses to start with `The data stored on disk was migrated by a newer version of SurrealDB … and cannot be read by this one`.

## Indexes

### Full-text, HNSW and DiskANN indexes keep their 3.2 layout

`3.3` serves full-text, HNSW and DiskANN indexes written by `3.2` on the layout they already have, so the upgrade requires no rebuild. A rebuild is optional: it lets an index join the new query plans, and it repairs the two faults from earlier releases described in [Vector indexes over record IDs that hold a number](#vector-indexes-over-record-ids-that-hold-a-number) and [Record IDs that differ only in a nested number's type](#record-ids-that-differ-only-in-a-nested-numbers-type). The cache for HNSW and DiskANN is scoped by stored format version, so readers before and after a rebuild stay isolated from each other.

> [!NOTE]
> Beta releases `3.3.0-beta.1` through `3.3.0-beta.3` rejected these indexes at query time and required an immediate rebuild. A datastore upgraded through one of those betas is unaffected once it reaches `3.3.0`, since the layout was preserved rather than converted.

### Rebuilding a 3.2 index lets it join the new plans

`3.3` gives each table one doc-ID space that all of its indexes share. Only an index that is defined or rebuilt on `3.3` records its entries in that space, and only such an index takes part in [bitmap index fusion](/docs/learn/querying/concepts-and-guides/query-optimisation.md#how-surrealdb-combines-indexes), where the planner combines several indexes in one query, and in [index pre-filtered vector search](/docs/learn/data-models/vector-search/similarity-search.md#pre-filtering-with-an-index). This applies to standard and `UNIQUE` indexes as well as to full-text, HNSW and DiskANN indexes. An index built by `3.2` keeps answering queries through the plans it used before, and [`REBUILD INDEX`](/docs/reference/query-language/statements/rebuild.md) moves it into the shared space, which can happen when it suits the workload rather than as part of the upgrade. The plan operators are listed under [Bitmap index fusion](/docs/reference/query-language/clauses/explain.md#bitmap-index-fusion) in the `EXPLAIN` reference.

### Vector indexes over record IDs that hold a number

An HNSW or DiskANN index written by `3.1.0-beta.1` through `3.2.4` over records whose IDs hold a number inside an array or object, such as `chunk:[42, 'intro']` or `doc:{tenant: 1}`, keeps a stale doc-ID mapping for those records. A kNN query leaves them out without an error and returns fewer records than the `k` it asked for. The fault comes from those releases rather than from the upgrade. Upgrading does not repair the mapping, and running `REBUILD INDEX <name> ON <table>` once per affected index does.

### Record IDs that differ only in a nested number's type

Two records whose IDs differ only in the numeric type of a nested value, such as `c:[1]` and `c:[1dec]`, or `c:{k: 1}` and `c:{k: 1f}`, share one doc-ID in any index built before `3.3`, so one of the two is missing from that index's results. A rebuild on `3.3` separates them in a full-text index only. A standard, HNSW or DiskANN index still treats them as one record however it was built, so one of the two stays missing and a kNN query can return the other twice. A `UNIQUE` index is unaffected, because its entries are keyed by the indexed values alone.

### Datastores written by a 3.3.0 pre-release

A datastore written by a `3.3.0` beta can carry index damage that no stable release leaves behind, wherever an indexed record's ID holds a number inside an array or object. Once every node runs `3.3.0`, running `REBUILD INDEX` once for each full-text, HNSW or DiskANN index on such a table repairs the mapping.

### Graph adjacency caches start empty

The adjacency caches that [`INLINE EDGES` and `INLINE REFERENCES`](/docs/reference/query-language/statements/define/table.md#using-inline-edges-and-inline-references-to-cache-adjacency) add in `3.3` are set per table, so no table defined on `3.2` has one. Once a table has the clause, each record's cache is built the first time one of its edges or references is written, and until then a traversal from that record scans its keys as before.

## Clusters and rolling upgrades

A cluster can be upgraded one node at a time, but `3.2` and `3.3.0` nodes do not fully interoperate while both are serving. These limits apply until every node runs `3.3.0`:

- **HNSW and DiskANN indexes.** Once an upgraded node writes to an HNSW or DiskANN index, a `3.2` node fails kNN queries against that index, writes to any record the index already has an update queued for, and its own compaction of that queue. The failures come and go as compaction drains the queue and later writes refill it, so a quiet interval does not mean they have stopped. Upgrading every node ends them, and so does stopping both queries and writes to those indexes from the older nodes, while routing only reads away is not enough. No durable data is harmed, and an older node recovers once it is upgraded.
- **Index builds.** A build still running while a `3.2` node serves writes can finish with wrong entries for records whose IDs hold a number inside an array or object: a `COUNT` index can count such a record twice or not at all, full-text relevance statistics drift, and a `UNIQUE` build can fail by reporting the record as a duplicate of itself. Letting builds finish before the rollout avoids this, and so does running `REBUILD INDEX` on any build that was in flight once every node runs `3.3.0`. A rebuild started while an older node still serves writes is exposed to the same problem.
- **Sequences.** A sequence defined during the rollout is visible to `3.3.0` nodes only, as described in [Sequence definitions move out of the table keyspace](#sequence-definitions-move-out-of-the-table-keyspace).
- **Silo modules.** Older nodes stop resolving silo modules once migration 2 has run, as described in [Silo module definitions are rekeyed](#silo-module-definitions-are-rekeyed).
- **Schema changes.** `3.3.0` stores user, access method, analyzer, table, field and index definitions in newer encodings than `3.2`, and a `3.2` node that reads a definition written by a `3.3.0` node fails with an `Invalid revision` error. This includes a table that a `3.3.0` node creates implicitly on its first write, so schema changes and writes to new tables are safest once every node is upgraded.

## Startup and health checks

From `3.3.0`, the startup work that follows opening the datastore runs in the background: the version check and data migrations, the default namespace and database, the root credentials, node registration and any import set with `--import-file`. With no import configured, the server waits up to 5 seconds for that work before it accepts connections, so an ordinary start is ready as soon as it accepts them. When the work takes longer, or an import is configured, the server accepts connections anyway, answers every path except `/`, `/status`, `/health`, `/version`, `/ready` and `/metrics` with `503 Service Unavailable` and `Retry-After: 1`, and reports `503` from `/ready` until startup finishes. In `3.2.4`, the version check, the defaults and node registration ran before the server accepted connections, so a slow start refused them instead.

This changes what the health endpoints report:

- [`/health`](/docs/reference/rest-api/http-protocol.md#health) answers during startup, so a `200` means that the storage engine responds, not that startup has finished. [`/ready`](/docs/reference/rest-api/http-protocol.md#ready) is the endpoint that reports whether it has.
- `/ready` returns `503` rather than `500` when the node's own cluster membership record is missing.
- A failure to initialise the datastore stops the process. A failed import or credential initialisation leaves a running server that never becomes ready, which `/ready` reports and `/health` does not.

## SurrealQL behaviour changes

### In-memory storage no longer supports `VERSION`

The in-memory backend (`mem://`) no longer supports [`VERSION` clauses](/docs/reference/query-language/statements/select.md#the-version-clause), and a server started on it with `versioned` set to `true` or a non-zero `retention`, whether through the options or the matching `SURREAL_DATASTORE_VERSIONED` or `SURREAL_DATASTORE_RETENTION` variable, fails to start. `versioned=false` and a zero retention are accepted. In Rust, `Surreal::new::<Mem>(()).versioned()` fails when the client is created rather than at the first `VERSION` query. SurrealKV and RocksDB support versioned reads when started with `?versioned=true`, as described under [Datastore configuration](/docs/reference/cli/db/commands/start.md#datastore-configuration) and [About SurrealMX](/docs/running/in-memory.md#about-surrealmx).

### `UPDATE` and `UPSERT` evaluate `WHERE` first

`UPDATE` and `UPSERT` check the `WHERE` condition before they evaluate the data clause, so a data clause with side effects runs only for the records the condition matches, and a data clause that would be rejected no longer raises an error for a record the condition excludes. Both clauses read one image of the record taken before the statement writes, and computed fields are part of that image. In `3.2`, a full table scan evaluated the data clause for every scanned record, and a computed field read `NONE` in the data clause while holding its value in the condition. [Evaluation order](/docs/reference/query-language/statements/update.md#evaluation-order) and [One image of the record](/docs/reference/query-language/statements/update.md#one-image-of-the-record) cover `UPDATE`, and the [`UPSERT` page](/docs/reference/query-language/statements/upsert.md#evaluation-order) has the same section.

### Field clauses run in dependency order

When a record is written, its `DEFAULT`, `VALUE` and `COMPUTED` clauses run in dependency order, so a field whose clause reads another field is evaluated after that field, where `3.2` evaluated fields in name order. `ASSERT` clauses run in a second pass once every field holds its final value. Events, live queries and changefeeds now receive computed fields in `$before` and `$after`, which `3.2` reported as `NONE`. See [Field evaluation order](/docs/reference/query-language/statements/define/field.md#field-evaluation-order).

### Function changes

- The seven `duration::set_*` functions and `object::matches` were accepted by the parser but never implemented, so a call failed when it ran. They are now parse errors, which means that a query naming one of them fails as a whole before any of its statements runs.
- Vector functions return an error instead of `NaN` for input they cannot compute: [`vector::divide`](/docs/reference/query-language/functions/database-functions/vector.md#vectordivide) with a zero in the divisor, [`vector::normalize`](/docs/reference/query-language/functions/database-functions/vector.md#vectornormalize), [`vector::angle`](/docs/reference/query-language/functions/database-functions/vector.md#vectorangle) and [`vector::project`](/docs/reference/query-language/functions/database-functions/vector.md#vectorproject) with a zero-magnitude vector, and [`vector::similarity::pearson`](/docs/reference/query-language/functions/database-functions/vector.md#vectorsimilaritypearson) with degenerate input such as a constant vector.
- [`vector::similarity::jaccard`](/docs/reference/query-language/functions/database-functions/vector.md#vectorsimilarityjaccard) treats its inputs as sets, so `vector::similarity::jaccard([1, 2], [2, 2])` returns `0.5` where `3.2` returned `1.0`.
- `vector::distance::chebyshev([], [])` and `vector::magnitude([])` return `0`, where `3.2` returned the most negative float and `-0f` respectively.
- [`time::nano`](/docs/reference/query-language/functions/database-functions/time.md#timenano) returns an error for a datetime outside the years 1677 to 2262, which a 64-bit nanosecond count cannot represent, instead of returning `0`.

### `ACCESS` statements on a missing access method

[`ACCESS … REVOKE`](/docs/reference/query-language/statements/access.md#revoke) and [`ACCESS … PURGE`](/docs/reference/query-language/statements/access.md#purge) on an access method that does not exist return an error such as `The access method 'x' does not exist in the database 'y'`, where `3.2` returned an empty result. `PURGE` also no longer creates a missing namespace, and reports that the namespace does not exist instead.

### ISO GQL (Cypher Query Language) is on by default

GQL no longer needs an experimental capability. On `3.2.x` it was reached with `--allow-experimental gql`; from `3.3.0` it is available without one, and the `gql` experimental tag is still accepted but does nothing.

A deployment that served no GQL on `3.2.x` by leaving the experimental tag unset now serves it, so closing it is an explicit step. The executor has four entry surfaces with separate switches, listed under [GQL entry surfaces](/docs/learn/security/authorization/capabilities.md#gql-entry-surfaces).

The [`eval::gql()`](/docs/reference/query-language/functions/database-functions/eval.md) function is the exception: eval stays denied for every subject by default, including under `--allow-all`.

## Permissions

### Writes in permission clauses

A `FOR create`, `FOR update` or `FOR delete` [permission clause](/docs/reference/query-language/statements/define/table.md#writes-inside-a-permission-clause) may perform writes, such as recording an audit entry, which `3.2.0` to `3.2.4` refused when the table was defined. A `FOR select` clause that writes is accepted by `DEFINE` and fails when a read evaluates it, with `A PERMISSIONS clause cannot contain a statement that modifies data`.

### `COMPUTED` fields that write

`DEFINE FIELD` refuses a `COMPUTED` body that writes directly, with an error saying that `COMPUTED` bodies must be read-only. A body that reaches a write through a function call is accepted and fails when the field is read, with `A COMPUTED clause cannot contain a statement that modifies data`. On `3.2.4`, a writing `COMPUTED` body performed its write on every read. A `COMPUTED` body also runs with the reading user's permissions, capped at those of the user who defined the field. See [Restrictions on computed fields](/docs/reference/query-language/statements/define/field.md#restrictions-on-computed-fields) and [A computed body is capped at the definer's permissions](/docs/reference/query-language/statements/define/field.md#a-computed-body-is-capped-at-the-definers-permissions).

## Security-related behaviour changes

Several security fixes in `3.3.0` change what a request is allowed to do, or how a refusal is reported:

- **Nesting depth.** A value nested more than 256 levels deep, including inside a record ID, is rejected when written, with `The value is nested too deeply`. A deeper value that is already stored returns the same error when read, where `3.2` could stop the server. See [Nesting depth](/docs/reference/query-language/language-primitives/data-types/values.md#nesting-depth).
- **Cross-tenant refusals.** A request for a namespace or database outside the caller's access is refused with the `NotAllowed` error kind, which is HTTP `403`, gRPC `PERMISSION_DENIED` and Postgres `42501`, in place of HTTP `400`, an internal error and `XX000`. The JSON-RPC error code `-32000` and the message are unchanged.
- **`USE` checks.** The [`USE`](/docs/reference/query-language/statements/use.md) statement, the RPC [`use`](/docs/reference/rest-api/rpc-protocol.md#use) method and `USE DEFAULT` are checked against the level the session authenticated at, and a system user who asks for a namespace or database outside that level is refused with `You don't have permission to change to the … namespace`. Root users and anonymous sessions are unaffected.
- **HTTP headers.** `Surreal-NS` and `Surreal-DB` headers that name a namespace or database outside the authenticated level are rejected with `401` during authentication.
- **`MATCHES` on restricted fields.** A [`MATCHES`](/docs/reference/query-language/language-primitives/operators.md#matches) (`@@`) condition on a field that the caller has no `SELECT` permission for is refused with `There was no suitable index supporting the expression`, so a full-text index no longer reveals values the caller cannot read.
- **Refresh token revocation.** The RPC `revoke` method verifies the refresh token it is given, and any failure returns an authentication error, so revoking a token requires the token itself. See [`WITH REFRESH`](/docs/reference/query-language/statements/define/access/record.md#with-refresh-token).
- **Access method identities.** A session authenticated through a JWT or bearer access method at root, namespace or database level is identified by the method and the token's `sub` claim, as in `api/alice`, rather than by the method alone. Audit records and other places that name the session's principal change accordingly, and a token without `sub` is still identified by the method name.
- **Network capabilities.** A port-specific `--deny-net` rule also applies when the host is named another way, and to JWKS URLs that rely on a default port. IPv6 transition addresses (NAT64, 6to4 and Teredo) count as private when the IPv4 address they embed is private. See [Network](/docs/learn/security/authorization/capabilities.md#network).
- **SCRAM for Postgres clients.** System users defined on `3.2` have no SCRAM-SHA-256 verifier, so the Postgres wire protocol listener added in `3.3.0` authenticates them with a cleartext password until the password is set again with `PASSWORD` or a verifier is imported with [`PASSSCRAM`](/docs/reference/query-language/statements/define/user.md#importing-a-scram-verifier-with-passscram).

## Configuration and metrics

### The query timeout bounds the whole request

When [`--query-timeout`](/docs/reference/cli/db/environment-variables.md#command-environment-variables) is set, it is also a wall-clock limit on the whole request for `POST /sql`, the `/sql` WebSocket, `/gql`, `/graphql` and every RPC method over WebSocket, HTTP `/rpc` and gRPC, including `signin`, `signup` and `authenticate`. The RPC `begin`, `commit` and `cancel` methods and GraphQL subscriptions are exempt. A request to `/sql` or `/gql` that runs out of time returns `504 Gateway Timeout` where `3.2` returned `400`. The limit applies only when the option is set. See [Request timeouts](/docs/reference/rest-api/http-protocol.md#request-timeouts).

### Renamed and changed settings

- `SURREAL_FTS_DOC_IDS_BATCH_SIZE` is renamed [`SURREAL_TABLE_DOC_IDS_BATCH_SIZE`](/docs/reference/cli/db/environment-variables.md#batch-config), with the same default of 1000, because the doc-ID space now belongs to the table rather than to one full-text index. The old name is no longer read.
- A zero value for `--node-membership-check-interval`, `--node-membership-cleanup-interval`, `--changefeed-gc-interval` or `--reclaim-interval` disables that background job. On `3.2.4`, a zero value stopped the server at startup.
- The storage thread pool no longer pins its workers to CPU cores. `3.2.4` pinned one worker per core on hosts with 16 or more cores when the pool size equalled the core count, which is the default. `SURREAL_KVS_THREADPOOL_SIZE` is unchanged.

### Metrics and `INFO`

- `surrealdb_transaction_total` labels a rolled-back transaction `outcome="cancelled"`, where `3.2` counted it as `success`. Read-only work and internal probes end in a rollback, so `outcome="success"` reads noticeably lower after the upgrade and now counts only committed transactions. `outcome=~"success|cancelled"` counts every finished transaction, as `outcome="success"` did before. See [Transaction metrics](/docs/manage/observability/metrics.md#transaction---surrealdbtransaction).
- The `building` object that [`INFO FOR INDEX`](/docs/reference/query-language/statements/info.md#index-information) returns always includes a `compacting` boolean.

### Binary size

Released binaries, the Homebrew package and the Docker images embed the Chinese, Japanese and Korean dictionaries for the [`SEGMENT` tokenizer](/docs/reference/query-language/statements/define/analyzer.md#segmentlanguage), which adds about 150 MB: 82 MB for Korean, 45 MB for Japanese and 22 MB for Chinese. The npm WebAssembly package, the Nix build and the default features of the Rust crates do not include them. A build without them can load [dictionaries](/docs/reference/query-language/statements/define/analyzer.md#dictionaries) from the directory set by [`SURREAL_SEGMENT_DICTIONARY_PATH`](/docs/reference/cli/db/environment-variables.md#file-config), and a `DEFINE ANALYZER` that names a language with no dictionary fails.

## SDKs and embedding

### JavaScript

The embedded engines are now built from the SurrealDB repository and published at the engine's version as `@surrealdb/node-native` and `@surrealdb/wasm-native`. Applications keep installing [`@surrealdb/node` and `@surrealdb/wasm`](/docs/reference/javascript/concepts/embedded-engines.md), which depend on them. The Node.js native binary, about 45 MB, arrives through a per-platform `@surrealdb/node-native-<platform>` package listed as an optional dependency, so an install that omits optional dependencies has no binary to load.

The `strict` option, which the Node.js engine accepted and ignored, is removed, so TypeScript code that still passes it fails type checking.

### Rust

- Futures returned by the SDK are `Send` but no longer `Sync`: a pending future can move to another thread, but a reference to it cannot be shared between threads.
- Engines implement the `SurrealEngine` trait from the new `surrealdb-engine-api` crate. The crate carries no stability guarantee and matters only to code that implements an engine.
- `surrealdb-core` no longer re-exports items from `surrealdb-rpc` under `surrealdb_core::rpc` or its `dbs`, `api`, `iam` and `kvs::export` modules, so code that used them depends on `surrealdb-rpc` directly.
- The `surrealdb-mcp` crate re-exports types from version 3 of `rmcp`.

Code that [embeds](/docs/reference/rust/embedding.md) `surrealdb-core` directly also sees these changes to the `Datastore` API:

- `Datastore::transaction` no longer takes a `LockType` argument.
- `get_capabilities()` returns `Arc<Capabilities>`, and `set_capabilities()` replaces the capabilities of a running datastore.
- Builders return `Arc<Datastore>`, and background maintenance starts with the datastore unless the builder calls `without_maintenance_tasks()`. The signatures of `run_datastore_router` and `unstable_from_datastore` change to match.
- `execute_with_cancel`, `execute_with_transaction` and `process_plan` are replaced by a `QueryRequest` passed to `Datastore::run` or `Datastore::run_streaming`, while `execute` and `process` remain.

### Surrealism modules (experimental)

[`DEFINE MODULE`](/docs/reference/query-language/statements/define/module.md#statement-syntax) changes in three ways that affect statements written for `3.2`:

- The module file follows `FROM` instead of `AS`, as in `DEFINE MODULE mod::color FROM f"modules:/color.surli" UNSIGNED;`.
- The [`UNSIGNED`](/docs/reference/query-language/statements/define/module.md#the-unsigned-keyword) keyword is required.
- A [silo package](/docs/reference/query-language/statements/define/module.md#silo-packages) path puts `::` before the version, as in `silo::acme::widgets::<1.2.3>`, and packages are fetched over HTTPS from the endpoint set by `SURREAL_SURREALISM_SILO_ENDPOINT`.

Silo modules that are already stored are moved to their new key by migration 2, as described in [Silo module definitions are rekeyed](#silo-module-definitions-are-rekeyed).

## SurrealDB Enterprise

- **Licence key.** An Enterprise node needs a valid licence key, set with `SURREAL_LICENSE_KEY` or `SURREAL_LICENSE_KEY_FILE`, and a node without one refuses to start. The node validates the key online against `api.keygen.sh`, so the first start after the upgrade needs to reach it. A `3.3.0-beta.4` node cannot validate a production licence, so beta.4 nodes need upgrading before one is used. The [Enterprise Edition](/docs/manage/enterprise.md) page describes licensing under Licence key.
- **`SURREAL_REMOTE_BUCKETS_READONLY` is removed.** Remote buckets that it made read-only become writable, so a bucket that needs to stay read-only takes the `READONLY` clause of [`DEFINE BUCKET`](/docs/reference/query-language/statements/define/bucket.md#statement-syntax).

Changes to SurrealDS clusters are listed in the [release notes](/releases).

---

Source: https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/overview

# Upgrading

This guide will help you upgrade your current SurrealDB installation to a newer release.

When moving to a new version of SurrealDB, follow the upgrade instructions so that your data is migrated correctly. This page contains information on how to upgrade your SurrealDB installation.

## Upgrading in general

Upgrading the SurrealDB server itself is done through a single command: [`surreal upgrade db`](/docs/reference/cli/db/commands/upgrade.md). For minor versions, a read through the [release notes](/releases) will suffice to see which breaking changes may exist.

To see the exact PRs that were merged between two versions, you can compare the changes between one version tag and another on GitHub. For example, [comparing `v3.0.0` with `v3.0.2`](https://github.com/surrealdb/surrealdb/compare/v3.0.0...v3.0.2) shows which PRs were merged between those two versions.

As the tags themselves are contained in the url, you can simply replace the numbers to see the details between one version and another.

```bash
# Changes between 3.0.0 and 3.0.2
https://github.com/surrealdb/surrealdb/compare/v3.0.0...v3.0.2

# Changes between 2.4.0 and 2.6.2
https://github.com/surrealdb/surrealdb/compare/v2.4.0...v2.6.2
```

Upgrading between major versions (currently 1.x to 2.x and 2.x to 3.x) contains more structural changes and breaking changes, so be sure to see the dedicated pages when upgrading to 2.x or 3.x.

---

Source: https://surrealdb.com/docs/build/migrating/from-other-databases/from-mongodb

# Migrating from MongoDB

How to map existing data and concepts from MongoDB to SurrealDB

This page details some MongoDB data types and their SurrealQL equivalents, followed by links to the [Surreal Sync](https://github.com/surrealdb/surreal-sync/) tool which allows data from MongoDB to be automatically imported to SurrealDB.

## Data types

The following chart shows MongoDB data types along with the equivalent or near-equivalent [SurrealQL data type](/docs/reference/query-language/language-primitives/data-types.md) for each.

|     MongoDB Data Type     |        BSON Type        |                                          JSON Extended Format v2                                           | SurrealDB Mapping |
| ------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------- |
| **Double**                | Double                  | `3.14` (Relaxed) or `{"$numberDouble": "3.14"}` (Canonical)                                                | `float`           |
| **String**                | String                  | `"text"`                                                                                                   | `string`          |
| **Object**                | Document                | `{"key": "value"}`                                                                                         | `object`          |
| **Array**                 | Array                   | `[1, 2, 3]`                                                                                                | `array`           |
| **Binary data**            | Binary                  | `{"$binary": {"base64": "...", "subType": "..."}}`                                                         | `bytes`           |
| **Undefined**             | Undefined               | `{"$undefined": true}`                                                                                     | `none`            |
| **ObjectId**              | ObjectId                | `{"$oid": "507f1f77bcf86cd799439011"}`                                                                     | `string`          |
| **Boolean**               | Boolean                 | `true`/`false`                                                                                             | `bool`            |
| **Date**                  | DateTime                | `{"$date": "2024-01-01T00:00:00Z"}` (Relaxed) or `{"$date": {"$numberLong": "1672531200000"}}` (Canonical) | `datetime`        |
| **Null**                  | Null                    | `null`                                                                                                     | `null`            |
| **Regular Expression**    | RegularExpression       | `{"$regularExpression": {"pattern": "...", "options": "..."}}`                                             | `regex`           |
| **DBPointer**             | DbPointer               | `{"$dbPointer": {"$ref": "...", "$id": {...}}}`                                                            | `string`          |
| **JavaScript**            | JavaScriptCode          | `{"$code": "function(){}"}`                                                                                | `string`          |
| **Symbol**                | Symbol                  | `{"$symbol": "text"}`                                                                                      | `string`          |
| **JavaScript with scope** | JavaScriptCodeWithScope | `{"$code": "...", "$scope": {...}}`                                                                        | `object`          |
| **32-bit integer**        | Int32                   | `42` (Relaxed) or `{"$numberInt": "42"}` (Canonical)                                                       | `int`             |
| **Timestamp**             | Timestamp               | `{"$timestamp": {"t": 1672531200, "i": 1}}`                                                                | `datetime`        |
| **64-bit integer**        | Int64                   | `{"$numberLong": "123"}`                                                                                   | `int`             |
| **Decimal128**            | Decimal128              | `{"$numberDecimal": "123.45"}`                                                                             | `number`          |
| **DBRef**                 | Document                | `{"$ref": "users", "$id": "123"}`                                                                          | `thing`           |
| **Min key**               | MinKey                  | `{"$minKey": 1}`                                                                                           | `object`          |
| **Max key**               | MaxKey                  | `{"$maxKey": 1}`                                                                                           | `object`          |

## Importing from MongoDB using Surreal Sync

Surreal Sync can be used to export MongoDB collections to SurrealDB.

It supports inconsistent full syncs and consistent incremental syncs, and together these provide the ability to reproduce consistent snapshots from the source MongoDB collections onto the target SurrealDB tables.

For more on how to import data from MongoDB to SurrealDB, please see the following pages in the Surreal Sync repo.

* [Surreal Sync for MongoDB](https://github.com/surrealdb/surreal-sync/blob/main/docs/mongodb.md)
* [MongoDB Data Types Support in Surreal Sync](https://github.com/surrealdb/surreal-sync/blob/main/docs/mongodb-data-types.md)

---

Source: https://surrealdb.com/docs/build/migrating/from-other-databases/from-mysql

# Migrating from MySQL

How to map existing data and concepts from PostgreSQL to SurrealDB

This page details some MySQL data types and their SurrealQL equivalents, followed by links to the [Surreal Sync](https://github.com/surrealdb/surreal-sync/) tool which allows data from MySQL to be automatically imported to SurrealDB.

## Data types

The following chart shows MySQL data types along with the equivalent or near-equivalent [SurrealQL data type](/docs/reference/query-language/language-primitives/data-types.md) for each.

|   MySQL Data Type   | Wire Protocol Type |               SQL Representation                | SurrealDB Mapping |                     Notes                      |
| ------------------- | ------------------ | ----------------------------------------------- | ----------------- | ---------------------------------------------- |
| **BOOLEAN/BOOL**    | Tiny               | `0`/`1`                                         | `bool`            | 0=false, 1=true conversion                     |
| **TINYINT**         | Tiny               | `-128` to `127`                                 | `int`             | Converted to 64-bit integer                    |
| **SMALLINT**        | Short              | `-32768` to `32767`                             | `int`             | Converted to 64-bit integer                    |
| **MEDIUMINT**       | Int24              | `-8388608` to `8388607`                         | `int`             | Converted to 64-bit integer                    |
| **INT/INTEGER**     | Long               | `-2147483648` to `2147483647`                   | `int`             | Converted to 64-bit integer                    |
| **BIGINT**          | LongLong           | `-9223372036854775808` to `9223372036854775807` | `int`             | Direct conversion                              |
| **FLOAT**           | Float              | `3.14`                                          | `float` (f64)     | Converted to double precision                  |
| **DOUBLE**          | Double             | `3.141592653589793`                             | `float` (f64)     | Direct conversion                              |
| **DECIMAL/NUMERIC** | NewDecimal         | `123.45`                                        | `number`          | Converted to SurrealDB Number with precision   |
| **CHAR(n)**         | String             | `'text'`                                        | `string`          | Fixed-length, padding removed                  |
| **VARCHAR(n)**      | VarString          | `'text'`                                        | `string`          | Variable-length string                         |
| **TEXT**            | Blob               | `'long text'`                                   | `string`          | Text blob as string                            |
| **TINYTEXT**        | TinyBlob           | `'short text'`                                  | `string`          | Small text as string                           |
| **MEDIUMTEXT**      | MediumBlob         | `'medium text'`                                 | `string`          | Medium text as string                          |
| **LONGTEXT**        | LongBlob           | `'very long text'`                              | `string`          | Large text as string                           |
| **BINARY(n)**       | String             | `0x48656c6c6f`                                  | `bytes`           | Fixed-length binary data                       |
| **VARBINARY(n)**    | VarString          | `0x48656c6c6f`                                  | `bytes`           | Variable-length binary data                    |
| **BLOB**            | Blob               | `0x48656c6c6f`                                  | `bytes`           | Binary large object                            |
| **TINYBLOB**        | TinyBlob           | `0x48656c6c6f`                                  | `bytes`           | Small binary data                              |
| **MEDIUMBLOB**      | MediumBlob         | `0x48656c6c6f`                                  | `bytes`           | Medium binary data                             |
| **LONGBLOB**        | LongBlob           | `0x48656c6c6f`                                  | `bytes`           | Large binary data                              |
| **DATE**            | Date               | `'2024-01-15'`                                  | `datetime`        | Converted to datetime at midnight UTC          |
| **TIME**            | Time               | `'14:30:00'`                                    | `string`          | Time format as string (HH:MM:SS.microseconds)  |
| **DATETIME**        | DateTime           | `'2024-01-15 14:30:00'`                         | `datetime`        | Converted to UTC datetime                      |
| **TIMESTAMP**       | Timestamp          | `'2024-01-15 14:30:00'`                         | `datetime`        | Timezone-aware, converted to UTC               |
| **YEAR**            | Year               | `2024`                                          | `int`             | Year as integer                                |
| **JSON**            | Json               | `'{"key": "value"}'`                            | `object`          | Parsed and converted recursively               |
| **GEOMETRY**        | Geometry           | `ST_GeomFromText('POINT(1 2)')`                 | `object`          | Converted to geometric object with coordinates |
| **POINT**           | Geometry           | `POINT(1.5, 2.5)`                               | `object`          | Converted to `{"x": 1.5, "y": 2.5}` object     |
| **ENUM**            | Enum               | `'option1'`                                     | `string`          | Enum value as string, constraints lost         |
| **SET**             | Set                | `'value1,value2'`                               | `array`           | Converted to array of strings                  |
| **BIT(n)**          | Bit                | `b'1010'`                                       | `string`          | Bit string as binary string representation     |

## Importing from MySQL using Surreal Sync

Surreal Sync can be used to export MySQL tables to SurrealDB.

It supports inconsistent full syncs and consistent incremental syncs, and together these provide the ability to reproduce consistent snapshots from the source MySQL tables onto the target SurrealDB tables.

For more on how to import data from MySQL to SurrealDB, please see the following pages in the Surreal Sync repo.

* [Surreal Sync for MySQL](https://github.com/surrealdb/surreal-sync/blob/main/docs/mysql.md)
* [MySQL Data Types Support in Surreal Sync](https://github.com/surrealdb/surreal-sync/blob/main/docs/mysql-data-types.md)

---

Source: https://surrealdb.com/docs/build/migrating/from-other-databases/from-neo4j

# Migrating from Neo4j

How to map existing data and concepts from Neo4j to SurrealDB

This page details some Neo4j data types and patterns in the Cypher query language along with their SurrealQL equivalents or near equivalents, followed by links to the [Surreal Sync](https://github.com/surrealdb/surreal-sync/) tool which allows data from Neo4j to be automatically imported to SurrealDB.

## Concept mapping

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Neo4j</th>
            <th colspan="2" scope="col">SurrealDB</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Neo4j">
                database
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                database
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Neo4j">
                node label
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                table
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Neo4j">
                node
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                record
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Neo4j">
                node property
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                field
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Neo4j">
                index
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                index
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Neo4j">
                id
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                record id
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Neo4j">
                transactions
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                transactions
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Neo4j">
                relationships
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                record links, embedding and graph relations
            </td>
        </tr>
    </tbody>
</table>

## Data types

The following chart shows Neo4j data types along with the equivalent or near-equivalent SurrealQL data type for each.

| Neo4j Data Type    | SurrealDB Mapping | Notes                                                                                     |
|--------------------|-------------------|-------------------------------------------------------------------------------------------|
| **Boolean**        | `bool`            |                                                                         |
| **Integer**        | `int`             |                                                                     |
| **Float**          | `float` (f64)     |                                                                          |
| **String**         | `string`          |                                                                          |
| **List**           | `array`           |                                              |
| **Map**            | `object`          |                                                |
| **Null**           | `null`            |                                                                         |
| **Date**           | `datetime`        | Convert to UTC datetime (assuming local timezone)                                        |
| **DateTime**       | `datetime`        | Convert to UTC datetime                                                                 |
| **LocalDateTime**  | `datetime`        | Convert to UTC datetime (assuming UTC)                                                   |
| **Duration**       | `duration`        |                                                                          |
| **Bytes**          | `bytes`           |                                                                          |
| **Time**           | `object`          | Convert to object with `type: "$Neo4jTime"`, hour, minute, second, nanosecond, offset_seconds fields |
| **LocalTime**      | `object`          | Convert to object with `type: "$Neo4jLocalTime"`, hour, minute, second, nanosecond fields |
| **Point2D**        | `object`          | Convert to GeoJSON-like object with `type: "Point"`, `srid` (4326), `coordinates: [longitude, latitude]`            |
| **Point3D**        | `object`          | Convert to GeoJSON-like object with `type: "Point"`, `srid` (4979), `coordinates: [longitude, latitude, elevation]` |
| **DateTimeZoneId** | `datetime`        | Convert to UTC datetime using embedded timezone ID                                      |

## Syntax mapping

The following shows some CRUD examples using SurrealQL syntax.

### Create

As Neo4j is schemaless, only the SurrealQL schemaless approach is shown below. For a schemafull option see the [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) page.

For more SurrealQL examples, see the [`CREATE`](/docs/reference/query-language/statements/create.md), [`INSERT`](/docs/reference/query-language/statements/insert.md) and [`RELATE`](/docs/reference/query-language/statements/relate.md) pages.

Simple create/insert operations:

```cypher
// Cypher
CREATE (John:Person {name:‘John’}), (Jane:Person {name: ‘Jane’})
```

```surql
-- SurrealQL
-- Table implicitly created if it doesn't exist
INSERT INTO person [ {id: “John”, name: “John”}, {id: “Jane”, name: “Jane”} ]
```

Graph relations via the [RELATE](/docs/reference/query-language/statements/relate.md) statement:

```cypher
// Cypher
MATCH (p:Person {name:‘Jane’}), (pr:Product {name:‘iPhone’}) CREATE (p)-[:ORDER]->(pr)
```

```surql
-- SurrealQL
RELATE person:Jane->order->product:iPhone
```

Defining an index:

```cypher
// Cypher
CREATE INDEX personNameIndex FOR (p:Person) ON (p.name)
```

```surql
-- SurrealQL
DEFINE INDEX idx_name ON TABLE person FIELDS name
```

### Read

For more SurrealQL examples, see the [`SELECT`](/docs/reference/query-language/statements/select.md), [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md) and [`RETURN`](/docs/reference/query-language/statements/return.md) pages.

Returning all the fields of a table:

```cypher
// Cypher
MATCH (p:Person) RETURN p
```

```surql
-- SurrealQL
SELECT * FROM person
```

Returning a single field of a table:

```cypher
// Cypher
MATCH (p:Person) RETURN p.name
```

```surql
-- SurrealQL
SELECT name FROM person
```

Using the `WHERE` clause to return certain records:

```cypher
// Cypher
MATCH (p:Person) WHERE p.name = “Jane” RETURN p.name
```

```surql
-- SurrealQL
SELECT name FROM person WHERE name = “Jane”
```

Using `EXPLAIN` to detail the query plan used:

```cypher
// Cypher
EXPLAIN MATCH (p:Person) WHERE p.name = "Jane" RETURN p.name
```

```surql
-- SurrealQL
SELECT name FROM person WHERE name = "Jane" EXPLAIN
```

Grouping and counting the number of records returned:

```cypher
// Cypher
MATCH (p:Person) RETURN count(*) as person_count
```

```surql
-- SurrealQL
SELECT count() AS person_count FROM person GROUP ALL
```

See all distinct values for a field among the records of a table:

```cypher
// Cypher
MATCH (p:Person) RETURN distinct p.name
```

```surql
-- SurrealQL
SELECT array::distinct(name) FROM person GROUP ALL
```

Returning up to a certain number of records:

```cypher
// Cypher
MATCH (p:Person) RETURN p LIMIT 10
```

```surql
-- SurrealQL
SELECT * FROM person LIMIT 10
```

See which `person` records have ordered a `product` via the `order` graph edge:

```cypher
// Cypher
MATCH (p:Person)-[:ORDER]->(pr:Product) RETURN p.name, pr.name
```

```surql
-- SurrealQL
SELECT name, ->order->product.name FROM person
```

Following a relationship a fixed number of times. SurrealQL uses a [recursive path](/docs/reference/query-language/language-primitives/idioms.md#recursive-paths), where `.{2}` repeats the path in parentheses twice:

```cypher
// Cypher
MATCH (:Person {id: 'a'})-[:KNOWS*2]->(p:Person) RETURN p
```

```surql
-- SurrealQL
person:a.{2}(->knows->person)
```

Following a relationship a variable number of times, returning every person reached. `+collect` gathers each person once, however many paths lead to them:

```cypher
// Cypher: from one to three hops, or any number with *1..
MATCH (:Person {id: 'a'})-[:KNOWS*1..3]->(p:Person) RETURN DISTINCT p
```

```surql
-- SurrealQL: from one to three hops, or any number with .{..+collect}
person:a.{1..3+collect}(->knows->person)
```

A path without an upper bound stops at a depth of 256, which is the fixed limit for recursive paths.

Following a relationship in either direction. `<->` returns both ends of each edge, and that includes the starting record, so filter it out to match Cypher:

```cypher
// Cypher
MATCH (:Person {id: 'c'})-[:KNOWS]-(p:Person) RETURN p
```

```surql
-- SurrealQL
(person:c<->knows<->person).filter(|$p| $p != person:c)
```

Returning every node along a path, filtered by a property on the first node. Cypher returns `a`, `b` and `c` as separate columns of one row per path. SurrealQL returns one result per starting record, with each level nested inside the one before it, using [destructuring](/docs/reference/query-language/language-primitives/idioms.md#destructuring):

```cypher
// Cypher
MATCH (s:Service {env: 'prod'})-[:RUNS_ON]->(h:Host)-[:USES]->(d:Disk) RETURN s, h, d
```

```surql
-- SurrealQL
SELECT
    name,
    ->runs_on->host.{ name, region, disks: ->uses->disk.{ id, size } } AS hosts
FROM service WHERE env = 'prod';
```

```surql title="Possible output"
[
	{
		hosts: [
			{ disks: [{ id: disk:d1, size: 100 }, { id: disk:d2, size: 500 }], name: 'h1', region: 'eu' },
			{ disks: [{ id: disk:d3, size: 50 }], name: 'h2', region: 'us' }
		],
		name: 'api'
	}
]
```

The order of the hosts depends on the IDs of the edges, which are random unless you set them. To filter the nodes at any level, as a Cypher `WHERE` on `h` or `d` would, put the condition in parentheses after the table name in the path:

```surql
-- SurrealQL: only hosts in the eu region, and only their disks over 80
SELECT
    name,
    ->runs_on->(host WHERE region = 'eu').{ name, disks: ->uses->(disk WHERE size > 80).size } AS hosts
FROM service WHERE env = 'prod';
```

```surql title="Output"
[
	{
		hosts: [{ disks: [100, 500], name: 'h1' }],
		name: 'api'
	}
]
```

To get the records at the end of the path without the structure, leave out the destructuring: `service:api->runs_on->host->uses->disk` returns the disks as one flat array.

### Update

For more SurrealQL examples, see the [`UPDATE`](/docs/reference/query-language/statements/update.md) page.

Conditionally updating records that have a certain value for a field:

```cypher
// Cypher
MATCH (p:Person)  WHERE p.name = "Jane"  SET p.last_name = 'Doe'  RETURN p
```

```surql
-- SurrealQL
UPDATE person SET last_name = "Doe" WHERE name = "Jane"
```

Unsetting (removing) the value of a field for certain records:

```cypher
// Cypher
MATCH (p:Person)   WHERE p.name = "Jane"   REMOVE p.last_name RETURN p
```

```surql
-- SurrealQL
UPDATE person UNSET last_name WHERE name = "Jane"
```

### Delete

For more SurrealQL examples, see the [`DELETE`](/docs/reference/query-language/statements/delete.md) and [`REMOVE`](/docs/reference/query-language/statements/remove.md) pages.

Conditionally deleting records based on the value of a field:

```cypher
// Cypher
MATCH (p:Person)  WHERE p.name = "Jane"  DELETE p
```

```surql
-- SurrealQL
DELETE person WHERE name = "Jane"
```

Deleting all records for a table (SurrealQL: table still exists):

```cypher
// Cypher
MATCH (p:Person)  DELETE p
```

```surql
-- SurrealQL
DELETE person
```

Deleting all records and table definition for a table:

```cypher
// Cypher
MATCH (p:Person)  DELETE p
```

```surql
-- SurrealQL
REMOVE TABLE person
```

## Importing from Neo4j using Surreal Sync

Surreal Sync can be used to export Neo4j nodes and relationships to SurrealDB.

It supports inconsistent full syncs and consistent incremental syncs, and together these provide the ability to reproduce consistent snapshots from the source Neo4j graph onto the target SurrealDB tables.

For more on how to import data from Neo4j to SurrealDB, please see the following pages in the Surreal Sync repo.

* [Surreal Sync for Neo4j](https://github.com/surrealdb/surreal-sync/blob/main/docs/neo4j.md)
* [Neo4j Data Types Support in Surreal Sync](https://github.com/surrealdb/surreal-sync/blob/main/docs/neo4j-data-types.md)

---

Source: https://surrealdb.com/docs/build/migrating/from-other-databases/from-postgresql

# Migrating from PostgreSQL

How to map existing data and concepts from PostgreSQL to SurrealDB

This page details some common PostgreSQL patterns and their SurrealQL equivalents, followed by links to the [Surreal Sync](https://github.com/surrealdb/surreal-sync/) tool which allows data from PostgreSQL to be automatically imported to SurrealDB.

## Importing from PostgreSQL using Surreal Sync

For more on how to import data from PostgreSQL to SurrealDB, please see the following pages in the Surreal Sync repo.

* [Surreal Sync for PostgreSQL (Trigger-Based)](https://github.com/surrealdb/surreal-sync/blob/main/docs/postgresql.md)
* [PostgreSQL wal2json Source](https://github.com/surrealdb/surreal-sync/blob/main/docs/postgresql-wal2json-source.md)
* [PostgreSQL Data Types Support in Surreal Sync](https://github.com/surrealdb/surreal-sync/blob/main/docs/postgresql-data-types.md)

Surreal Sync reads from a running PostgreSQL server through a connection string, and has no source that reads a `pg_dump` file. `surreal db import` cannot read one either, because it expects SurrealQL. To migrate from a dump, restore it into a PostgreSQL server first, such as a temporary one in Docker, and run Surreal Sync against that server. For a small number of tables, another option is to export each table to CSV with the PostgreSQL `COPY` command and [import the CSV files](/docs/build/migrating/from-files-and-streams/csv.md).

## Data types

The following chart shows PostgreSQL data types along with the equivalent or near-equivalent [SurrealQL data type](/docs/reference/query-language/language-primitives/data-types.md) for each.

| PostgreSQL Data Type | Wire Protocol Type |            SQL Representation            | SurrealDB Mapping |                            Notes                             |
| -------------------- | ------------------ | ---------------------------------------- | ----------------- | ------------------------------------------------------------ |
| **BOOLEAN**          | Boolean            | `true`/`false`                           | `bool`            |                                             |
| **SMALLINT**         | Int2               | `32767`                                  | `int`             |                                   |
| **INTEGER**          | Int4               | `2147483647`                             | `int`             |                                  |
| **BIGINT**           | Int8               | `9223372036854775807`                    | `int`             |                                             |
| **SERIAL**           | Int4               | `1, 2, 3...`                             | `int`             | Auto-increment converted to regular integer                  |
| **BIGSERIAL**        | Int8               | `1, 2, 3...`                             | `int`             | Auto-increment converted to regular integer                  |
| **REAL**             | Float4             | `3.14`                                   | `float` (f64)     | Converted to double precision                                |
| **DOUBLE PRECISION** | Float8             | `3.141592653589793`                      | `float` (f64)     |                                             |
| **NUMERIC/DECIMAL**  | Numeric            | `123.45`                                 | `number`          | Converted to SurrealDB Number with exact precision preserved |
| **MONEY**            | Money              | `$123.45`                                | `number`          | Currency symbol removed, converted to number                 |
| **CHAR(n)**          | Bpchar             | `'text'`                                 | `string`          | Fixed-length, padding removed                                |
| **VARCHAR(n)**       | Varchar            | `'text'`                                 | `string`          | Variable-length string                                       |
| **TEXT**             | Text               | `'long text'`                            | `string`          | Unlimited length string                                      |
| **BYTEA**            | Bytea              | `\\x48656c6c6f`                          | `bytes`           | Binary data, hex decoded                                     |
| **DATE**             | Date               | `'2024-01-15'`                           | `datetime`        | Converted to datetime at midnight UTC                        |
| **TIME**             | Time               | `'14:30:00'`                             | `string`          | Time-only as string (SurrealDB has no pure time type)        |
| **TIMESTAMP**        | Timestamp          | `'2024-01-15 14:30:00'`                  | `datetime`        | Converted to UTC datetime                                    |
| **TIMESTAMPTZ**      | Timestamptz        | `'2024-01-15 14:30:00+00'`               | `datetime`        | Timezone-aware, converted to UTC                             |
| **INTERVAL**         | Interval           | `'1 day 2 hours'`                        | `duration`        | Converted to SurrealDB duration                              |
| **UUID**             | Uuid               | `'550e8400-e29b-41d4-a716-446655440000'` | `string`          | UUID string representation                                   |
| **JSON**             | Json               | `'{"key": "value"}'`                     | `string`          | JSON stored as string representation                         |
| **JSONB**            | Jsonb              | `'{"key": "value"}'`                     | `string`          | Binary JSON stored as string representation                  |
| **ARRAY**            | Array              | `'{1,2,3}'`                              | `array`           | Recursively processed, element types converted               |
| **POINT**            | Point              | `'(1.5, 2.5)'`                           | `object`          | Convert to `{"x": 1.5, "y": 2.5}` object                   |
| **LINE**             | Line               | `'{1,2,3}'`                              | `object`          | Convert to coefficient object                              |
| **LSEG**             | Lseg               | `'[(1,2),(3,4)]'`                        | `object`          | Line segment as start/end point object                       |
| **BOX**              | Box                | `'(1,2),(3,4)'`                          | `object`          | Bounding box as corner points object                         |
| **PATH**             | Path               | `'[(1,2),(3,4)]'`                        | `array`           | Array of point objects                                       |
| **POLYGON**          | Polygon            | `'((1,2),(3,4),(5,6))'`                  | `array`           | Array of point objects                                       |
| **CIRCLE**           | Circle             | `'<(1,2),3>'`                            | `object`          | Center point and radius object                               |
| **INET**             | Inet               | `'192.168.1.1'`                          | `string`          | IP address as string                                         |
| **CIDR**             | Cidr               | `'192.168.0.0/24'`                       | `string`          | Network address as string                                    |
| **MACADDR**          | Macaddr            | `'08:00:2b:01:02:03'`                    | `string`          | MAC address as string                                        |

## Inserting data

```sql
// PostgreSQL
INSERT INTO product
    (name, description, price, category, images, options)
    VALUES
    ("Shirt", "Slim fit", 6, "clothing", ARRAY['image1.jpg', 'image2.jpg', 'image3.jpg'])
;
```

```surql
-- SurrealQL
CREATE product CONTENT {
    name: 'Shirt',
    id: 'shirt',
    description: 'Slim fit',
    price: 6,
    category: 'clothing',
    images: ['image1.jpg', 'image2.jpg', 'image3.jpg']
};
```

## Defining a schemafull table

A table in PostgreSQL might be defined as follows.

```surql
CREATE TABLE product (
    id SERIAL PRIMARY KEY,
    name TEXT,
    description TEXT,
    price NUMERIC(8,2),
    category TEXT,
    images TEXT[]
);
```

In SurrealQL, a table does not by default need to be defined before it can be used. However, the following statements will produce a strict schema similar to the PostgreSQL one above.

```surql
DEFINE TABLE product SCHEMAFULL;
DEFINE FIELD name ON TABLE product TYPE string;
DEFINE FIELD description ON TABLE product TYPE string;
DEFINE FIELD price ON TABLE product 
    TYPE number 
    -- Only show two digits after decimal point
    VALUE math::fixed($value, 2) 
    -- Price must be within this range
    ASSERT $value IN 0..=99999999;
DEFINE FIELD category ON TABLE product TYPE string;
DEFINE FIELD images ON TABLE product TYPE array<string>;
```

One difference between this and the PostgreSQL schema above is that a `product` will have a randomly generated ID as opposed to an incrementing one.

```surql
CREATE product SET 
    name = 'Shirt', 
    description = 'Nice shirt', 
    price = 20.449, 
    category = 'Clothing', 
    images = ["some_img.ping", "another_img.png"];

-- Output
[
	{
		category: 'Clothing',
		description: 'Nice shirt',
		id: product:1j29aq5q0do48k6xvyem,
		images: [
			'some_img.ping',
			'another_img.png'
		],
		name: 'Shirt',
		price: 20.45f
	}
]
```

### Selecting data

Selecting records using an ID:

```sql
// PostgreSQL
SELECT * FROM product WHERE id=1;
```

```surql
-- SurrealQL
SELECT * FROM product:shirt;
```

Selecting multiple specific records:

```sql
// PostgreSQL
SELECT * FROM product WHERE id IN (1, 2, 3);
```

```surql
-- SurrealQL
SELECT * FROM [product:1, product:2, product:3];
```

Counting the number of records in a table:

```sql
// PostgreSQL
SELECT COUNT(*) FROM product;
```

```surql
-- SurrealQL
SELECT count() FROM product GROUP ALL;
```

### Queries with identical syntax

As SurrealQL is inspired by SQL, many queries between it and PostgreSQL are identical.

```surql
SELECT * FROM product LIMIT 5;
SELECT name, price FROM product;
SELECT * FROM product ORDER BY price DESC;
SELECT * FROM order_item WHERE quantity = 2;
```

### Using record ID instead of the `WHERE` clause

If a record ID is known ahead of time and you are using a version of SurrealDB before 3.0, be sure to query by the record ID itself instead of using a `WHERE` clause in SurrealQL. This will avoid a full table scan if the field is not indexed.

```sql
// PostgreSQL
SELECT * FROM product WHERE id = 1;

// Immediate access in SurrealDB 3.0+, table scan in previous versions
SELECT * FROM product WHERE id = product:1;

// Accessing the record directly will
// take a fraction of the time in 2.x
product:1.*;
```

### Joining and querying related tables

Take the following query with joins in PostgreSQL:

```surql
SELECT p.id AS product_id, p.name AS product_name
FROM product p
JOIN order_item oi ON p.id = oi.product_id
JOIN customer_order co ON oi.order_id = co.order_id
JOIN customer c ON co.customer_id = c.customer_id
WHERE c.name = 'Pratim'
ORDER BY p.id;
```

In SurrealQL, tables can be joined to each other via edges, such as the `bought` edge in this example.

```surql
-- Relate a 'customer' to a 'product' via 'bought'
RELATE customer:tobie->bought->product:iphone CONTENT {
    option: { Size: 'M', Color: 'Max' },
    quantity: 1,
    total: 600,
    status: 'Pending',
    created_at: time::now()
};
```

Once the tables have been related (joined), they can be queried with this syntax.

```surql
SELECT * FROM customer:tobie->bought;
```

An example of more complex query with joins to return all people who bought the same products as a certain customer (including the original customer).

```sql
// PostgreSQL
SELECT DISTINCT c.*
FROM customer c
JOIN customer_order co ON c.customer_id = co.customer_id
JOIN order_item oi ON co.order_id = oi.order_id
JOIN product p ON oi.product_id = p.id
WHERE p.id IN (
    -- Subquery: Get all product IDs bought by Tobie
    SELECT p2.id
    FROM product p2
    JOIN order_item oi2 ON p2.id = oi2.product_id
    JOIN customer_order co2 ON oi2.order_id = co2.order_id
    JOIN customer c2 ON co2.customer_id = c2.customer_id
    WHERE c2.name = 'Tobie'
)
```

```surql
-- SurrealQL
customer:tobie->bought->product<-bought<-customer.*;
```

---

Source: https://surrealdb.com/docs/build/migrating/from-other-databases/from-snowflake

# Migrating from Snowflake

How to map existing data and concepts from Snowflake to SurrealDB

This page shows how Snowflake data types map to their SurrealQL equivalents, then explains how to load Snowflake tables into SurrealDB with the [Surreal Sync](https://github.com/surrealdb/surreal-sync/) tool.

## Data types

The following chart shows Snowflake data types along with the equivalent or near-equivalent [SurrealQL data type](/docs/reference/query-language/language-primitives/data-types.md) for each. Surreal Sync reads values through the Snowflake SQL REST API, which returns every cell as a string together with the column's logical type, and converts each one using that type.

| Snowflake Data Type | SurrealDB Mapping | Notes |
| ------------------- | ----------------- | ----- |
| **BOOLEAN**         | `bool`            |                                                              |
| **NUMBER / INT / INTEGER / BIGINT** | `int` | Whole numbers (scale 0) that fit a 64-bit integer      |
| **NUMBER(p,s) / DECIMAL / NUMERIC** | `number` | Values with a scale, or larger than a 64-bit integer, keep their precision and scale as a decimal |
| **FLOAT / REAL / DOUBLE** | `float` (f64) | Double-precision floating point                         |
| **VARCHAR / STRING / TEXT / CHAR** | `string` |                                                       |
| **DATE**            | `datetime`        | Stored as days since the Unix epoch, converted to a datetime at midnight UTC |
| **TIME**            | `datetime`        | Seconds since midnight                                        |
| **TIMESTAMP_NTZ / DATETIME** | `datetime` | No timezone; taken as the given wall-clock instant           |
| **TIMESTAMP_LTZ / TIMESTAMP_TZ / TIMESTAMP** | `datetime` | Timezone-aware; the absolute instant is preserved as UTC     |
| **VARIANT / OBJECT** | `object`         | Parsed from the stored JSON document                         |
| **ARRAY**           | `array`           | Parsed from the stored JSON array                            |
| **BINARY / VARBINARY** | `bytes`        | Hex-decoded to raw bytes                                      |

Any column whose logical type is not listed above is preserved as a `string` rather than failing the import.

## Importing from Snowflake using Surreal Sync

Surreal Sync can read tables from a Snowflake database and write them to SurrealDB.

> [!NOTE]
> The Snowflake source is ingestion-only. It performs a single full snapshot of the selected tables and does not support incremental synchronisation or change data capture. There is no durable cursor, so an interrupted run cannot be resumed part-way; re-run the command to start again.

Rows are read one Snowflake result partition at a time and written in batches, so a large table does not need to fit in memory.

### Prerequisites

Surreal Sync connects to Snowflake through the [SQL REST API](https://docs.snowflake.com/en/developer-guide/sql-api/index) using key-pair (JWT) authentication. Before running an import:

1. Generate an unencrypted PKCS#8 RSA key pair. Encrypted private keys are not supported yet.
2. Register the public key on the Snowflake user that Surreal Sync will authenticate as:

   ```sql
   ALTER USER my_user SET RSA_PUBLIC_KEY='MIIBIjANBgkq...';
   ```

3. Make sure that user has a role able to read the target database and schema, and a virtual warehouse to run the queries.

Keep the private key file accessible to Surreal Sync and pass its path with `--private-key-path`.

### Usage

```bash
surreal-sync from snowflake \
  --account myorg-myaccount \
  --user my_user \
  --private-key-path ./rsa_key.p8 \
  --warehouse COMPUTE_WH \
  --database MY_DB \
  --schema PUBLIC \
  --to-namespace my_namespace \
  --to-database my_database \
  --surreal-endpoint ws://localhost:8000 \
  --surreal-username root \
  --surreal-password secret
```

When `--tables` is omitted, every base table in the schema is imported (views and temporary tables are skipped). Each table becomes a table of the same name in SurrealDB, and each row becomes a record. Snowflake upper-cases unquoted identifiers, so table and column names arrive upper-cased.

Most connection options also read from an environment variable, which is useful for keeping credentials out of shell history.

### Options

| Option | Environment variable | Required | Description |
| ------ | -------------------- | -------- | ----------- |
| `--account` | `SNOWFLAKE_ACCOUNT` | Yes | Account identifier as used in the host `<account>.snowflakecomputing.com`, for example `myorg-myaccount` or `xy12345.us-east-1`. |
| `--user` | `SNOWFLAKE_USER` | Yes | User whose key pair is registered for JWT authentication. |
| `--private-key-path` | `SNOWFLAKE_PRIVATE_KEY_PATH` | Yes | Path to the unencrypted PKCS#8 private key PEM file. |
| `--private-key-passphrase` | `SNOWFLAKE_PRIVATE_KEY_PASSPHRASE` | No | Passphrase for an encrypted key. Not supported yet; setting it returns an error. |
| `--warehouse` | `SNOWFLAKE_WAREHOUSE` | Yes | Virtual warehouse used to run the queries. |
| `--database` | `SNOWFLAKE_DATABASE` | Yes | Database to read from. |
| `--schema` | `SNOWFLAKE_SCHEMA` | No | Schema within the database. Defaults to `PUBLIC`. |
| `--role` | `SNOWFLAKE_ROLE` | No | Role to assume for the session. |
| `--tables` | - | No | Comma-separated list of tables to import. When omitted, all base tables in the schema are imported. |
| `--id-columns` | - | No | Comma-separated columns forming the SurrealDB record ID. See [Record IDs](#record-ids). |
| `--transforms-config` | - | No | Path to a TOML file describing a transform pipeline. Omit to import rows unchanged. |
| `--to-namespace` | - | Yes | Target SurrealDB namespace. |
| `--to-database` | - | Yes | Target SurrealDB database. |

The `--surreal-endpoint`, `--surreal-username`, `--surreal-password`, `--batch-size`, and `--dry-run` options are shared with the other Surreal Sync sources. Use `--dry-run` to read and convert rows without writing to SurrealDB.

### Record IDs

Snowflake primary keys are optional and often absent, so the record ID for each row depends on `--id-columns`:

- **Omitted:** a sequential per-table integer is generated. It is deterministic within a run but not stable across re-runs, so prefer explicit ID columns when you need stable IDs.
- **A single column:** the value becomes the record ID, as an integer when it parses as one and otherwise as a string.
- **Several columns:** the values are joined with `:` into a string ID.

When ID columns are given, they are used only for the record ID and are not repeated as fields on the record. Column names are matched case-insensitively.

---

Source: https://surrealdb.com/docs/build/migrating/from-other-databases/overview

# Migrating from other databases

Map data and concepts from other databases to SurrealDB using Surreal Sync, the CLI import or SDKs.

This section details how to map data, queries and concepts you may know from other databases and datatypes into SurrealDB.

All of the sources in this section of the documentation can be automatically imported to SurrealDB using the [Surreal Sync](https://github.com/surrealdb/surreal-sync/) tool.

To migrate from other databases not yet supported, consider beginning by exporting the database as JSON which can be [imported on the command line](/docs/reference/cli/db/commands/import.md). Alternatively, you can use [one of the many available SDKs](/docs/languages.md) to access your existing database and transfer its content directly to a SurrealDB instance.

* [MongoDB](/docs/build/migrating/from-other-databases/from-mongodb.md)
* [MySQL](/docs/build/migrating/from-other-databases/from-mysql.md)
* [Neo4j](/docs/build/migrating/from-other-databases/from-neo4j.md)
* [PostgreSQL](/docs/build/migrating/from-other-databases/from-postgresql.md)
* [Snowflake](/docs/build/migrating/from-other-databases/from-snowflake.md)

---

Source: https://surrealdb.com/docs/explore/cli

# SurrealDB CLI

The surreal command installs once, then installs, versions and runs components: the database (surreal db), SurrealDB Cloud (surreal cloud) and WebAssembly modules (surreal mod). A first session, how components work, and where each workflow is documented.

`surreal` is the SurrealDB command line. You install it once, and it installs, updates and runs the rest: the database, SurrealDB Cloud management, and module tooling. Each of these is a **component** with its own commands and its own version, so a new `surreal cloud` release does not wait for a database release, and the reverse.

This page covers what the command does and gets you through a first session. For the full option lists, see the [CLI reference](/docs/reference/cli.md).

## Components

A component is a separate program that the `surreal` launcher finds, fetches when needed, and starts for you. You type `surreal <component> <command>`, and everything after the component name goes to that component unchanged.

| Component | Command | What it does | Guide |
| --- | --- | --- | --- |
| Database | `surreal db` | Start a server, open a SurrealQL shell, import and export data, validate query files | [Using the database](/docs/explore/cli/database.md) |
| Cloud | `surreal cloud` | Organisations, instances, backups, members and tokens on SurrealDB Cloud | [Managing SurrealDB Cloud](/docs/explore/cli/cloud.md) |
| Modules | `surreal mod` | Build and run WebAssembly modules | [Working with modules](/docs/explore/cli/modules.md) |

The launcher itself has five commands of its own, and they are the only commands that install or change versions: `surreal install`, `surreal upgrade`, `surreal use`, `surreal uninstall` and `surreal version`. [Managing components](/docs/explore/cli/components.md) explains each of them, how to pin a version, and how to run another version once with `+VERSION`.

## Install

The install script installs `surreal` and, by default, the database as a pinned component.

```bash title="macOS and Linux"
curl -sSf https://install.surrealdb.com | sh
```

```powershell title="Windows (PowerShell)"
irm https://windows.surrealdb.com | iex
```

Add the Cloud component at the same time with `--with cloud` (`-With cloud` on Windows), or install it later with `surreal install cloud`. [Install the SurrealDB CLI](/docs/explore/cli/install.md) covers the options, Homebrew and other package managers, and installing without the database.

## First session

Run the database locally, then talk to it.

```bash title="Run a local database"
surreal db start --user root --pass secret
```

In a second terminal, open a SurrealQL shell against it.

```bash title="Open a shell"
surreal db sql --username root --password secret --pretty
```

To work with SurrealDB Cloud, install the Cloud component, sign in, and create an instance.

```bash title="Manage SurrealDB Cloud"
surreal install cloud
surreal cloud auth login
surreal cloud org use acme
surreal cloud instance create api --type shared-1 --region aws-euw1
surreal cloud instance sql api -- --ns app --db main
```

`surreal cloud instance sql` resolves the instance and credentials, then hands off to `surreal db sql` through the launcher, so one command covers both planes. If a command needs a component that is not installed, `surreal` names the download host and asks before it fetches anything.

## Control plane and data plane

The components divide along a clear line.

| Component | Plane | Answers |
| --- | --- | --- |
| `surreal cloud` | Control plane | Which instances exist, what they run, what they cost, who may reach them |
| `surreal db` | Data plane | Queries, imports, exports, and running a server yourself |

A component never installs or upgrades another one. When `surreal cloud` needs the database, it runs `surreal db` through the launcher, which offers to install it if it is missing.

## Output that scripts can read

Every launcher command and every `surreal cloud` command takes `--json`, and sends its data to stdout and everything else, including progress, prompts and warnings, to stderr. A failed command leaves stdout empty.

```bash title="Machine-readable output"
surreal cloud instance list --json | jq -r '.[] | select(.state == "ready") | .name'
```

[Scripting and CI](/docs/explore/cli/cloud/scripting.md) covers exit codes, non-interactive credentials and long-running operations.

## Where to go next

- [Install](/docs/explore/cli/install.md) - The install script, package managers, and installing without the database.

- [Managing components](/docs/explore/cli/components.md) - Install, upgrade, pin, switch and remove components.

- [Using the database](/docs/explore/cli/database.md) - Start a server, query it, and move data in and out.

- [Managing SurrealDB Cloud](/docs/explore/cli/cloud.md) - Sign in, create and scale instances, and manage members.

- [CLI reference](/docs/reference/cli.md) - Every command and flag of the launcher and each component.

---

Source: https://surrealdb.com/docs/explore/cli/cloud

# Managing SurrealDB Cloud

Manage SurrealDB Cloud from the command line with surreal cloud: sign in, create and scale instances, pause and resume them, read logs and metrics, manage members, and script it in CI. Includes the move from surrealctl.

`surreal cloud` is the component of the [`surreal` command](/docs/explore/cli.md) for the SurrealDB Cloud control plane. Use it to sign in, create and list instances, scale them, pause and resume them, take backups, read logs and metrics, manage organisation members, and mint tokens, from a terminal, a shell script or a CI job.

This page covers what the component is for and gets you through a first session. It is written for operators and developers who already have a SurrealDB Cloud account. For exhaustive flag detail, see the [`surreal cloud` reference](/docs/reference/cli/cloud.md).

## When to use which tool

- **[SurrealDB Studio](https://studio.surrealdb.com)** is the place for sign-up, browsing data, billing pages and anything you do once. The browser is the fastest route for one-off work.
- **`surreal cloud`** is for anything you want to repeat, script, schedule or review in a pull request: provisioning, scaling, pausing an environment overnight, pulling logs into a report, granting a colleague a role.
- **[`surreal db`](/docs/explore/cli/database.md)** is for schema, queries and data, and for a local server during development.

`surreal cloud` and Studio use the same API, so a change made in one shows up in the other.

## Install

Install the component with the launcher. If you do not have `surreal` yet, the [install script](/docs/explore/cli/install.md) installs it, and `--with cloud` adds this component in the same step.

```bash
surreal install cloud
```

Instance commands that open a shell or move data, `instance sql`, `instance import` and `instance export`, run the database through the launcher as `surreal db`, which offers to install it if it is missing. Nothing else in `surreal cloud` needs the database. To upgrade the component, run `surreal upgrade cloud`; see [Managing components](/docs/explore/cli/components.md).

## Sixty-second quickstart

```bash title="First session"
surreal cloud auth login
surreal cloud whoami
surreal cloud org list
surreal cloud org use acme

surreal cloud instance create api --type shared-1 --region aws-euw1
surreal cloud instance sql api -- --ns app --db main
surreal cloud instance pause api
surreal cloud instance delete api --force
```

`auth login` opens a browser and stores the session for later commands. `org use` remembers an organisation so you do not have to name it every time. `instance create` waits until the instance is ready before it returns, so the `sql` command on the next line connects to a running database.

## Output that scripts can read

Every command takes `--json`. Every command sends its data to stdout and everything else, including progress, prompts, warnings and errors, to stderr, so a pipe always receives data and nothing else, and a failed command leaves stdout empty.

```bash title="Machine-readable output"
surreal cloud instance list --json | jq -r '.[] | select(.state == "ready") | .name'
```

## Guides

- [Authenticating surreal cloud](/docs/explore/cli/cloud/authentication.md) - Login sessions, personal access tokens, scopes and CI credentials.

- [Organisations with surreal cloud](/docs/explore/cli/cloud/organisations.md) - Choose an organisation, manage members and invitations, and read usage.

- [Instances with surreal cloud](/docs/explore/cli/cloud/instances.md) - Create, scale, pause, back up, connect to and delete instances.

- [Scripting surreal cloud](/docs/explore/cli/cloud/scripting.md) - Exit codes, waiting, non-interactive credentials and CI recipes.

## Coming from surrealctl

`surrealctl` is now `surreal cloud`, with the same commands and flags. On its first run, `surreal cloud` copies your configuration and stored login from `~/.config/surrealctl` (`%APPDATA%\surrealctl` on Windows) to `~/.surrealdb/cloud`, and says so once. The original stays in place, so an installed `surrealctl` keeps working beside it.

The `SURREALCTL_*` environment variables still work as aliases of `SURREAL_CLOUD_*`, and a run that uses any of them prints one warning naming each replacement. Update scripts to `surreal cloud` and the `SURREAL_CLOUD_` prefix.

---

Source: https://surrealdb.com/docs/explore/cli/cloud/authentication

# Authenticating surreal cloud

Choose between a login session and a personal access token with surreal cloud, create and revoke tokens, scopes, profiles, where credentials are stored, and authenticating an unattended run.

`surreal cloud` accepts two kinds of credential. This page explains what each one is good for, how to create and inspect them, and how to hand one to a CI job. It is for anyone setting up their own machine, and for engineers wiring `surreal cloud` into a pipeline. To install the component first, see [Install](/docs/explore/cli/install.md) and [Components](/docs/explore/cli/components.md). For the mechanism underneath - the sign-in flows, what travels on the wire, and how the credential store behaves - see the [authentication reference](/docs/reference/cli/cloud/authentication.md).

## Two kinds of credential

The two credentials differ in what they can **write**, not in what they can reach.

| Credential | How you get it | Use it for | Cloud writes |
| --- | --- | --- | --- |
| Login session | `surreal cloud auth login` | Everyday work at a terminal | Yes |
| Personal access token | `surreal cloud token create`, or SurrealDB Studio | Scripts, CI jobs, shared runners | No |

A login session is the credential that can do everything. A personal access token reads the control plane and cannot change it, which is what makes it safe to leave in a CI secret store.

## Login sessions

`surreal cloud auth login` signs in through your identity provider and stores a refresh token plus one access token per audience. There are three sign-in flows, chosen with `--flow`: `browser` opens a browser and listens on `127.0.0.1` for the redirect, `device` shows a code to type on another device, and `paste` prints a URL and takes back the address the browser was redirected to. `--no-browser` shows a code or a URL instead of opening a browser, and `--force` signs in again when the profile already has a credential.

Four commands inspect and maintain the session.

```bash
surreal cloud auth status          # which credential is in use, read from disk
surreal cloud auth status --verify # the same, plus one request to check it
surreal cloud auth refresh         # renew now, rather than at the next request (add --all for the account-management token too)
surreal cloud auth logout          # remove this profile's stored credentials
```

`auth status` and `auth scopes` touch no network, so they work on a plane, in a container with no egress, and in a CI job that is about to fail for another reason. `whoami` is the command that asks the API who you are.

`auth status` also warns when this machine's clock is more than five minutes out of step with the API, because a wrong clock produces authentication failures that look like a rejected credential.

`auth logout` revokes the refresh token at the identity provider on a best-effort basis. A failed revocation prints a warning and still removes the credential from this machine. Logging out when nothing is signed in succeeds and still prints a document, so a teardown script does not fail on its second run.

> [!NOTE]
> `auth scopes` returns an empty list for a login session. Identity scopes are not the authorisation model for a session - your role in the organisation is. Run `surreal cloud org permissions` to see what you may do.

## Personal access tokens

A personal access token is a long-lived string beginning `sdbp_`. Create one with a login session in place, then hand it to whatever needs it.

```bash
surreal cloud token scopes
surreal cloud token create "ci-nightly-report" --scope read:cloud --expires-in 90 | tail -1
```

<OptionsTable
    title="token create"
    options={[
        {
            "name": "<LABEL>",
            "required": true,
            "description": "What this token is for. Shown in every listing. 1 to 120 characters."
        },
        {
            "name": "--scope",
            "value": "<SCOPE>",
            "description": "A scope to grant. Repeat the flag to grant several, up to 32."
        },
        {
            "name": "--expires-in",
            "value": "<DAYS>",
            "default": "30",
            "description": "Days until the token expires, from 1 to 365, or `never`."
        },
        {
            "name": "--reveal",
            "description": "Print the secret even when stdout is a terminal."
        }
    ]}
/>

Four things to know about `token create` before you run it:

- **The secret is returned once.** It is the last line on stdout, so `| tail -1` is the whole capture recipe. Nothing can recover it later.
- **It refuses to print a secret to a terminal.** Pipe the output, or pass `--reveal` if you meant to read it. The check happens before the token is minted, so a forgotten flag never costs you a credential nobody can use.
- **It is the one command with no `--json` form.** The secret can neither go inside the document nor share stdout with it, so `--json` is a usage error here.
- **A token with no `--scope` is permitted nothing.** Grant what the job needs and no more.

List and revoke tokens by id or label.

```bash
surreal cloud token list
surreal cloud token delete ci-nightly-report
```

> [!WARNING]
> Revocation is not instant. A revoked token can continue to work for several minutes while the platform's exchange cache expires. Rotate a leaked token and then confirm with `surreal cloud token list`.

## What a personal access token cannot do

**It cannot manage personal access tokens.** All four `token` verbs - `list`, `create`, `delete`, and `scopes` - refuse a personal access token. The refusal is local, costs no requests, and exits `4`. A leaked token must not be able to mint its own replacements, nor revoke the one you would use to clean up after it.

```text
Managing personal access tokens needs an interactive login session, so nothing was sent.

Sign in with:  surreal cloud auth login
```

**It cannot write to the control plane.** The API refuses a personal access token on every mutating route, whatever scopes the token carries. `instance create`, `instance delete`, `org update`, and `team invite` all need a login session. Reads work normally.

**It cannot exceed its scopes.** A missing scope produces a 403 naming the scope, and exits `4`.

Everything else works: `org list`, `instance list`, `instance logs`, `auth status`, `auth scopes`, and `auth logout`.

## Choosing between them

- **At a terminal, sign in.** A login session covers every command and renews itself.
- **In CI, use a token** for anything that only reads: nightly usage reports, drift checks, dashboards, alerting on instance state.
- **For unattended writes, use a login session** on a dedicated machine account, with the credential file mounted into the job. Scope the account's role in the organisation to what the pipeline needs.

## Supplying a token

Two flags supply a token, each with an environment variable, and neither writes the token to disk.

<OptionsTable
    title="Credential options"
    options={[
        {
            "name": "--token",
            "value": "<TOKEN>",
            "env": "SURREAL_CLOUD_TOKEN",
            "description": "Personal access token to authenticate with. Conflicts with `--token-file`."
        },
        {
            "name": "--token-file",
            "value": "<PATH>",
            "env": "SURREAL_CLOUD_TOKEN_FILE",
            "description": "Read the personal access token from a file, or `-` for stdin."
        }
    ]}
/>

`SURREAL_CLOUD_TOKEN` is the usual choice in CI, because the environment is where a secret store puts things.

```bash
export SURREAL_CLOUD_TOKEN="$(cat /run/secrets/surreal-cloud)"
surreal cloud instance list --json
```

`--token` has no short form on purpose: `-t` reads as `--type` on the instance commands, and a credential in `argv` is visible through `ps` and lands in shell history.

A credential supplied this way lasts for the one invocation. The only command that stores a token is `auth login --with-token`, which reads it from stdin and remembers it for the profile.

```bash
surreal cloud auth login --with-token --label laptop < ~/token.txt
```

## Scopes

The scopes include `read:cloud`, `write:cloud-instances`, `write:cloud-organization`, and `write:cloud-billing`. Run `surreal cloud token scopes` for the current list.

Two commands report on them, and they answer different questions.

- `surreal cloud token scopes` - the catalogue of scopes a token can be granted. This one is the menu.
- `surreal cloud auth scopes` - what the credential in your hand carries. This one is the receipt.

## Profiles

A profile bundles a credential, an API base URL, and configuration under one name. Use profiles to keep a work account and a personal account apart, or production and staging.

```bash
surreal cloud context use staging          # switch the active profile
surreal cloud --profile staging auth login # sign that profile in
surreal cloud context list                 # every profile, and which is active
surreal cloud context show                 # what this invocation resolved, and why
```

Each profile stores its own credential, so switching profiles switches identity. `surreal cloud org use` works *within* a profile and remembers an organisation; neither command is expressible as the other.

## Where credentials are stored

Credentials live in `credentials.json`, mode `0600`, beside `config.toml` in the configuration directory, `~/.surrealdb/cloud` (`%USERPROFILE%\.surrealdb\cloud` on Windows). The directory follows `SURREAL_CLI_HOME` when the launcher has another home, and `--config` or `SURREAL_CLOUD_CONFIG_DIR` puts it elsewhere. `config.toml` holds no secrets and is safe to commit; `credentials.json` is not.

Two behaviours matter at a terminal. `surreal cloud` refuses to read a `credentials.json` that group or other can read, and tells you the `chmod` to run. And concurrent invocations serialise on an advisory lock, so if a run reports that another `surreal cloud` is updating credentials, retry in a moment.

On first run, `surreal cloud` copies the configuration and stored login of the former `surrealctl` tool from `~/.config/surrealctl` (`%APPDATA%\surrealctl` on Windows) once, and leaves the original in place. The old `SURREALCTL_*` environment variables still work as deprecated aliases of `SURREAL_CLOUD_*`, with a warning naming each replacement.

The [authentication reference](/docs/reference/cli/cloud/authentication.md#where-credentials-are-stored) covers the rest: the resolution order for the directory, why the store is a file rather than the OS keyring, how the lock and the atomic rename work, and how clock skew is learned.

## Troubleshooting

| Symptom | Cause | Resolution |
| --- | --- | --- |
| Exit `3` on any command | No credential, or one that expired and could not be renewed | `surreal cloud auth login`, or `surreal cloud auth refresh` |
| Exit `4` on a `token` command | A personal access token is in use | Sign in with a login session |
| Exit `4` on a write | Insufficient scope, insufficient role, or a token where a session is needed | Check `surreal cloud org permissions`; use a login session for writes |
| `auth status` warns about clock skew | This machine's clock is more than five minutes out | Fix time synchronisation on the host |
| Exit `30` | An interactive sign-in did not complete | Run `surreal cloud auth login` again; add `--flow device` when there is no browser |
| Exit `11` | The credential store could not be read or written | Check the permissions and free space on the configuration directory |
| A refusal to read `credentials.json` | The file is readable by group or other | `chmod 600` on the path in the message |

To see which requests are being made and which configuration layer won each value, add `--debug`. Credentials are logged as a digest, never in full.

## Next steps

- [Organisations](/docs/explore/cli/cloud/organisations.md) - choose an organisation, manage members, read usage and spend.
- [Instances](/docs/explore/cli/cloud/instances.md) - everyday instance work.
- [Scripting](/docs/explore/cli/cloud/scripting.md) - exit codes, `--json`, and unattended runs.
- [Members and roles](/docs/manage/organisations/members-and-roles.md) - what a role permits in an organisation.

---

Source: https://surrealdb.com/docs/explore/cli/cloud/instances

# Instances with surreal cloud

Deploy, find, connect to, scale, pause, back up, set capabilities on, and delete SurrealDB Cloud instances with the surreal cloud instance commands.

This page walks through everyday instance work from the command line: creating an instance, finding it again, opening a SurrealQL session, resizing it, pausing it, taking backups, and deleting it. It assumes you are signed in and that an organisation resolves; see [Authentication](/docs/explore/cli/cloud/authentication.md) if not. For every flag on every verb, see the [`surreal cloud` reference](/docs/reference/cli/cloud/commands/instance.md).

## Before you start

Instances belong to an organisation, and there is no route that lists them across organisations. Pick one for the profile once, and every `instance` command follows it.

```bash
surreal cloud org use acme
```

`--org` overrides it for a single command, and `SURREAL_CLOUD_ORG` overrides it for a shell. See [Organisations](/docs/explore/cli/cloud/organisations.md#choosing-an-organisation) for the full order of precedence.

Two catalogues tell you what you can deploy.

```bash
surreal cloud catalog regions
surreal cloud catalog instance-types
surreal cloud catalog instance-versions
surreal cloud catalog storage-types
surreal cloud org plans
```

`catalog` lists what the platform offers in general. `org plans` is narrower and organisation-specific - it is the one to trust before a `create`.

## Create an instance

<Synopsis>
surreal cloud instance create [OPTIONS] --type <SLUG> --region <SLUG> <NAME>
</Synopsis>

Price it first if the cost matters, then deploy.

```bash
surreal cloud instance estimate --type shared-1 --region aws-euw1
surreal cloud instance create production --type shared-1 --region aws-euw1
```

<OptionsTable
    title="Options"
    options={[
        {
            "name": "<NAME>",
            "required": true,
            "description": "A name for the instance. 1 to 30 characters."
        },
        {
            "name": "--type",
            "value": "<SLUG>",
            "required": true,
            "description": "The instance type, by slug. See `surreal cloud catalog instance-types`."
        },
        {
            "name": "--region",
            "value": "<SLUG>",
            "required": true,
            "description": "The region to deploy into, by slug. See `surreal cloud catalog regions`."
        },
        {
            "name": "--version",
            "value": "<VERSION>",
            "description": "The SurrealDB version to run. Defaults to the platform's current one."
        },
        {
            "name": "--compute-units",
            "value": "<N>",
            "description": "How many compute units to run, for the types that scale."
        },
        {
            "name": "--storage",
            "value": "<GB>",
            "description": "Storage to allocate, in gigabytes."
        },
        {
            "name": "--access-type",
            "value": "<ACCESS>",
            "description": "How the instance may be reached: `public`, `private`, or `dual`."
        },
        {
            "name": "--restore-from",
            "value": "<INSTANCE/SNAPSHOT>",
            "description": "Restore from a snapshot, as `<instance>/<snapshot>`."
        }
    ]}
/>

`create` waits until the instance is ready before it returns, so the next line in your script can connect. Pass `--no-wait` to return as soon as the API accepts the request; the output then includes the `surreal cloud instance watch` command to pick the state back up.

> [!WARNING]
> Creating an instance is not idempotent. A retried `instance create` is a second instance and a second bill. `surreal cloud` never replays a create automatically for this reason - if a create fails without a clear answer, run `surreal cloud instance list` before trying again.

## Naming an instance

Every command that takes an instance accepts four spellings: the id, the slug, the name, or `org/name` when you want to be explicit about which organisation.

```bash
surreal cloud instance get production
surreal cloud instance get acme/production
surreal cloud instance get prod-a1b2c3
```

Omit the name at a terminal and `surreal cloud` offers a picker. Omit it in a script and you get a usage error that lists the available instances, so the failure tells you what you should have typed.

## Find instances

```bash
surreal cloud instance list
surreal cloud instance list --wide
surreal cloud instance list --columns name,state,version --sort name
surreal cloud instance get production
```

`--columns`, `--wide`, `--sort`, `--reverse`, and `--limit` shape the table. The API has no pagination or sort parameter, so all five are applied on your machine after the whole list arrives - and none of them reaches `--json`, where the complete payload is always emitted. A `--limit 1` must not silently truncate a pipeline.

## Connect to an instance

```bash
surreal cloud instance endpoint production
surreal cloud instance sql production --namespace app --database main
```

`instance endpoint` prints the endpoint alone, with no label and no styling, and its output is byte-identical whether you are at a terminal or in a pipe. That makes `$(surreal cloud instance endpoint production)` safe to embed in another command.

`instance sql` resolves the instance, mints a database token, and hands over to `surreal db sql` through the launcher. If the database component is not installed, the launcher offers to install it; see [Components](/docs/explore/cli/components.md). Everything after `--` is passed to `surreal db sql` verbatim, so the full flag surface of the database shell is available. The token travels in the child process's environment, never in `argv`, where `ps` and shell history can both read it.

`instance import` and `instance export` work the same way. All three need a namespace and a database, because `surreal db` requires them. The check happens before a token is minted.

```bash
surreal cloud instance export production --namespace app --database main -- backup.surql
surreal cloud instance import staging --namespace app --database main -- seed.surql
```

To authenticate your own client instead, mint a database token.

```bash
surreal cloud instance token production > token.txt
surreal cloud instance jwks production
```

`instance token` refuses to print to a terminal, where the token would stay in your scrollback. Pipe it, redirect it, or pass `--reveal` if you meant to read it. `instance jwks` fetches the key set that verifies those tokens; under `--json` it emits the whole key set so it can be piped straight into a verifier.

## Scale an instance

```bash
surreal cloud instance update production --compute-units 4
surreal cloud instance update production --storage 100
surreal cloud instance update production --version 3.2.4
```

Pass at least one of `--type`, `--compute-units`, `--storage`, `--version`, or `--access-type`. With no flags at all, `update` is a usage error rather than a silent no-op.

One invocation may be several requests. Each field has its own route, and `surreal cloud` applies them in a fixed order - type, compute units, storage, version, access type - stopping at the first failure. If a later change fails, the earlier ones have already been applied.

> [!IMPORTANT]
> Every change in this list restarts the instance. Plan updates the way you would plan a deployment, and expect a reconnect window. Storage can only be changed once every few hours; `surreal cloud` reports the cool-off and when the last change happened rather than sending a request that would be refused. A type change starts the cool-off as well, so change `--type` and `--storage` in separate invocations.

## Pause and resume

```bash
surreal cloud instance pause staging
surreal cloud instance resume staging
```

Pausing stops compute charges and keeps storage and configuration. Pausing an instance that is already paused prints a note, still emits the same document, and exits `0`, so a script that pauses an environment every evening is safe to run twice.

## Watch an operation

`instance watch` follows an operation that was started elsewhere - by a colleague, by Studio, or by an earlier `--no-wait` command.

```bash
surreal cloud instance watch production
surreal cloud instance watch production --until paused
surreal cloud instance watch production --json
```

`--until` takes `ready`, `paused`, or `deleted`. Under `--json`, `watch` writes one compact object per line to stdout as the state changes, because the transitions are the answer rather than a progress report.

## Waiting semantics

`create`, `update`, `delete`, `pause`, and `resume` wait for the instance to settle before they return.

<OptionsTable
    title="Wait options"
    options={[
        {
            "name": "--wait",
            "default": "on",
            "description": "Wait for the operation to finish. On by default in every output mode."
        },
        {
            "name": "--no-wait",
            "description": "Return as soon as the API accepts the request."
        },
        {
            "name": "--wait-timeout",
            "value": "<DURATION>",
            "default": "15m",
            "description": "How long to wait before giving up, such as `90s` or `20m`."
        }
    ]}
/>

Waiting is on in rich, plain, CI, and `--json` output alike. A default that changed with the terminal would mean `CI` changed behaviour rather than presentation, and would break the obvious script: create, then connect.

Polling backs off - two seconds for the first thirty, then five, then ten - with jitter so parallel waiters do not all poll at the same moment. Being rate limited extends the deadline rather than consuming it, up to a couple of minutes of credit, so a busy account does not get spurious timeouts.

If the wait gives up, `surreal cloud` exits `10` and says so. That is a distinct outcome from a failure: **the operation is still running server-side.** Poll again with `instance get` or `instance watch` rather than rolling back.

Interrupting a wait does not stop the operation either. It carries on, and `surreal cloud instance watch <name>` picks the state back up.

## Read state, logs, and metrics

```bash
surreal cloud instance status production
surreal cloud instance logs production --level error --limit 100
surreal cloud instance logs production --follow
surreal cloud instance metrics production --metric cpu --from 2026-08-11T10:00:00Z
surreal cloud instance usage production
```

`instance status` reports the deployment phase and the snapshots the instance can be restored from, as one document.

`--level` is a display filter applied on your machine - the API has no level parameter - so `--json` still carries every line the API sent, and a note tells you how many lines were hidden. `--follow` polls every five seconds and, under `--json`, writes one compact object per new line.

`--metric` is free-form, because the set of metrics is not published anywhere `surreal cloud` can read. `cpu`, `memory`, and `disk` are the usual ones. `--from` and `--to` take RFC 3339 timestamps and are forwarded byte for byte.

For the equivalent views in the browser, see [Monitoring](/docs/manage/instances/monitoring.md).

## Backups

```bash
surreal cloud instance backup list production
surreal cloud instance backup create production
surreal cloud instance backup policy get production
surreal cloud instance backup policy set production --daily 14 --weekly 8
```

The three retention flags - `--daily`, `--weekly`, `--monthly` - take a number of periods to keep, or the literal `default` to restore your organisation's own value for that tier. `--frequency-hours` sets how often a snapshot is taken, in whole hours up to 24; the plan decides which values are allowed.

Only a **reduction** asks for confirmation. Lengthening a retention cannot lose a snapshot, so it is applied without a prompt.

Which tiers you may change depends on the organisation's plan. `backup policy get` shows which are editable.

`instance backup create` returns as soon as the platform accepts the request. It has no `--wait`, and a new snapshot takes a while to appear in the list, so do not expect to restore from one you took a moment ago.

Restoring means creating a new instance from a snapshot, which leaves the original untouched.

```bash
snapshot=$(surreal cloud instance backup list production --json \
  | jq -r 'sort_by(.started_at) | last | .snapshot_id')

surreal cloud instance create recovery \
  --type shared-1 --region aws-euw1 \
  --restore-from "production/${snapshot}"
```

See [Backups and recovery](/docs/manage/instances/backups.md) for retention behaviour and what a snapshot contains.

## Capabilities

Capabilities decide what SurrealQL running on the instance may do: scripting, guest access, outbound network access, which functions and RPC methods are permitted.

```bash
surreal cloud instance capabilities get production
surreal cloud instance capabilities set production --deny-scripting --allow-net api.example.com
```

The endpoint is a full replacement, so `capabilities set` reads the current configuration, applies your flags, prints what would change, and asks before writing. Two consequences matter:

- **A list flag replaces that list.** `--allow-net a.example.com,b.example.com` sets the whole list. A later run naming only `a.example.com` removes `b.example.com`.
- **Anything you do not name is left exactly as it is**, including capabilities this version of `surreal cloud` does not know about.

Flags that would change nothing print a note and write nothing. `--force` skips the confirmation.

For what each capability means, see [Configure an instance](/docs/manage/instances/configure.md) and [Network access](/docs/manage/instances/network-access.md).

## Delete an instance

```bash
surreal cloud instance delete staging
surreal cloud instance delete staging --force
```

The confirmation names the instance, its slug, and its region. Declining changes nothing and exits `0`. `--force` skips the prompt; in an unattended session with neither `--force` nor `--yes`, `delete` exits `2` having sent no request.

Deletion removes the instance and everything in it. Take an export or confirm a snapshot first.

## Worked examples

### Stand up a review environment

```bash title="review-env.sh"
#!/usr/bin/env bash
set -euo pipefail

name="review-${1}"

surreal cloud instance create "$name" --type shared-1 --region aws-euw1
surreal cloud instance import "$name" --namespace app --database main -- fixtures/seed.surql
surreal cloud instance endpoint "$name"
```

`create` returns only once the instance is ready, so the `import` on the next line has something to talk to. The final line prints the endpoint for whatever consumes this script.

### Pause every non-production instance overnight

```bash title="pause-overnight.sh"
#!/usr/bin/env bash
set -euo pipefail

surreal cloud instance list --json \
  | jq -r '.[] | select(.state == "ready" and .name != "production") | .name' \
  | while read -r name; do
        surreal cloud instance pause "$name" --wait-timeout 5m
    done
```

### Clone production from its latest snapshot

```bash title="clone-production.sh"
#!/usr/bin/env bash
set -euo pipefail

snapshot=$(surreal cloud instance backup list production --json \
  | jq -r 'sort_by(.started_at) | last | .snapshot_id')

surreal cloud instance create scratch \
  --type shared-1 --region aws-euw1 \
  --restore-from "production/${snapshot}"

surreal cloud instance endpoint scratch
```

## Next steps

- [Organisations](/docs/explore/cli/cloud/organisations.md) - members, roles, invitations, usage, and spend.
- [Scripting](/docs/explore/cli/cloud/scripting.md) - exit codes and the `--json` contract in full.
- [`surreal cloud` reference](/docs/reference/cli/cloud/commands/instance.md) - every instance flag and default.

---

Source: https://surrealdb.com/docs/explore/cli/cloud/organisations

# Organisations with surreal cloud

Select an organisation, manage members and invitations, check roles, and read usage, spend and plans from the command line with surreal cloud.

An organisation owns instances, members, and billing. This page covers how `surreal cloud` decides which organisation you mean, how to manage the people in it, and how to read what it has used and what it has cost. It is for anyone who administers a team's SurrealDB Cloud account.

## Choosing an organisation

Almost every command needs an organisation. `surreal cloud` works down a fixed order and stops at the first answer.

1. `--org` on the command line
2. `SURREAL_CLOUD_ORG` in the environment
3. `org` in this profile's section of `config.toml`
4. The organisation remembered by `surreal cloud org use`
5. Your account's default organisation
6. The only candidate, when you belong to exactly one
7. An interactive picker, at a terminal
8. An error naming the flag

```bash
surreal cloud org list
surreal cloud org use acme          # remember it for this profile
surreal cloud --org acme instance list
surreal cloud org use --clear       # forget it again
```

`surreal cloud context show` reports which organisation resolved and which layer decided it. Add `--debug` to any command to see the winning layer for every value.

In CI, set `SURREAL_CLOUD_ORG` to the organisation **id** rather than its name. An id is used as it stands; a name costs a lookup on every command.

> [!NOTE]
> `SURREAL_CLOUD_ORG` outranks the value `org use` remembered. If a remembered organisation seems to be ignored, check whether the variable is exported. `org use` warns you about it at the time.

## Manage organisations

```bash
surreal cloud org list
surreal cloud org list --all                 # include archived organisations
surreal cloud org get acme
surreal cloud org create acme-labs --use
surreal cloud org update acme --name "Acme Ltd"
surreal cloud org archive acme
```

An organisation name is 1 to 30 characters. `--use` on `create` remembers the new organisation immediately, so the commands after it need no `--org`. `org archive` asks for confirmation unless you pass `--force`.

`org get` shows the plan, the state, your role, the member count, the instance allowances, and the billing configuration.

## Members

Members are addressed by username or user id.

```bash
surreal cloud team list
surreal cloud team get ada
surreal cloud team update ada --role admin
surreal cloud team remove ada
```

`team remove` ends someone's membership of the organisation. Their account is untouched, which is why the verb is `remove` and not `delete`.

`team list` deliberately does not fold in pending invitations. Someone who has been invited is not yet a member; `invite list` is where they appear.

## Invitations

Roles are defined per organisation, so read the list before you send an invitation.

```bash
surreal cloud org roles
surreal cloud team invite ada@example.com --role member
surreal cloud invite list
surreal cloud invite delete ada@example.com
```

<OptionsTable
    title="team invite"
    options={[
        {
            "name": "<EMAIL>",
            "required": true,
            "description": "The email address to invite."
        },
        {
            "name": "--role",
            "value": "<ROLE>",
            "required": true,
            "description": "The role to grant. Run `surreal cloud org roles` for the ones this organisation defines."
        }
    ]}
/>

`surreal cloud team invite` and `surreal cloud invite create` are the same command under two names - use whichever reads better where you are. If the API rejects the role, the error points you back at `org roles`.

`invite delete` withdraws an invitation, by the email address it was sent to or by its code. The confirmation names the address.

## Roles and permissions

```bash
surreal cloud org roles        # the roles this organisation can assign
surreal cloud org permissions  # what you are permitted to do here
```

`org permissions` answers "what may I do in this organisation" for the credential in hand. It is the question `auth scopes` cannot answer for a login session, where the authorisation model is your role rather than a scope list.

For what Owner, Admin, and Member each permit, see [Members and roles](/docs/manage/organisations/members-and-roles.md).

## Personal access tokens

Tokens belong to your account rather than to an organisation, but they are how a pipeline reads an organisation's data.

```bash
surreal cloud token scopes
surreal cloud token create "ci-read-only" --scope read:cloud | tail -1
surreal cloud token list
surreal cloud token delete ci-read-only
```

All four verbs need a login session, so a token cannot mint its own replacement. See [Authentication](/docs/explore/cli/cloud/authentication.md#personal-access-tokens) for scopes, expiry, and how to capture the secret.

## Usage and spend

```bash
surreal cloud org usage                  # consumption across every instance
surreal cloud instance usage production  # one instance
surreal cloud org spend                  # the current billing period
surreal cloud org spend --period 03-2026 # a specific month
surreal cloud org plans                  # the plans available to this organisation
```

`--period` is written month first, as `MM-YYYY`. If you write it the other way round, `surreal cloud` says so and shows the correction rather than sending the request.

In text output, `org spend` prints a total on stderr. The total is computed before `--limit` is applied, so it is always the whole bill even when the table is trimmed.

> [!IMPORTANT]
> Under `--json`, money stays in integer minor units - cents, or millicents where the API uses them. Do not treat `amount` as a decimal. Divide before you display.

```bash title="Total spend for a month, in whole currency units"
surreal cloud org spend --period 03-2026 --json | jq '[.[].amount] | add / 100'
```

```bash title="Line items as TSV"
surreal cloud org spend --json \
  | jq -r '.[] | [.effective_at, .resource, .description, .amount] | @tsv'
```

For invoices, payment methods, and how the platform bills, see [Billing](/docs/manage/organisations/billing.md).

## Open the dashboard

```bash
surreal cloud open org acme
surreal cloud open billing acme
surreal cloud open instance production
surreal cloud open terms
```

The URL is always printed; a browser is only opened when somebody is present to look at it, so this is safe in a script that logs its output. `surreal cloud` links to the terms and never accepts them on your behalf.

## Worked examples

### Onboard a new engineer

```bash title="onboard.sh"
#!/usr/bin/env bash
set -euo pipefail

email="$1"

surreal cloud team invite "$email" --role member
surreal cloud invite list --columns email,role,status
```

### Report which instances cost the most

```bash title="top-spend.sh"
#!/usr/bin/env bash
set -euo pipefail

surreal cloud org spend --json \
  | jq -r 'group_by(.instance_id)
           | map({instance: .[0].instance_id, cents: ([.[].amount] | add)})
           | sort_by(-.cents)
           | .[] | [.instance, .cents] | @tsv'
```

### Audit who is in every organisation you belong to

```bash title="audit-members.sh"
#!/usr/bin/env bash
set -euo pipefail

surreal cloud org list --json | jq -r '.[].id' | while read -r org; do
    echo "== ${org}"
    surreal cloud --org "$org" team list --json | jq -r '.[] | [.username, .role] | @tsv'
done
```

## Next steps

- [Scripting](/docs/explore/cli/cloud/scripting.md) - exit codes, `--json`, and unattended runs.
- [Instances](/docs/explore/cli/cloud/instances.md) - the instance lifecycle from the command line.
- [Organisations](/docs/manage/organisations.md) - the same concepts in SurrealDB Studio.
- [Reference](/docs/reference/cli/cloud/commands/org.md) - every `org` flag, with [`team`](/docs/reference/cli/cloud/commands/team.md) and [`invite`](/docs/reference/cli/cloud/commands/invite.md) alongside.

---

Source: https://surrealdb.com/docs/explore/cli/cloud/scripting

# Scripting surreal cloud

Drive surreal cloud from CI and shell scripts: the JSON contract, exit codes for control flow, unattended runs, waiting semantics, and installing the component in a pipeline.

This page covers running `surreal cloud` where nobody is watching: in CI, in cron, and in shell scripts. It explains the output contract, how to branch on exit codes, how to keep a run from blocking on a prompt, and where to put a credential. It is for engineers automating control-plane work.

## The two streams

`surreal cloud` splits its output by purpose, and the split is absolute.

- **stdout** carries the command's data payload and nothing else.
- **stderr** carries progress, prompts, warnings, hints, errors, and debug output.

So `| jq` always works, `--json > out.json` can still ask you a question, and a failed command leaves stdout empty rather than half a document. A broken downstream pipe is not an error: `surreal cloud instance list | head -3` exits `0` silently.

## Reading successful output

`--json` prints the bare wire payload on stdout, with no envelope. There is no `.data[]` to unwrap, because the stream split and the exit code already answer "did it work".

```bash
surreal cloud instance list --json | jq -r '.[] | select(.state == "ready") | .name'
surreal cloud instance get production --json | jq -r '.version'
surreal cloud org list --json | jq -r '.[] | [.id, .name] | @tsv'
```

Every invocation emits exactly one document. Two commands stream instead, and say so in their own help: `instance watch` and `instance logs --follow` emit newline-delimited JSON, one object per line. Two more are not documents at all: `completion` writes a shell script, and `token create` writes a secret, so it has no `--json` form.

A field the API adds appears in `--json` without a new release of `surreal cloud`, so treat the payload as open: read the keys you need and ignore the rest.

`--limit`, `--sort`, `--columns`, `--wide`, and `--reverse` are presentation only. They shape a table and never reach `--json`, so `--limit 1` cannot silently truncate a pipeline.

## Reading failures

On failure, `--json` writes one enveloped object to **stderr** and leaves stdout empty.

```json title="Error envelope"
{
  "kind": "conflict",
  "message": "could not pause `api`: Instance is not in a valid state",
  "status": 409,
  "code": null,
  "request_id": "01J8XYZ7QK9M3P5R7T9V1W3Y5Z",
  "hint": "The instance is busy with another change.",
  "command": "surreal cloud instance get --wait",
  "docs": null,
  "retry_after_secs": null,
  "exit_code": 6
}
```

Every key is always present, with an explicit `null` rather than being omitted, so a consumer can index without checking first.

| Key | Type | Contents |
| --- | --- | --- |
| `kind` | string | The error class. Never `null` |
| `message` | string | The whole failure chain, including which command failed |
| `status` | integer or `null` | The HTTP status, when there was one |
| `code` | string or `null` | The API's own error code, when it sent a non-empty one |
| `request_id` | string or `null` | The request identifier to quote in a support report |
| `hint` | string or `null` | What to do about it |
| `command` | string or `null` | A command to run next |
| `docs` | string or `null` | A documentation link, for the classes that have a useful one |
| `retry_after_secs` | integer or `null` | How long to wait, from the response |
| `exit_code` | integer | The process exit code, so the document and the shell agree |

One further key appears conditionally: `candidates`, an array of suggested names, when a reference failed to resolve and `surreal cloud` had near matches to offer.

`kind` is a closed vocabulary of eleven values, so it is safe to match on: `auth`, `forbidden`, `not_found`, `conflict`, `invalid`, `rate_limited`, `upstream`, `network`, `not_available`, `wait_timeout`, `unknown`.

```bash
if ! surreal cloud instance pause api --json > /dev/null 2> error.json; then
    jq -r '"\(.kind): \(.message)"' < error.json >&2
fi
```

`not_available` means a feature is not enabled for this deployment. It does not indicate a fault.

## Exit codes

The exit codes below are a stable contract; error text is not.

| Code | Meaning |
| --- | --- |
| `0` | Success, including an empty list |
| `1` | An unclassified failure |
| `2` | Bad invocation, or a destructive command that could not confirm |
| `3` | Not authenticated, or the credential expired and could not be renewed |
| `4` | Authenticated but not allowed: scope, role, or credential kind |
| `5` | The named resource does not exist |
| `6` | The resource is not in a state that allows this, or a precondition failed |
| `7` | The API rejected the request as invalid |
| `8` | Rate limited, after the retry budget was spent |
| `9` | The API or its upstream is unreachable, or the feature is not available |
| `10` | A wait gave up. **The operation is still running** |
| `11` | The credential store could not be read or written |
| `12` | A component the command needs, such as the database for `instance sql`, is not installed and was not fetched. The message names the command that installs it |
| `30` | An interactive sign-in did not complete |
| `130` | Interrupted |

`0`, `1`, and `2` keep their conventional meanings, so `if ! surreal cloud …` and usage errors behave exactly as they do with the `surreal db` CLI. Everything above `2` is additive.

`10` has its own code because "still running" is a genuinely different answer from "failed". A pipeline may reasonably poll again rather than roll back.

### Branching on the code

Capture the status rather than testing it inside an `if`, so `set -e` does not end the script and `$?` still means what you think it does.

```bash title="create-if-missing.sh"
#!/usr/bin/env bash
set -euo pipefail

status=0
surreal cloud instance get api --json > instance.json 2> error.json || status=$?

case "$status" in
    0)  ;;
    5)  surreal cloud instance create api --type shared-1 --region aws-euw1 --json > instance.json ;;
    3)  echo "credential expired; re-authenticate the runner" >&2; exit 1 ;;
    8)  exit 75 ;;  # tell the scheduler to requeue
    *)  jq -r '.message' < error.json >&2; exit "$status" ;;
esac
```

## Unattended runs

<OptionsTable
    title="Options that matter in automation"
    options={[
        {
            "name": "--json",
            "short": "-j",
            "env": "SURREAL_CLOUD_JSON",
            "description": "Emit machine-readable JSON on stdout."
        },
        {
            "name": "--no-input",
            "env": "SURREAL_CLOUD_NO_INPUT",
            "description": "Never prompt for input; fail instead. Also spelled `--non-interactive`."
        },
        {
            "name": "--yes",
            "short": "-y",
            "env": "SURREAL_CLOUD_YES",
            "description": "Assume yes for every confirmation."
        },
        {
            "name": "--quiet",
            "short": "-q",
            "description": "Suppress progress and informational output."
        },
        {
            "name": "--plain",
            "env": "SURREAL_CLOUD_PLAIN",
            "description": "Disable tables, spinners, and relative times."
        },
        {
            "name": "--org",
            "value": "<ORG>",
            "env": "SURREAL_CLOUD_ORG",
            "description": "The organisation to operate on, by id or name."
        },
        {
            "name": "--token",
            "value": "<TOKEN>",
            "env": "SURREAL_CLOUD_TOKEN",
            "description": "Personal access token to authenticate with."
        },
        {
            "name": "--timeout",
            "value": "<DURATION>",
            "default": "30s",
            "env": "SURREAL_CLOUD_TIMEOUT",
            "description": "Maximum time to wait for a single API request."
        },
        {
            "name": "--retries",
            "value": "<N>",
            "default": "3",
            "env": "SURREAL_CLOUD_RETRIES",
            "description": "How many times to retry a failed request."
        },
        {
            "name": "--debug",
            "env": "SURREAL_CLOUD_DEBUG",
            "description": "Log every API request and response to stderr."
        }
    ]}
/>

Every one of these has an environment variable, so a pipeline can set them once for the whole job rather than repeating flags on each step.

Two rules govern prompts.

**A destructive command refuses rather than proceeds when it cannot ask.** With no `--yes` and no command-specific `--force`, `instance delete` in a non-interactive session exits `2` having sent nothing. A command that quietly went ahead because there was no terminal would be the worst possible default.

**`--no-input` makes any session behave as an unattended one**, even at a terminal. Use it to test a pipeline locally: if the script works with `--no-input`, it will work in CI.

**Bash**

```bash
export SURREAL_CLOUD_NO_INPUT=1
surreal cloud instance delete staging --force
```

**PowerShell**

```powershell
$env:SURREAL_CLOUD_NO_INPUT = "1"
surreal cloud instance delete staging --force
```

## CI detection and output modes

`surreal cloud` detects a CI environment and switches to plain output on its own: no spinners, no borders, no relative times. `CI=false`, `CI=0`, and an empty `CI` all mean *not* CI, so a system that sets the variable deliberately is not trapped in plain output.

What CI changes is structure, interactivity, and how much progress is reported. What it must not change, and does not: colour, `--json` bytes, exit codes, which requests are made, timeouts, poll intervals, `--wait` defaults, and confirmation semantics.

`--json` output is byte-identical whatever the terminal, the width, or the colour settings. Plain output does not depend on terminal width either, so it is stable to diff between runs. For parsing, still prefer `--json`.

## The token in the environment

Put a personal access token in the environment and leave it out of `argv`, where `ps` and shell history can both read it. See [Authentication](/docs/explore/cli/cloud/authentication.md#personal-access-tokens) for how to create one.

```bash
export SURREAL_CLOUD_TOKEN="$(cat /run/secrets/surreal-cloud)"
export SURREAL_CLOUD_ORG=67upifj5dt6p87ch3nh5t3a8
surreal cloud instance list --json
```

Set `SURREAL_CLOUD_ORG` to the organisation **id**. An id is used as it stands; a name costs a lookup on every command.

A missing component is never downloaded silently in an unattended run: the launcher exits `12` with the install command, and `SURREAL_CLI_OFFLINE=1` forbids all network use. Install what the job needs in an earlier step, as below. See [Install](/docs/explore/cli/install.md) and [Components](/docs/explore/cli/components.md).

```yaml title=".github/workflows/instance-report.yml"
name: instance-report

on:
  schedule:
    - cron: "0 6 * * *"

jobs:
  report:
    runs-on: ubuntu-latest
    env:
      SURREAL_CLOUD_TOKEN: ${{ secrets.SURREAL_CLOUD_TOKEN }}
      SURREAL_CLOUD_ORG: ${{ vars.SURREAL_CLOUD_ORG_ID }}
      SURREAL_CLOUD_NO_INPUT: "1"
    steps:
      - run: curl -sSf https://install.surrealdb.com | sh -s -- --no-db --with cloud
      - run: surreal cloud status
      - run: surreal cloud instance list --json > instances.json
      - uses: actions/upload-artifact@v4
        with:
          name: instances
          path: instances.json
```

Run `surreal cloud status` early in a job. It takes about one second, and it names which credential is in use, whether the API answers, and which organisation resolved, all before a later step fails for one of those reasons.

> [!IMPORTANT]
> A personal access token reads the control plane and cannot write to it. A job that creates, updates, or deletes needs a login session on a dedicated machine account. See [What a personal access token cannot do](/docs/explore/cli/cloud/authentication.md#what-a-personal-access-token-cannot-do).

## Waiting in a pipeline

`instance create`, `update`, `delete`, `pause`, and `resume` wait for the operation to finish, in every output mode including `--json`. You have three choices.

| Choice | Behaviour | Use it when |
| --- | --- | --- |
| Default | Wait up to 15 minutes | The next step needs the instance ready |
| `--wait-timeout <DURATION>` | Wait that long, then exit `10` if it has not settled | The job has its own time budget |
| `--no-wait` | Return as soon as the request is accepted | Something else will follow up |

Under `--json`, a waiting command streams progress as newline-delimited JSON on **stderr** and puts one final document on stdout. That split is what keeps "exactly one document on stdout" true for a command that reports progress.

```json title="Progress events, on stderr"
{"event":"started","resource":"instance","goal":"ready","state":"pending"}
{"event":"transition","resource":"instance","from":"pending","to":"ready","elapsed_secs":86}
{"event":"finished","resource":"instance","outcome":"succeeded","state":"ready","elapsed_secs":86,"succeeded":true}
```

Six event types appear: `started`, `transition`, `heartbeat`, `poll_failed`, `throttled`, and `finished`. `instance watch --json` inverts the stream and writes these to stdout instead, because for a watch the transitions *are* the answer.

Treat exit `10` as "poll again", not "failed".

```bash title="fail-fast-then-follow.sh"
#!/usr/bin/env bash
set -euo pipefail

status=0
surreal cloud instance create api \
    --type shared-1 --region aws-euw1 \
    --wait-timeout 2m --json > instance.json || status=$?

if [ "$status" -eq 10 ]; then
    echo "still provisioning after two minutes; following the state" >&2
    surreal cloud instance watch api
elif [ "$status" -ne 0 ]; then
    exit "$status"
fi
```

Interrupting a wait does not stop the operation. It continues server-side, and `surreal cloud instance watch <name>` picks the state back up.

## Retries, timeouts, and repeated runs

`--timeout` bounds a single request and `--retries` bounds how many times one is retried. Retry behaviour depends on what the request would change: a read is retried on a connection failure or a 5xx, and a create is never retried after a 5xx, because the instance may exist and a replay would bill for two. Rate limits honour the server's `Retry-After`.

Your script needs to know what each command does when it runs again:

| Command | Repeating it |
| --- | --- |
| `instance create` | Creates a second instance. Check with `instance get` first |
| `instance backup create` | Takes a second snapshot |
| `instance pause`, `instance resume` | Safe. Already-paused prints a note, emits the same document, exits `0` |
| `instance delete` | Safe. A missing instance while waiting counts as done |
| `auth logout` | Safe. Signed out already still exits `0` with a document |
| `instance capabilities set` | Safe. No change means no write |

## Calling the API directly

When a route has no verb yet, `surreal cloud api` sends the request with the credential, headers, and retry policy already handled.

```bash
surreal cloud api get /api/cloud/v0/organizations
surreal cloud api get /api/cloud/v0/organizations --include
surreal cloud api post /api/cloud/v0/organizations -d @body.json --force
```

The path is a path, not a URL - the host comes from `--api`. Any write method asks for confirmation unless you pass `--force`. `--query key=value` and `--header name:value` are repeatable. Authentication headers cannot be overridden here; change the credential instead. `--include` prints the status and headers to stderr, and `--raw` prints the body exactly as it arrived.

## Configuration on the machine

Values a script should not have to repeat can live in the profile instead.

```bash
surreal cloud config list
surreal cloud config set json true
surreal cloud config set org acme
surreal cloud config path
```

An environment variable outranks the configured value, and `config set` warns you when the matching variable is exported. `config get` prints the value alone, and nothing at all when there is none, so `[ -z "$(surreal cloud config get org)" ]` behaves.

## Checklist before shipping a pipeline

- Set `SURREAL_CLOUD_NO_INPUT=1` so nothing can block on a prompt.
- Set `SURREAL_CLOUD_ORG` to an organisation id.
- Pass `--json` and parse with `jq`. Never grep stderr.
- Branch on exit codes, not on messages.
- Treat `10` as "poll again", and `8` as "requeue".
- Pass `--yes` or a command's `--force` deliberately, on the steps that need it.
- Give a read-only job a personal access token, and nothing more.
- Run `surreal cloud status` first, so a misconfigured job says so in one line.

## Next steps

- [Authentication](/docs/explore/cli/cloud/authentication.md) - credentials, scopes, and profiles.
- [Instances](/docs/explore/cli/cloud/instances.md) - the workflows these scripts drive.
- [`surreal cloud` reference](/docs/reference/cli/cloud.md) - every command, flag, default, and environment variable.
- [Output and exit codes](/docs/reference/cli/cloud/output-and-exit-codes.md) - the full contract.

---

Source: https://surrealdb.com/docs/explore/cli/components

# Managing components

How surreal installs, upgrades, pins, switches and removes components, how version names work (3.3.2, 2, beta, nightly), running another version once with +VERSION, and offline or unattended use.

`surreal` runs components, and it is the only thing that installs, upgrades or removes them. Five launcher commands cover the whole life of a component: `surreal install`, `surreal upgrade`, `surreal use`, `surreal uninstall` and `surreal version`. A component never installs or upgrades another one, and `surreal <component> ...` always belongs to the component itself.

This page explains how components are stored, how to name a version, and how to install, upgrade, pin, switch and remove them. The reference for each command is [Launcher commands](/docs/reference/cli/launcher.md).

## How components are stored

Every component version is a separate executable under the SurrealDB home, `~/.surrealdb` unless `SURREAL_CLI_HOME` says otherwise.

```text
~/.surrealdb/components/<name>/<version>/surreal-<name>   the executable
~/.surrealdb/components/<name>/pin                        the version that runs by default
```

Several versions of one component can sit side by side. The **pin** names the one that runs when you type `surreal db ...`, and nothing else decides it. The first install of a component pins it, a later install leaves the pin alone, and only `surreal use`, `surreal upgrade` and `surreal uninstall` move it. That is why installing a second database version cannot quietly change which database `surreal db start` runs.

When you run a component, `surreal` looks for its executable in this order:

1. The version named with `+VERSION`, from the store.
2. The pinned version, from the store.
3. A `surreal-<name>` beside the `surreal` executable, which is how a package that ships several binaries together provides them.
4. A `surreal-<name>` on your `PATH`.

A name that is not a component, such as `surreal foo`, runs a `surreal-foo` from your `PATH` if one exists, and reports an unknown component otherwise.

## Naming a version

Commands that take `COMPONENT[@VERSION]` accept these forms.

| Form | Meaning |
| --- | --- |
| `db` | The newest stable release. While a component has no stable release, its newest beta, then its newest alpha |
| `db@3.3.2` | That exact release (`db@v3.3.2` also works) |
| `db@2` | The newest stable release on major line 2 |
| `db@latest` | The newest stable release |
| `db@beta`, `db@alpha` | The newest release on that prerelease channel |
| `db@nightly` | The latest nightly build. Only the database publishes nightlies |

## See what is installed

```bash
surreal version
```

The table shows each component, the version that runs, where that version comes from, every installed version, and the path it runs from. In the `SOURCE` column, `pinned` is a version the launcher installed and pinned, and `path` is a copy found beside `surreal` or on `PATH`. `surreal version --json` prints the same information as one JSON document, and `surreal --version` prints it on a few lines.

## Install a component

```bash
surreal install cloud
surreal install db@3.3.2 cloud@0.1.0
```

`surreal install` downloads each release, checks it against its published SHA-256, runs it once to confirm it starts, and only then puts it in place. The first install of a component becomes its default. Installing another version of a component you already have installs it beside the pinned one and leaves the pin where it was.

To make what you install the default, add `--pin`. A version you name exactly becomes the default whatever it was before. A channel's release, such as `db@2` or `db@beta`, becomes the default unless the current default is a later release.

```bash
surreal install db@2 --pin
```

## Upgrade

```bash
surreal upgrade
```

With no arguments, `surreal upgrade` upgrades the launcher and every installed component that has a default version. Each follows the channel its default is on: a stable release stays on its major line, so a database on 2.x upgrades to the newest 2.x, and a prerelease follows its own channel. The `--dry-run` flag says what would change without changing it.

```bash
surreal upgrade --dry-run
```

To upgrade only some things, or to move to a particular version, name them.

```bash
surreal upgrade cloud
surreal upgrade db@3.3.2
surreal upgrade db@3
surreal upgrade cli
```

`cli` is the launcher itself. Naming a version moves the default to it, installing it first if needed.

The database also takes its familiar release flags, which are shorthand for naming the channel or version.

| Flag | Same as |
| --- | --- |
| `surreal upgrade --nightly` | `surreal upgrade db@nightly` |
| `surreal upgrade --beta` | `surreal upgrade db@beta` |
| `surreal upgrade --alpha` | `surreal upgrade db@alpha` |
| `surreal upgrade --version <VERSION>` | `surreal upgrade db@<VERSION>` |

> [!NOTE]
> The database no longer upgrades itself. `surreal db upgrade` is not run and points you to `surreal upgrade db`.

## Pin or switch a version

`surreal use` makes an installed version the default. It never downloads.

```bash
surreal use db@3.3.2
surreal use db@2
```

A major line or channel, such as `db@2`, selects the newest installed release on it. To pin a version that is not installed yet, use `surreal install db@<VERSION> --pin` or `surreal upgrade db@<VERSION>`.

## Run another version once

`+VERSION` as the first argument runs one version of whatever follows, for that command only. The pin does not change. If that version is not installed, `surreal` offers to install it first, under the rules in [Offline and unattended use](#offline-and-unattended-use).

```bash
surreal +2.6.5 db start --user root --pass secret
surreal +2 db export --endpoint http://localhost:8000 backup.surql
```

The version takes the same forms as in `COMPONENT@VERSION`: a release, a major line, or `latest`, `beta`, `alpha` or `nightly`. This is how you run an older database against data that has not been migrated yet, or compare two releases.

## Remove a component

```bash
surreal uninstall db@2.7.0
surreal uninstall cloud
```

`surreal uninstall <component>@<version>` removes that version. Naming a component alone removes every version of it and its default. The default version is removed by name only when nothing else is installed, so move the pin with `surreal use` first.

## Offline and unattended use

A component is never fetched behind your back.

- **At a terminal**, `surreal` names the download host and asks before it installs a missing component. `surreal db start` on a machine without the database offers to install it.
- **Unattended** (a CI runner, an agent, `--no-input`, a pipe on standard input, or a dumb terminal), `surreal` makes no request, and exits with code 12 and the command that would install the component. The `SURREAL_CLI_YES` variable does not count as consent to a download in the middle of another command; to install without a prompt, run `surreal install` explicitly, with `--yes` if you wish.
- **With `SURREAL_CLI_OFFLINE=1`**, the network is never touched, and a missing component fails at once.

In a pipeline, install what you need in an early step.

```bash title="CI step"
surreal install db cloud --no-input
```

## Environment variables

| Variable | Effect |
| --- | --- |
| `SURREAL_CLI_HOME` | Where the SurrealDB home is. Defaults to `~/.surrealdb` |
| `SURREAL_CLI_OFFLINE` | `1` forbids every network request |
| `SURREAL_CLI_DOWNLOAD_ROOT` | Replaces the download host, for a mirror |
| `SURREAL_CLI_NO_BANNER` | Hides the banner above `surreal help` |
| `SURREAL_CLI_JSON`, `SURREAL_CLI_PLAIN` | Same as `--json` and `--plain` on the launcher's commands |

These are the launcher's variables. Variables that start with `SURREAL_` and not `SURREAL_CLI_` belong to the database, and the launcher never reads them. Cloud's variables start with `SURREAL_CLOUD_`. See [Launcher commands](/docs/reference/cli/launcher.md) for exit codes and the full list.

## Package managers and shared machines

Where a package manager such as Homebrew or Nix owns `surreal`, the launcher refuses to install or replace anything in its place, and the package manager upgrades it. Components that the package provides beside `surreal` run before any pinned copy in the store, so a package upgrade is not shadowed by an older pinned version.

---

Source: https://surrealdb.com/docs/explore/cli/database

# Using the database

Run the SurrealDB database through the surreal launcher: surreal db start, sql, import, export, is-ready, validate, fix, ml and mcp. Workflows for local development, moving data, and checking a server, plus the deprecated surreal start forms.

`surreal db` is the database component. It starts a server, opens an interactive SurrealQL shell, moves data in and out, and checks that a server is ready. Everything after `db` is forwarded to the database unchanged, so every database flag and environment variable works the way its [reference page](/docs/reference/cli/db.md) describes.

The database is installed by the [install script](/docs/explore/cli/install.md) by default. If it is not installed, `surreal db <command>` offers to install it the first time you run it.

## Commands

| Command | Use it to |
| --- | --- |
| [`surreal db start`](/docs/reference/cli/db/commands/start.md) | Run a server, in memory or on disk |
| [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) | Open a SurrealQL shell, or pipe queries into a server |
| [`surreal db import`](/docs/reference/cli/db/commands/import.md) | Load a SurrealQL file into a database |
| [`surreal db export`](/docs/reference/cli/db/commands/export.md) | Write a database out as a SurrealQL file |
| [`surreal db is-ready`](/docs/reference/cli/db/commands/isready.md) | Check that a server accepts connections |
| [`surreal db validate`](/docs/reference/cli/db/commands/validate.md) | Check that SurrealQL files parse |
| [`surreal db fix`](/docs/reference/cli/db/commands/fix.md) | Repair storage issues |
| [`surreal db ml`](/docs/reference/cli/db/commands/ml.md) | Import and export machine-learning models |
| [`surreal db mcp`](/docs/reference/cli/db/commands/mcp.md) | Run the Model Context Protocol server over stdio |
| [`surreal db version`](/docs/reference/cli/db/commands/version.md) | Print the database version, or a server's |

Upgrading the database is not a database command. Use [`surreal upgrade db`](/docs/explore/cli/components.md#upgrade).

## Run a database for development

Start an in-memory server with a root user. It listens on `127.0.0.1:8000`.

```bash
surreal db start --user root --pass secret
```

Add a storage path to keep the data between runs.

```bash
surreal db start --user root --pass secret surrealkv:mydata
```

A relative path is resolved from the folder you run the command in. See [`surreal db start`](/docs/reference/cli/db/commands/start.md) for the storage engines, binding, TLS and capabilities.

In a second terminal, open a shell against it.

```bash
surreal db sql --username root --password secret --pretty
```

The shell connects to `ws://localhost:8000` and the `main` namespace and database. Name others with `--namespace` and `--database`. To run queries from a script instead, pipe them in.

```bash
echo "SELECT * FROM person;" | surreal db sql --username root --password secret --hide-welcome
```

## Check that a server is ready

`surreal db is-ready` exits successfully once a server accepts connections, which suits a health check or a wait loop in a script.

```bash
surreal db start --user root --pass secret &
until surreal db is-ready --endpoint http://localhost:8000; do sleep 1; done
```

## Move data in and out

Export a database to a SurrealQL file, and import it into another.

```bash
surreal db export --endpoint http://localhost:8000 --username root --password secret --namespace app --database main backup.surql
surreal db import --endpoint http://localhost:8000 --username root --password secret --namespace app --database main backup.surql
```

Both commands need a namespace and a database. `surreal db export` writes to standard output when you give it no file, or `-` as the file, so it composes with other tools.

To import into or export from a SurrealDB Cloud instance, use [`surreal cloud instance import` and `surreal cloud instance export`](/docs/explore/cli/cloud/instances.md#connect-to-an-instance), which resolve the endpoint and credentials and then run these commands for you.

## Validate query files

`surreal db validate` parses SurrealQL files without running them. With no argument it checks every `**/*.surql` file under the current folder, so it fits a pre-commit hook or a CI step.

```bash
surreal db validate
surreal db validate "schema/**/*.surql"
```

## Run a different database version

The launcher keeps several database versions side by side. Run one command against another version with `+VERSION`, or change the default with `surreal use`.

```bash
surreal +2.6.5 db export --endpoint http://localhost:8000 --namespace app --database main
surreal use db@2
```

See [Managing components](/docs/explore/cli/components.md) for installing, pinning and upgrading versions.

## The deprecated top-level commands

The database's commands also run without `db`, and print a warning that names the replacement.

```bash
surreal start
```

```text title="Output"
warning: `surreal start` is deprecated; use `surreal db start`.
```

The set of commands that still run this way is closed: `start`, `sql`, `import`, `export`, `isready`, `is-ready`, `validate`, `fix`, `ml`, `module` and `mcp`, and database options placed before a command, as in `surreal --log debug start`. The warning goes to stderr and names only the command, never an argument, so it is safe in CI logs. A command the database adds later runs only as `surreal db <command>`. Change scripts and service definitions to the `surreal db` form.

> [!NOTE]
> Docker and other container images run the database directly. Inside an image, the command is `surreal start` and not `surreal db start`, and the examples in [Running with Docker](/docs/running/docker.md) use that form.

## Environment variables

`surreal db` reads the same `SURREAL_*` variables as before. `SURREAL_USER`, `SURREAL_PASS`, `SURREAL_NAMESPACE`, `SURREAL_DATABASE` and the rest are listed on the [database environment variables](/docs/reference/cli/db/environment-variables.md) page. The launcher's own variables start with `SURREAL_CLI_`, and it never reads `SURREAL_*` itself, so the two sets stay apart.

---

Source: https://surrealdb.com/docs/explore/cli/install

# Install the SurrealDB CLI

Install the surreal command with the install script on macOS, Linux and Windows. Options for the database switch (--db, --no-db, SURREAL_CLI_INSTALL_DB), choosing a release, adding components, mirrors, package managers and Docker.

This page installs the `surreal` command and, optionally, the components it runs. Pick one installation method; everything after it applies whichever you chose.

The install script puts the `surreal` launcher on your machine, then uses it to install the database as a component and pin it, so `surreal db start` works from then on with no network connection. The script checks every download against the SHA-256 published beside it, and installs nothing if a check fails.

## Install on macOS and Linux

```bash
curl -sSf https://install.surrealdb.com | sh
```

The script installs `surreal` into `/usr/local/bin`. If that folder is not writable it falls back to `~/.surrealdb`, or asks where to install when you pass `--interactive`, and then prints the `PATH` line to add. It never edits your shell profile. The script is served as plain text, so you can open the same URL in a browser and read it before you run it.

Pass options after `-s --`, which is how `sh` forwards arguments to a script it reads from a pipe.

```bash
curl -sSf https://install.surrealdb.com | sh -s -- --no-db --with cloud
```

<OptionsTable
	title="Options"
	options={[
		{ "name": "--db", "description": "Install the database and pin it. This is the default." },
		{ "name": "--no-db", "description": "Install `surreal` without the database. The database installs on first use instead." },
		{ "name": "--minimal", "description": "The same as `--no-db`." },
		{ "name": "--version", "short": "-v", "value": "<VERSION>", "description": "The database release to install and pin." },
		{ "name": "--nightly", "short": "-n", "description": "Install the latest nightly database." },
		{ "name": "--beta", "short": "-b", "description": "Install the latest beta database." },
		{ "name": "--alpha", "short": "-a", "description": "Install the latest alpha database." },
		{ "name": "--with", "value": "<A,B>", "description": "Further components to install, comma-separated. For example `--with cloud`." },
		{ "name": "--interactive", "short": "-i", "description": "Ask where to install when the default folder is not writable." },
		{ "name": "<DIR>", "description": "Where to put `surreal`, instead of `/usr/local/bin`." },
		{ "name": "--help", "short": "-h", "description": "Print the options and exit." }
	]}
/>

`--version`, `--nightly`, `--beta` and `--alpha` choose the database release, so they apply only when the database is installed. The script refuses them together with `--no-db`.

## Install on Windows

Run the PowerShell script from a normal or an elevated shell.

```powershell
irm https://windows.surrealdb.com | iex
```

The script installs `surreal.exe` into `C:\Program Files\SurrealDB` from an elevated shell, and into `%LOCALAPPDATA%\SurrealDB` otherwise, and adds that folder to your user `PATH`. A folder you name yourself is not added to `PATH`.

`irm | iex` cannot pass options. To pass them, run the script as a script block.

```powershell
& ([scriptblock]::Create((irm https://windows.surrealdb.com))) -NoDb -With cloud
```

<OptionsTable
	title="Parameters"
	options={[
		{ "name": "-Db", "description": "Install the database and pin it. This is the default." },
		{ "name": "-NoDb", "description": "Install `surreal` without the database." },
		{ "name": "-Minimal", "description": "Another name for `-NoDb`." },
		{ "name": "-Version", "short": "-v", "value": "<VERSION>", "description": "The database release to install and pin." },
		{ "name": "-Nightly", "short": "-n", "description": "Install the latest nightly database." },
		{ "name": "-Beta", "short": "-b", "description": "Install the latest beta database." },
		{ "name": "-Alpha", "short": "-a", "description": "Install the latest alpha database." },
		{ "name": "-With", "value": "<A,B>", "description": "Further components to install. For example `-With cloud`." },
		{ "name": "-Interactive", "short": "-i", "description": "Ask where to install when the install folder is not writable." },
		{ "name": "<InstallDir>", "description": "Where to put `surreal.exe`." }
	]}
/>

## Choose whether to install the database

The database is installed by default. Choose explicitly with `--db` or `--no-db` (`-Db` or `-NoDb` on Windows), or with the `SURREAL_CLI_INSTALL_DB` environment variable for callers that cannot pass options, such as `curl ... | sh` in a Dockerfile or a CI step.

| Setting | Effect |
| --- | --- |
| `SURREAL_CLI_INSTALL_DB=1` | Install the database, as `--db` does |
| `SURREAL_CLI_INSTALL_DB=0` | Install `surreal` alone, as `--no-db` does |

```bash title="Install surreal alone from a pipe"
curl -sSf https://install.surrealdb.com | SURREAL_CLI_INSTALL_DB=0 sh
```

A flag wins over the variable, and the last of `--db`, `--no-db` and `--minimal` wins over the others. Because the default may change, a script that needs one outcome should say so with a flag or the variable.

Without the database, `surreal db <command>` offers to install it the first time you run it, and `surreal install db` installs it at any time. Components installed this way follow the rules in [Managing components](/docs/explore/cli/components.md).

## Install more components

`--with` installs further components in the same step, through the launcher.

```bash
curl -sSf https://install.surrealdb.com | sh -s -- --with cloud
```

With `surreal` already installed, `surreal install cloud` does the same. [Managing components](/docs/explore/cli/components.md) covers versions, upgrading and pinning.

## Confirm the installation

`surreal version` lists each component, the version that runs, and where it runs from.

```bash
surreal version
```

```text title="Sample output"
COMPONENT  VERSION  SOURCE  INSTALLED  PATH
cli        0.1.0    cli                /usr/local/bin/surreal
db         3.3.4    pinned  3.3.4      /home/you/.surrealdb/components/db/3.3.4/surreal-db
cloud      0.1.0    pinned  0.1.0      /home/you/.surrealdb/components/cloud/0.1.0/surreal-cloud
```

`surreal --version` prints the same information on a few lines, and `surreal version --json` prints it as one JSON document.

## Where files go

The launcher keeps everything it installs in one folder, the SurrealDB home. It is `~/.surrealdb` (`%USERPROFILE%\.surrealdb` on Windows) unless `SURREAL_CLI_HOME` names another folder. Each component version is a separate file under `components/<name>/<version>/`, which is what lets several versions sit side by side.

## Other ways to install

### Package managers

Homebrew, Chocolatey and Scoop install the database as a single `surreal` executable, and update it through the package manager. That executable is the database itself, not the launcher: it has no `surreal db` prefix and no component management, so run its commands without `db` and update it with the package manager. See the installation pages for [macOS](/docs/running/installation/macos.md) and [Windows](/docs/running/installation/windows.md). The install script leaves a `surreal` that a package manager owns alone; remove the package first, or install into another folder.

### Docker

The Docker image runs the database directly, so Docker examples use `surreal start` rather than `surreal db start`, and nothing inside the image installs or upgrades components. See [Running with Docker](/docs/running/docker.md).

### A mirror

`SURREAL_CLI_DOWNLOAD_ROOT` replaces the download host, for an internal mirror. The mirror must publish the launcher under `cli/launcher/`, each component in its own folder beneath `cli/`, and the database at its root, in the same layout as `https://download.surrealdb.com`. The value is an `http` or `https` address with no user name, password, query or fragment.

### Offline machines

`SURREAL_CLI_OFFLINE=1` forbids every network request. A component that is already installed runs normally, and a missing one fails at once with the command that would install it. See [Managing components](/docs/explore/cli/components.md#offline-and-unattended-use).

## Troubleshooting

### A different `surreal` runs

A `surreal` earlier on your `PATH`, such as an older standalone database, runs instead of the launcher. The install script warns when it finds one. Remove the older executable, or put the launcher's folder first on `PATH`. `surreal version` shows the path each component runs from.

---

Source: https://surrealdb.com/docs/explore/cli/modules

# Working with modules

Scaffold, build, inspect and run Surrealism WebAssembly modules from the command line with surreal db module, and how the mod component of the surreal launcher relates to it.

Modules are WebAssembly extensions that add functions to the database. This page shows the command-line workflow for a [Surrealism](/docs/learn/extensions/plugins/overview.md) module: scaffold a project, build it, inspect the result, and run a function before you deploy it.

`mod` is the launcher's component for this work, and it is the intended home of these commands. Its first release is not published yet, so `surreal install mod` does not work today. Until it is, the same commands are available from the database component as `surreal db module`, and the examples below use that form. The top-level `surreal module` form is deprecated and prints a warning.

## Create a module project

```bash
surreal db module init my-module
```

`init` scaffolds a Rust project with a `surrealism.toml` and a starter `src/lib.rs`. It asks for the organisation and module name interactively. In a script, add `--headless` with `--org`, and `--name` when the module should not take the folder's name.

```bash
surreal db module init my-module --headless --org acme --name my-module
```

## Build the module

Run `build` from the project folder, or name the source folder.

```bash
surreal db module build
surreal db module build ./my-module --out my-module.wasm
```

A build is a release build. Add `--debug` for a debug build.

## Inspect and run it

`info` shows the module's information, and `sig` shows the signature of one of its functions.

```bash
surreal db module info my-module.wasm
surreal db module sig my-module.wasm --fnc greet
```

`run` calls a function with arguments, without a database. Repeat `--arg` for each argument.

```bash
surreal db module run my-module.wasm --fnc greet --arg "World"
```

## Use the module in a database

Define the built file in a database with [`DEFINE MODULE`](/docs/reference/query-language/statements/define/module.md) and call its functions from SurrealQL. The [Surrealism guides](/docs/learn/extensions/plugins/overview.md) cover writing the functions, the attribute reference and deployment.

## Reference

The command reference is [`surreal db module`](/docs/reference/cli/db/commands/module.md) today, and the [modules page](/docs/reference/cli/modules.md) in the CLI reference explains how it relates to `surreal mod`.

---

Source: https://surrealdb.com/docs/explore/ml-models

# Introduction

SurrealML runs trained ML models in SurrealDB: train in Python. Then load and infer with sklearn or PyTorch.

SurrealML is an engine for storing and executing trained ML models. It leaves training to existing frameworks and works with them to ease the storage, loading, and execution of models. Someone using SurrealML will be able to train their model in a chosen framework in Python, save their model, and load and execute the model in either Python or Rust. The inference engine is composed of python bindings interacting with a core written in Rust. This means that the exact same code that the Python client calls will be running on a production node by itself, or in the database. While the SurrealML engine runs in the database, it is developed in a completely isolated GitHub repository, leaving the user free to choose how they deploy and interact with the SurrealML engine.

The first step is to install the package and check that SurrealML works on your machine.

## Installation

To install SurrealML, make sure you have Python installed. Then, install the `SurrealML` library and either `PyTorch` or `sklearn`, based on your model choice. You can install the package with both `PyTorch` and `SKLearn` with the command below:

```bash
pip install "git+https://github.com/surrealdb/surrealml#egg=surrealml[sklearn,torch]"
```

If you want to use `SurrealML` with `sklearn` you will need the following installation:

```bash
pip install "git+https://github.com/surrealdb/surrealml#egg=surrealml[sklearn]"
```

For `PyTorch`:

```bash
pip install "git+https://github.com/surrealdb/surrealml#egg=surrealml[torch]"
```

Once the package is installed, you can then train and save your first model using sklearn.

## Quick start with Sklearn

Sklearn models can also be converted and stored in SurrealML’s `.surml` format enabling developers to load them in any Python version as we are not relying on `pickle`. Metadata in the file also enables other users of the model to use them as they are without having to worry about the normalisation of the data or getting the right inputs in order. We will cover `.surml` files in more depth in the storage section. You will also be able to load your Sklearn models in Rust and run them, meaning you can use them in your SurrealDB server.

Before we start writing any training code we need to import the following:

```python
from sklearn.linear_model import LinearRegression
from surrealml import SurMlFile, Engine
from surrealml.model_templates.datasets.house_linear import HOUSE_LINEAR
```

Here we can see that we have imported the standard `sklearn` `LinearRegression` model. We then import the `SurMlFile` object which will facilitate the saving, loading, and execution of the trained model. We will use the `Engine` enum to tell our `SurMlFile` object if we are using `sklearn` or `torch`. We then finally import a small example dataset called `HOUSE_LINEAR`. This example dataset is a simple linear correlation between house prices, the square foot of the house, and the number of floors. This dataset is also used in the CI testing pipeline when we push updates.

Now that we have imported everything that we need, we can train our model with the code below:

```python
model = LinearRegression()
model.fit(HOUSE_LINEAR["inputs"], HOUSE_LINEAR["outputs"])
```

This will give us a trained model, which we can now save with `SurrealML`. First we declare a `SurMlFile` object instance with the inputs, name, model object, and engine with the following code:

```python
file = SurMlFile(
	model=model,
	name="house-price-prediction",
	inputs=HOUSE_LINEAR["inputs"],
	engine=Engine.SKLEARN
)

file.add_version(version="0.0.1")
```

The next step is optional, but it would be nice to map our inputs to some keys. We must be careful with the order that we declare our columns as they need to be mapped with the order of inputs from the vector that our model was trained on. If you click on `HOUSE_LINEAR` you will see the following declaration:

```python
HOUSE_LINEAR = {
    "inputs": inputs,
    "outputs": house_price,

    "squarefoot": squarefoot,
    "num_floors": num_floors,
    "input order": ["squarefoot", "num_floors"],
    "raw_inputs": {
        "squarefoot": raw_squarefoot,
        "num_floors": raw_num_floors,
    },
    "normalised_inputs": {
        "squarefoot": squarefoot,
        "num_floors": num_floors,
    },
    "normalisers": {
        "squarefoot": {
            "type": "z_score",
            "mean": squarefoot.mean(),
            "std": squarefoot.std()
        },
        "num_floors": {
            "type": "z_score",
            "mean": num_floors.mean(),
            "std": num_floors.std()
        }
    },
}
```

Here we can see that there are some normalisers involved. We can also see that the input order for the model training was `["squarefoot", "num_floors"]` . Therefore, we can add the column names to our `SurMlFile` object instance with the code below:

```python
file.add_column("squarefoot")
file.add_column("num_floors")
```

The `add_column` was the only order that we have to be careful about. We need to add our normalisers to the `SurMlFile` object instance with the code below but we do not have to worry about the order as the normalisers by default will be mapped to the columns:

```python
file.add_normaliser(
	"squarefoot",
	"z_score",
	HOUSE_LINEAR["squarefoot"].mean(),
	HOUSE_LINEAR["squarefoot"].std()
)

file.add_normaliser(
	"num_floors",
	"z_score",
	HOUSE_LINEAR["num_floors"].mean(),
	HOUSE_LINEAR["num_floors"].std()
)
```

We are nearly done with adding metadata, with just one item left to add: the output that the model is trying to predict. This can be achieved by the following code:

```python
file.add_output(
	"house_price",
	"z_score",
	HOUSE_LINEAR["outputs"].mean(),
	HOUSE_LINEAR["outputs"].std()
)
```

And now our file is ready to be saved which is done with the code below:

```python
file.save(path="./linear.surml")
```

The file is stored in the `.surml` format meaning that there is a header with the data that we defined, and the weights are stored in the `ONNX` format. This means that there are no language-dependent dependencies. We are now ready to load and perform calculations on our model. We can load our model with the following code:

```python
new_file = SurMlFile.load(path="./linear.surml", engine=Engine.SKLEARN)
```

If you are confident in what you are doing at this point, you can choose to perform calculations through `surrealML` using a raw compute in which the raw vector of inputs is directly passed into the model with the code below:

```python
print(new_file.raw_compute(input_vector=[5, 6]))
```

However, if you want the normalisation to be automatically applied, and inputs mapped via keys, we can use a buffered compute with the following code:

```python
print(
	new_file.buffered_compute(
		value_map={
			"squarefoot": 5,
			"num_floors": 6
		}
	)
)
```

Both types of executions are executing the ML model using the Rust engine internally. As a result, the exact same code will be running your model in SurrealDB or your inference server regardless of whether you choose to build a server in Python or Rust. This `.surml` file can also be loaded by Rust, or by any Python version that has surrealML installed, to execute inference.

With the model loading and executing locally, the next section covers deploying it onto SurrealDB by uploading it.

## Model deployment

Before we try and upload our model, we need to have a node running. We can do this with the `docker-compose.yml` file below:

```yaml
version: '3'
services:
  surrealdb:
    image: surrealdb/surrealdb
    command: start
    environment:
      - SURREAL_USER=root
      - SURREAL_PASS=secret
      - SURREAL_LOG=trace
    ports:
      - 8000:8000
```

Once our node is running via docker, we can then upload our trained model with the following code:

```python
url = "http://0.0.0.0:8000/ml/import"
SurMlFile.upload(
    path="./linear.surml",
    url=url,
    chunk_size=36864,
    namespace="main",
    database="main",
    username="root",
    password="secret"
)

```

The `upload` function will chunk the model and stream it up to a SurrealDB node. We can then perform an execution of the model with the following SurrealQL function:

```python
ml::house-price-prediction<0.0.1>({
	squarefoot: 500.0,
	num_floors: 2.0
})
```

Here, `house-price-prediction` is the name of the model. The `<0.0.1>` is the version of the model. The SurrealQL function above will give us a model output from the inputs that we passed in.

We can now explore how our model can interact with other data with the SurrealQL script below:

```sql
CREATE house_listing SET squarefoot_col = 500.0, num_floors_col = 1.0;
CREATE house_listing SET squarefoot_col = 1000.0, num_floors_col = 2.0;
CREATE house_listing SET squarefoot_col = 1500.0, num_floors_col = 3.0;

SELECT * FROM (
	SELECT *,
	ml::house_price_prediction<0.0.1>({
		squarefoot: squarefoot_col,
		num_floors: num_floors_col
	}) AS price_prediction
	FROM house_listing
)
WHERE price_prediction > 177206.21875;
```

Here we are feeding the fields from the table `house_listing` into a model we uploaded called `house-price-prediction` with a version of `0.0.1`. We then get the results of that trained ML model as the field `price_prediction`. We then use the calculated predictions to filter the records, giving us the following result:

```json
[
  {
    "id": "house_listing:7bo0f35tl4hpx5bymq5d",
    "num_floors_col": 3,
    "price_prediction": 406534.75,
    "squarefoot_col": 1500
  },
  {
    "id": "house_listing:8k2ttvhp2vh8v7skwyie",
    "num_floors_col": 2,
    "price_prediction": 291870.5,
    "squarefoot_col": 1000
  }
]
```

Having covered everything that we need to get up and running with SurrealML, we should explore some other concepts in more depth to get the most out of SurrealML and be able to troubleshoot problems.

## In this section

- [Computation](/docs/explore/ml-models/surrealml/computation.md) - where a model runs, and what the database does with it
- [Storage](/docs/explore/ml-models/surrealml/storage.md) - how a trained model is stored and versioned

---

Source: https://surrealdb.com/docs/explore/ml-models/surrealml/computation

# Computation

SurrealML enables machine learning models to be greatly simplified, ensuring reproducibility and consistency in machine learning pipelines.

Once a model is loaded, you will want to execute it to perform a calculation. First we will cover the high-level execution of models in Python and Rust, then we will explore what happens internally for these executions. Before we go through some code examples, let us define the different types of computation that we can perform on a model.

## Raw compute

Raw compute passes an array directly into the model for execution. Here we bypass all the metadata in the file. This means that you need to perform your own normalisations on your input data if you normalised the model training data when training the model. This approach is more beneficial to complex data structures such as images, as they are multi-dimensional. When executing the raw compute, you do not need to pass in the dimensions of the input, as these are extracted from the saved model itself, as [`compute.rs`](https://github.com/surrealdb/surrealml/blob/34baa045da9184ccd1479220e6205dd298b78ee3/modules/core/src/execution/compute.rs#L80C68-L80C75) shows.

## Buffered compute

Buffered compute passes a dictionary or hashmap with the names of the columns as the keys, and the values associated with the column. The engine then checks all the columns, applies normalisation functions to the inputs if the normalisation functions are present for the column, and constructs a vector in the correct order for inputs. At this point, the model computation will error if any of the columns are missing, only allowing valid input to pass through. The processed vector is then passed into the raw compute function.

Now that we have explored the two types of computation, we can explore how to perform these computations.

## Executing a model in Python

We can execute a model in Python using the following code:

```python
# raw compute
print(new_file.raw_compute(input_vector=[5, 6]))

# buffered compute (implement data from the metadata)
print(
	new_file.buffered_compute(
		value_map={
			"squarefoot": 5, 
			"num_floors": 6
		}
	)
)
```

## Executing a model in Rust

Starting with the following `use` statements will let us bring a number of necessary types into scope:

```rust
use surrealml_core::storage::surml_file::SurMlFile;
use surrealml_core::execution::compute::ModelComputation;
use ndarray::ArrayD;
use std::collections::HashMap;
```

We can then create a compute unit with the code below:

```rust
let compute_unit = ModelComputation {
    surml_file: &mut new_file,
};
```

First we can do a buffered compute with a standard HashMap with the following code:

```rust
let mut input_values = HashMap::new();
input_values.insert(String::from("squarefoot"), 1000.0);
input_values.insert(String::from("num_floors"), 2.0);

let output = compute_unit.buffered_compute(&mut input_values).unwrap();
```

If we want to perform a raw compute, we can do so with the code below:

```rust
let x = vec![1000.0, 2.0];
let data: ArrayD<f32> = ndarray::arr1(&x).into_dyn();

let output = compute_unit.raw_compute(data, None).unwrap();
```

Note that the input dimensions fed into the `.raw_compute()` method are specified as None, as they will be extracted directly from the saved model.

## Executing a model in SurrealDB

Once the model is stored in the database, it can be called with the following code:

```sql
	ml::house-price-prediction<0.0.1>({
		squarefoot: squarefoot_col,
		num_floors: num_floors_col
	})
```

Here the `ml::` is saying that we are using the SurrealDB’s ML module. The `house-price-prediction` is the name of the model in the metadata of the `surml` file. The `<0.0.1>` is the version that is also defined in the header of the `surml` file. The object passed into it is essentially a hashmap for the buffered compute. This calculation then yields a result that can then be inputted into the rest of the SurrealQL statement.

## Execution mechanics

The interface between the ONNX `C++` and Rust can be found in [`onnx_environment.rs`](https://github.com/surrealdb/surrealml/blob/main/modules/core/src/execution/onnx_environment.rs). Based on different build parameters, we embed the `libonnxruntime` library into the Rust binary. When performing an execution, we load the `libonnxruntime` library into a `Lazy<Arc<Environment>>`. This leads to a single Environment instance that lasts for the entire duration of the program. This environment can be accessed by any thread, which is safe to do so as access is protected by a lock that is only available once another thread has given it up.

Tests of running a computation in Rust live in [the `compute.rs` test module](https://github.com/surrealdb/surrealml/blob/34baa045da9184ccd1479220e6205dd298b78ee3/modules/core/src/execution/compute.rs#L161). Python does not perform any computations, but all interactions between the Rust module and the Python code sit in [`rust_adapter.py`](https://github.com/surrealdb/surrealml/blob/develop/surrealml/rust_adapter.py), in the `RustAdapter` object.

---

Source: https://surrealdb.com/docs/explore/ml-models/surrealml/storage

# Storage

SurrealML enables machine learning models to be greatly simplified, ensuring reproducibility and consistency in machine learning pipelines.

If you have completed the introduction, you would have stored your ML model in a `.surml` file. Seeing as the `.surml` file is at the heart of storage, we will start by covering the anatomy of a `.surml` file.

## The anatomy of a surml file

A `.surml` file is essentially a header, with weights stored in the ONNX format. Interacting with the file takes the following form:

<img src="~/assets/img/image/light/surrealml-storage-schema.png" darkSrc="~/assets/img/image/dark/surrealml-storage-schema.png" alt="Diagram of the SurrealML storage flow: Python and Rust programs reach the Rust ML core, which reads the SURML file's metadata and ONNX model and executes it through the ONNX runtime library." />

<br />
<br />

The metadata is data around the model. You can see [the header definition in `header/mod.rs`](https://github.com/surrealdb/surrealml/blob/main/modules/core/src/storage/header/mod.rs). The metadata has the following fields (all fields can be empty if needed):

- **keys ⇒** The name of the column and the index of where that column is placed in an input vector
- **normalisers ⇒** A map of normalisers with parameters to execute the normaliser, and a reference to the column that the normaliser is attached to
- **output ⇒** The name of the output and a normaliser attached to the output (if needed)
- **name ⇒** The name of the model being stored
- **version ⇒** The version of the model. Model versions use a default and auto-increment function that results in formats such as  `0.0.1` and `0.0.2`.
- **description ⇒** The description of the model
- **engine ⇒** The type of engine that was used to train the model, such as the native Linfa Rust module, the Rust PyTorch model, or Undefined (trained using a third party module such as Sklearn, PyTorch, etc.)
- **Origin ⇒** Where the model was trained, such as locally, in the database, or Undefined (local is automatically defined when using the Python surrealML package)
- **input_dims ⇒** the input dimensions that are needed to perform a model imputation. This is automatically defined when tracing the model when saving it in the `.surml` file format.

When reading a file, the loader loads the first 4 bytes of a file. Those first 4 bytes are then converted into a 4 byte integer. This 4 byte integer then tells the loader how many bytes to load to get all of the metadata about the model. Once this is loaded, we can then assume that the rest of the file is ONNX protobuf, and this protobuf data is then loaded into the ONNX runtime C++ library for inference calculations.

## Why ONNX

SurrealML supports the ONNX runtime which is the standard format for storing machine learning weights, and is officially [supported by Microsoft](https://github.com/microsoft/onnxruntime).

ONNX is the only model format SurrealML runs. The model inside a `.surml` file is an ONNX graph, and SurrealDB executes it with ONNX Runtime. SurrealML cannot load other weight formats, such as GGUF files for quantised large language models.

Internally, SurrealML exports the torch model into ONNX format which is officially supported by [PyTorch](https://pytorch.org/tutorials/advanced/super_resolution_with_onnxruntime.html).

The convergence of the ML community to ONNX as its standard format has led to [research and academic papers](https://cloudblogs.microsoft.com/opensource/2020/12/17/accelerate-simplify-scikit-learn-model-inference-onnx-runtime/) produced by FANG researchers on how to convert models such as random forests into ONNX.

There are also desktop apps like Netron that let you inspect these ONNX models in graphical form such as the following:

https://netron.app/

Alongside this, there is also work directly supported by a range of large companies to enable ONNX to be run in WASM:

https://onnxruntime.ai/docs/build/web.html

And the official huggingface github is also working on a pure Rust implementation of ONNX with the code link below:

https://github.com/huggingface/candle/tree/main/candle-onnx

ONNX also supports GPUs as seen in the following link:

https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html

With all this support for the ecosystem, it makes sense for us to support ONNX and automate processes that convert models to ONNX such as Sklearn.

Now that we have covered the anatomy of a `.surml` file, we can now move to loading and saving files.

## Loading and saving with Python

This has been covered in the introduction section, so if you want to see the code around loading and saving, please visit the introduction section. We can save a model with the following code:

```python
file = SurMlFile(
	model=model, # the trained model object
	name="house-price-prediction",
	inputs=HOUSE_LINEAR["inputs"],
	engine=Engine.SKLEARN # change to the pytorch version is storing a pytorch model
)

file.add_version(version="0.0.1")
file.save(path="./linear.surml")
```

To load the file, we use the code below:

```python
new_file = SurMlFile.load(path="./linear.surml", engine=Engine.SKLEARN)
```

We can also load `.surml` files in Rust too.

## Saving and loading surml in rust

To load in Rust you will need the following dependency in your `cargo.toml` file (version may have increased since the time of writing this):

```toml
surrealml-core = "0.0.8"
```

Alternatively, you can add the dependency by typing  `cargo add surrealml-core` on the command line. The `core` is the exact same code that runs in the Python client, ensuring consistency between the client and the server in production.

Starting with the following `use` statements will let us bring a number of necessary types into scope:

```rust
use std::fs::File;
use std::io::{self, Read, Write};

use surrealml_core::storage::surml_file::SurMlFile;
use surrealml_core::storage::header::Header;
use surrealml_core::storage::header::normalisers::{
    wrapper::NormaliserType,
    linear_scaling::LinearScaling
};
```

We can then load the ONNX file that was saved from a training session with the code below:

```rust
let mut file = File::open("./stash/linear_test.onnx").expect("File to be found");
let mut model_bytes = Vec::new();
file.read_to_end(&mut model_bytes).expect(”File content to be read to string”);
```

Once we have loaded our bytes from the ONNX file, we will insert the `model_bytes` once we have defined our file. We can first define our header and add the columns with the following code:

```rust
let mut header = Header::fresh();
header.add_column(String::from("squarefoot"));
header.add_column(String::from("num_floors"));
header.add_output(String::from("house_price"), None);
```

We can then add the normalisers with the code below:

```rust
header.add_normaliser(
    "squarefoot".to_string(),
    NormaliserType::LinearScaling(LinearScaling { min: 0.0, max: 1.0 })
);
header.add_normaliser(
    "num_floors".to_string(),
    NormaliserType::LinearScaling(LinearScaling { min: 0.0, max: 1.0 })
);
```

We now have everything we need to package our `.surml` file which we can do and write to disk with the following code:

```rust
let surml_file = SurMlFile::new(header, model_bytes);
surml_file.write("./stash/test.surml").unwrap();
```

If we want to load a model, it can either be from bytes or a file using the code below:

```rust
let new_file = SurMlFile::from_file("./stash/test.surml").unwrap();
let file_from_bytes = SurMlFile::from_bytes(surml_file.to_bytes()).unwrap();
```

---

Source: https://surrealdb.com/docs/explore/studio

# Introduction

SurrealDB Studio is the official visual interface for SurrealDB.

SurrealDB Studio is the official visual interface for managing and querying SurrealDB. It connects to any SurrealDB instance, letting you run SurrealQL, explore records, and manage schema from the web or desktop.

[Visit studio.surrealdb.com](https://studio.surrealdb.com)

Full documentation for SurrealDB Studio is coming soon.

## In this section

- [SurrealQL editors](/docs/explore/studio/surrealql-editors.md) - write and run queries against a connection
- [Search and shortcuts](/docs/explore/studio/search-and-shortcuts.md) - move around Studio from the keyboard

---

Source: https://surrealdb.com/docs/explore/studio/search-and-shortcuts

# Search and shortcuts

The SurrealDB Studio command palette and its keyboard shortcuts.

SurrealDB Studio offers a range of shortcuts to help you navigate the interface more efficiently. You can find a list of all available shortcuts by pressing `Ctrl + K` or `Cmd + K` on your keyboard.

In the search modal, you can access all available shortcuts for open sessions, the views, all available tables in your connected database and the settings dialog.

<img src="~/assets/img/image/surrealist/search-and-hot-key.png" alt="Search and Shortcuts" />

<br/>

<br/>

<table>
    <thead>
        <tr>
            <th scope="col">Options</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Type">
                <code>Connections</code>
            </td>
            <td scope="row" data-label="Description">
                Open a list of all available connections and create a new connection type.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>Views</code>
            </td>
            <td scope="row" data-label="Description">
                Open all available views in the current session. This can be the Cloud view, GraphQL view, Query view, Designer view or the Authentication view.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>Tables</code>
            </td>
            <td scope="row" data-label="Description">
                Shows all available tables in the connected database. If you have no tables in the connection, load the Surreal Deal Store dataset, you can see the tables in the dataset.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>Query</code>
            </td>
            <td scope="row" data-label="Description">
                See all saved queries, view the query history and create a new query. Comment out a line in the query editor using `Cmd + /` or `Ctrl + /`.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>Explorer</code>
            </td>
            <td scope="row" data-label="Description">
                Import and export your database schema in a `.surql` file format.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>Authentication</code>
            </td>
            <td scope="row" data-label="Description">
                Create user permissions and roles for your connected database.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>API Docs</code>
            </td>
            <td scope="row" data-label="Description">
                Access the SurrealDB API documentation and access snippets for your queries in different SDK languages.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>Settings</code>
            </td>
            <td scope="row" data-label="Description">
                Access the Settings dialog to customise the appearance and behaviour of SurrealDB Studio.
                Increase the view size of your editor with `cmd + Option + +` or reduce with `cmd + Option + -`.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>Navigator</code>
            </td>
            <td scope="row" data-label="Description">
                Reload the navigator to see all available shortcuts and open the search modal.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>Developer</code>
            </td>
            <td scope="row" data-label="Description">
                Open a new connection and reset tour settings.
            </td>
        </tr>
    </tbody>
</table>

---

Source: https://surrealdb.com/docs/explore/studio/surrealql-editors

# SurrealQL editors

SurrealQL editor shortcuts in SurrealDB Studio. Indentation, comments, multi-cursor edits, running queries, record inspection, and JSON formatting.

Throughout SurrealDB Studio you will encounter various SurrealQL editors. These editors support intelligent SurrealQL highlighting and provide a range of features to help you write queries and edit records.

## Shortcuts

Editors support an array of useful shortcuts to help you navigate and edit more efficiently.

<table>
    <thead>
        <tr>
            <th scope="col">Shortcuts</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Shortcuts">
                <code>tab</code>
            </td>
            <td scope="row" data-label="Description">
                Indent the current line by 4 spaces.
            </td>
        </tr>
		<tr>
            <td scope="row" data-label="Shortcuts">
                <code>shift + tab</code>
            </td>
            <td scope="row" data-label="Description">
                Unindent the current line by 4 spaces.
            </td>
        </tr>
		<tr>
            <td scope="row" data-label="Shortcuts">
                <code>ctrl/cmd + f</code>
            </td>
            <td scope="row" data-label="Description">
                Open the search and replace panel
            </td>
        </tr>
		<tr>
            <td scope="row" data-label="Shortcuts">
                <code>ctrl/cmd + /</code>
            </td>
            <td scope="row" data-label="Description">
                Toggles comments on the selected or active line(s)
            </td>
        </tr>
		<tr>
            <td scope="row" data-label="Shortcuts">
                <code>ctrl/cmd + left click</code>
            </td>
            <td scope="row" data-label="Description">
                Place multiple cursors at the clicked locations
            </td>
        </tr>
		<tr>
            <td scope="row" data-label="Shortcuts">
                <code>ctrl/cmd + shift + L</code>
            </td>
            <td scope="row" data-label="Description">
                Selects all occurrences of the currently selected text
            </td>
        </tr>
		<tr>
            <td scope="row" data-label="Shortcuts">
                <code>ctrl/cmd + d</code>
            </td>
            <td scope="row" data-label="Description">
                Selects next occurrence of the currently selected text
            </td>
        </tr>
		<tr>
            <td scope="row" data-label="Shortcuts">
                <code>ctrl/cmd + enter</code>
            </td>
            <td scope="row" data-label="Description">
                Execute the query query (only applied to the query editor)
            </td>
        </tr>
	</tbody>
</table>

## Record inspection

Some editors support record inspection, which allows you to open a record in the Record inspector drawer by holding `ctrl/cmd` and left-clicking on a record id.

## JSON

Some editors, such as the record editor and query responses, support falling back to a JSON representation. This can be configured in the Settings dialog under `Appearance > Value formatting mode`.

---

Source: https://surrealdb.com/docs/explore/tutorials

# Demos and tutorials

Hands-on demo applications and tutorials. Learn SurrealDB by building real projects.

Learn SurrealDB by working through practical examples. Whether you prefer a guided walkthrough or exploring a ready-made application, the resources here will help you put concepts into practice.

- [Demos](/docs/explore/tutorials/demos/overview.md) - sample applications and datasets you can clone, import, or run locally to see SurrealDB in action.
- [Tutorials](/docs/explore/tutorials/tutorials/overview.md) - step-by-step guides covering topics such as authentication integrations, real-time applications, AI-powered features, and more.

---

Source: https://surrealdb.com/docs/explore/tutorials/demos/kaig-ai-demos

# Kai G AI demos

A collection of AI demo applications built with SurrealDB, showcasing RAG, agents, and knowledge graph patterns.

[Kaig](https://github.com/surrealdb/kaig) is an open-source collection of AI demo applications that use SurrealDB as their data layer. Each demo is a self-contained project you can run locally to explore a specific AI pattern.

## What you will find

The repository includes demos covering:

- **Retrieval-augmented generation (RAG)** - embedding documents into SurrealDB's vector indexes and using similarity search to ground LLM responses in real data.
- **AI agents** - autonomous agents that read from and write to SurrealDB as part of their tool-use loop.
- **Knowledge graphs** - building and querying graph structures that give AI models structured context.

## Getting started

Clone the repository and follow the README in the demo you want to explore:

```bash
git clone https://github.com/surrealdb/kaig.git
cd kaig
```

Each demo directory contains its own setup instructions, dependencies, and a walkthrough of the code.

---

Source: https://surrealdb.com/docs/explore/tutorials/demos/overview

# Demos

Sample applications and datasets you can clone, import, or run locally to explore SurrealDB without a long-form walkthrough.

This section collects **demos**: fairly complete examples you can **clone and run** (or import) to see SurrealDB in a realistic setting. Each item is a self-contained project or dataset with its own README or import steps rather than a page-by-page guide.

Use demos when you want to explore behaviour, skim the code, or validate a setup quickly. For guided steps and explanations, use the [tutorials](/docs/explore/tutorials/tutorials/overview.md) section instead.

Pick a demo from the sidebar to get started.

## Blink note-taking app

Blink is an open-source sample project that shows how SurrealDB can sit behind a Notion-like editing experience, with the database engine running in the browser via WebAssembly. Blink is a demo rather than a hosted product, and it shows how real-time structured data, permissions and UI state can fit together.

What to look for: how the app models pages and blocks, how collaboration and sync behave, and how SurrealDB's API surface maps to a rich client. For anyone evaluating SurrealDB for knowledge or agent tooling, Blink is a concrete reference implementation rather than a minimal CRUD example.

Clone and run it from the [Blink repository on GitHub](https://github.com/kearfy/blink). The README carries the build steps and browser requirements, which vary by WASM toolchain.

## Other demos

- [Kai G AI demos](/docs/explore/tutorials/demos/kaig-ai-demos.md) - community AI demos built on SurrealDB

## Full applications

- [Surreal Deal Store](/docs/explore/tutorials/demos/surreal-deal-store.md) - a complete storefront to clone and run

---

Source: https://surrealdb.com/docs/explore/tutorials/demos/surreal-deal-store

# Surreal Deal Store

Surreal Deal Store, a sample dataset of 12 tables with graph relations and record links, as .surql files to download and import into SurrealDB for trying SurrealQL queries on realistic data.

To further test out SurrealDB and SurrealQL functionality, we've included two demo datasets here in `.surql` files which you can download and [`import`](/docs/reference/cli/db/commands/import.md) into SurrealDB using the [CLI](/docs/reference/cli/db.md).

## Surreal Deal Store - there is a lot in store for you!

Surreal Deal Store is our demo dataset based on our [SurrealDB Store](https://surrealdb.store/). The dataset is made up of 12 tables using both [graph relations](/docs/reference/query-language/statements/relate.md) and [record links](/docs/reference/query-language/language-primitives/record-links.md).

In the diagram below, the nodes in pink are the [standard tables](/docs/reference/query-language/statements/define/table.md), the ones in purple represent the [edge tables](/docs/reference/query-language/statements/relate.md), which show the relationships between records when SurrealDB is used as a graph database. The nodes in grey are the [pre-computed table views](/docs/reference/query-language/statements/define/table.md).

<img src="~/assets/img/image/light/surreal-deal-store-light.png" darkSrc="~/assets/img/image/dark/surreal-deal-store.png" alt="Surreal Deal Data Model" />

### Download

**SurrealDB 3.x**

| Dataset                                                                          | URL                                                       |
| -------------------------------------------------------------------------------- | --------------------------------------------------------- |
| [Surreal Deal Store (mini)](https://datasets.surrealdb.com/datasets/surreal-deal-store/mini-v3.surql) | https://datasets.surrealdb.com/datasets/surreal-deal-store/mini-v3.surql |

**SurrealDB 2.x**

| Dataset                                                                          | URL                                                       |
| -------------------------------------------------------------------------------- | --------------------------------------------------------- |
| [Surreal Deal Store](https://datasets.surrealdb.com/surreal-deal-store.surql)             | https://datasets.surrealdb.com/surreal-deal-store.surql      |
| [Surreal Deal Store (mini)](https://datasets.surrealdb.com/surreal-deal-store-mini.surql) | https://datasets.surrealdb.com/surreal-deal-store-mini.surql |

### Import

Once one of the datasets has been downloaded, it's now time to [start the server](/docs/reference/cli/db/commands/start.md).

```bash
# Create a new in-memory server
surreal db start --user root --pass secret --allow-all
```

Lastly, use the [import command](/docs/reference/cli/db/commands/import.md) to add the dataset.

Use the command below to import the [surreal deal store dataset](https://datasets.surrealdb.com/surreal-deal-store.surql):

```bash
surreal db import --endpoint http://localhost:8000 --user root --pass secret \
  --ns main --db main surreal-deal-store.surql
```

To import the downloaded [Surreal Deal store (mini)](https://datasets.surrealdb.com/surreal-deal-store-mini.surql) dataset, use the command below:

```bash
surreal db import --endpoint http://localhost:8000 --user root --pass secret \
  --ns main --db main surreal-deal-store-mini.surql
```

Please be aware that the import process might take a few seconds.

### Using Curl

First, start the SurrealDB server:

```bash
# Create a new in-memory server
surreal db start --user root --pass secret --allow-all
```

Then download the file and load it into the database:

```bash
# Download the file
curl -L "https://datasets.surrealdb.com/surreal-deal-store.surql" -o surreal-deal-store.surql

# Load the file into the database using the rest endpoint
curl -v -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" --data-binary @surreal-deal-store.surql http://localhost:8000/import
```

If you want to use the mini version:

```bash
# Download the file
curl -L "https://datasets.surrealdb.com/surreal-deal-store-mini.surql" -o surreal-deal-store-mini.surql

# Load the file into the database using the rest endpoint
curl -v -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" --data-binary @surreal-deal-store-mini.surql http://localhost:8000/import
```

### Sample queries

Here are some sample queries you can run on the Surreal Deal Store dataset. We've also included a [SurrealDB Studio Mini](https://app.surrealdb.com/mini) below to help you run these queries.

> [!NOTE]
> The query results below have been limited to 4 records for brevity. If you remove the `LIMIT 4` clause from the queries, you'll see the full results.

```surql
-- Query 1: Using record links to select from the seller table
SELECT
  name,
  seller.name
FROM product LIMIT 4;
-- Query 2: Using graph relations to select from the person and product table
SELECT
    time.created_at as order_date,
    product_name,
    <-person.name as person_name,
    ->product.details
FROM order LIMIT 4;
-- Query 3: Conditional filtering based on an embedded object property.
SELECT
  name,
  email
FROM person
WHERE address.country ?= "England" LIMIT 4;
-- Query 4: Conditional filtering using relationships.
SELECT * FROM review
WHERE ->product.sub_category ?= "Activewear" LIMIT 4;
-- Query 5: Count orders based on order status
SELECT count() FROM order
WHERE order_status IN [ "processed", "shipped"]
GROUP ALL LIMIT 4;
-- Query 6: Get a deduplicated list of products that were ordered
SELECT
    array::distinct(product_name) as ordered_products
FROM order
GROUP ALL LIMIT 4;
-- Query 7: Get the average price per product category
SELECT
    ->product.category AS product_category,
    math::mean(price) AS avg_price
FROM order
GROUP BY product_category
ORDER BY avg_price DESC LIMIT 4;
-- Query 8: encapsulating logic in a function
RETURN fn::number_of_unfulfilled_orders();
-- Query 9: using a custom fuction for currency conversion
SELECT
    product_name,
    fn::pound_to_usd(price) AS price_usd
FROM order LIMIT 4;
```

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/auth0-integration

# Integrate Auth0 with SurrealDB

Use Auth0 as the authentication provider for a single-page application backed only by SurrealDB.

This guide will cover using [Auth0](https://auth0.com/) as the authentication provider for single-page web applications using SurrealDB as the only backend.

This guide uses [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md), the method for every current version of SurrealDB. The tabs also carry the older [Scope](/docs/reference/query-language/statements/define/scope.md) and [Token](/docs/reference/query-language/statements/define/token.md) statements for readers still on `v1.x` - both were removed in later versions.

In this guide you will learn how to:

- Configure Auth0 to issue tokens that can be used with SurrealDB.
- Configure SurrealDB to accept tokens issued by Auth0.
- Define user-level authorization using SurrealDB [record users](/docs/learn/security/authentication/users.md#record-users).
- Authenticate users with Auth0 in a single-page application.
- Retrieve and update information from SurrealDB using the authenticated user.

This guide will cover the most general case, in which SurrealDB is the only backend for your application. You can still follow this guide even if you have additional backends, but in that case you may have other options available to request and validate tokens issued by Auth0. Likewise, even if your application is not strictly a Single-Page Application (SPA), you may still follow and benefit from this guide.

## Prerequisites

This guide assumes the following:

- You have a [fresh instance of SurrealDB running.](/docs/running/overview.md)

- You can [use a local Docker container](/docs/running/docker.md) without volumes for the purposes of this guide.

```bash
docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest \
  start --user root --pass secret
```

To run the SurrealQL statements mentioned in this guide, you will also need an interactive shell.

```bash
surreal db sql -u root -p secret --pretty
```

You will also need to [create an Auth0 account](https://auth0.com/signup), which can be on the free plan.

## Configuring Auth0

### Creating a simple SPA, an Auth0 application and an Auth0 API

First of all, you will need to complete the regular setup for creating a Single-Page Application and an API resource within Auth0. You can do this by following the official Auth0 [documentation](https://auth0.com/docs/quickstart/spa) for your SPA.

If you are using plain JavaScript, follow [the vanilla guide](https://auth0.com/docs/quickstart/spa/vanillajs/01-login) to create an application in Auth0 and [the API guide](https://auth0.com/docs/quickstart/spa/vanillajs/02-calling-an-api) to create an API in Auth0.

> [!NOTE]
> You will not need to create an actual backend API (e.g. using backend languages like NodeJS or Go) as the documentation suggests when using SurrealDB. A simple file server (e.g. <code>python3 -m http.server 8080</code>, for local testing or any static web server for production) that can serve the static content of your website will suffice. However, creating an API resource in Auth0 is necessary, as it will generate an “audience” string which will be required for Auth0 to add claims to its access tokens.

At the end of those tutorials, you should have both an application and an API created in your Auth0 account and a simple client-side web application that authenticates with Auth0 using those two resources. This website will capture the access token issued by Auth0, which is the token that we are using with SurrealDB. If you were not able to create a working website, you can also use this [minimal example created by SurrealDB](https://github.com/surrealdb/examples/tree/main/auth0).

When completing the actions above, make sure to keep the following information handy:

- Auth0 Client ID, generated when creating the application.
- Auth0 Domain, generated when creating the application.
- Auth0 Audience, generated when creating the API.

### Creating a custom Auth0 action to add claims for SurrealDB

Now, Auth0 is ready to perform authentication and issue tokens for your application. However, SurrealDB expects these tokens to contain some specific claims. Auth0 allows adding custom claims through its “actions” and “flows” features. Since Auth0 [requires claims for an API audience to be namespaced](https://auth0.com/docs/troubleshoot/product-lifecycle/past-migrations/custom-claims-migration#restricted-token-audience), these claims will need to have the `https://surrealdb.com/` prefix.

To add custom claims, you must [create an Auth0 action](https://auth0.com/docs/customize/actions/write-your-first-action#create-an-action) with the “Login / Post Login” trigger.

For this example, you can use the following code:

**Using DEFINE ACCESS**

```js
exports.onExecutePostLogin = async (event, api) => {
  if (event.authorization) {
    // The claims in this block are expected by SurrealDB.
    // These values should match your SurrealDB installation.
        api.accessToken.setCustomClaim(`https://surrealdb.com/ns`,
        "main");
        api.accessToken.setCustomClaim(`https://surrealdb.com/db`,
        "main");
    // This value corresponds to the name of the JWT access method
    // which will be created in SurrealDB during the next section.
        api.accessToken.setCustomClaim(`https://surrealdb.com/ac`,
        "auth0");

    // In this block, we will add additional claims which are not required by SurrealDB.
    // These claims can be used from SurrealQL to implement application logic.
        // In this example,
        we will add the data that we will store for each user.
    // We will also use some of this data to perform authorization.
        api.accessToken.setCustomClaim(`https://surrealdb.com/email`,
        event.user.email);
    api.accessToken.setCustomClaim(`https://surrealdb.com/email_verified`, event.user.email_verified);
        api.accessToken.setCustomClaim(`https://surrealdb.com/name`,
        event.user.name);
    api.accessToken.setCustomClaim(`https://surrealdb.com/nickname`, event.user.nickname);
    api.accessToken.setCustomClaim(`https://surrealdb.com/picture`, event.user.picture);
  }
};
```

**Using Scope and Token (1.x only)**

```js
exports.onExecutePostLogin = async (event, api) => {
  if (event.authorization) {
    // The claims in this block are expected by SurrealDB.
    // These values should match your SurrealDB installation.
        api.accessToken.setCustomClaim(`https://surrealdb.com/ns`,
        "main");
        api.accessToken.setCustomClaim(`https://surrealdb.com/db`,
        "main");
    // These values correspond to the names of the SCOPE and TOKEN resources
    // which will be created in SurrealDB during the next section.
        api.accessToken.setCustomClaim(`https://surrealdb.com/sc`,
        "user");
        api.accessToken.setCustomClaim(`https://surrealdb.com/tk`,
        "auth0");

    // In this block, we will add additional claims which are not required by SurrealDB.
    // These claims can be used from SurrealQL to implement application logic.
        // In this example,
        we will add the data that we will store for each user.
    // We will also use some of this data to perform authorization.
        api.accessToken.setCustomClaim(`https://surrealdb.com/email`,
        event.user.email);
    api.accessToken.setCustomClaim(`https://surrealdb.com/email_verified`, event.user.email_verified);
        api.accessToken.setCustomClaim(`https://surrealdb.com/name`,
        event.user.name);
    api.accessToken.setCustomClaim(`https://surrealdb.com/nickname`, event.user.nickname);
    api.accessToken.setCustomClaim(`https://surrealdb.com/picture`, event.user.picture);
  }
};
```

This action should be saved and added to the “Login” flow in the “Actions > Flows” section.

## Configuring SurrealDB

**Using DEFINE ACCESS**

### Defining permissions and fields in SurrealDB

For this simple example, we will create a single table named “user”, where any user that authenticates through Auth0 using your application will be granted complete permissions over their data. For this to work as intended, we will need to ensure that the email address is unique between users and that users are granted permissions to access their own record as long as they authenticated with the access method that we will define.

```surql
DEFINE TABLE user SCHEMAFULL
  -- Authorised users can select, update, delete and create user records.
  -- Records that do not match the permissions will not be modified nor returned.
  PERMISSIONS FOR select, update, delete, create
  WHERE
    -- The access method must match the method that we will define.
    $access = "auth0"
    -- The record identifier must match that of the authenticated user.
    AND id = $auth
;

-- In this example, we will use the email as the primary identifier for a user.
DEFINE INDEX email ON user FIELDS email UNIQUE;
DEFINE FIELD email
  ON user TYPE string ASSERT string::is_email($value);
-- We define some other information present in the token that we want to store.
DEFINE FIELD name ON user TYPE string;
DEFINE FIELD nickname ON user TYPE string;
DEFINE FIELD picture ON user TYPE string;
```

### Defining a token verification method in SurrealDB

Next, we should configure SurrealDB so that it can verify tokens sent to it through the [HTTP REST API](/docs/reference/rest-api/http-protocol.md) via the “Authorization” header or through any of the [SDKs](/docs/languages.md) via the “Authenticate” methods.

To do that, we will use the JWKS support in SurrealDB in order to define a token verification mechanism pointing to a JWKS object served by Auth0. This JWKS object can be found in a [dedicated endpoint for your Auth0 domain](https://auth0.com/docs/secure/tokens/json-web-tokens/locate-json-web-key-sets). Pointing to a JWKS file ensures that token verification will work even after [rotating the signing keys](https://auth0.com/docs/get-started/tenant-settings/signing-keys/rotate-signing-keys) and that tokens signed with revoked keys will no longer be accepted by SurrealDB. To understand how revocation is handled by SurrealDB, read the [JSON Web Key Set documentation](/docs/reference/query-language/statements/define/access/jwt.md#json-web-key-set-jwks) under `DEFINE ACCESS ... TYPE JWT`.

We will also use the [`AUTHENTICATE`](/docs/reference/query-language/statements/define/access/record.md#with-authenticate-clause) clause in order to check that any necessary token claims have the expected values before returning the user matching the email address provided by Auth0. This is required because Auth0 has no knowledge of the record identifiers that are used in SurrealDB, so we need to use an identifier that can actually be provided by Auth0 in order to retrieve the corresponding record user.

The following queries will create the required resources to authenticate a token for a record user:

```surql
-- Specify the namespace and database that will be used.
-- These values should match the custom claims that we configured before.
USE NS main DB main;

-- Define the public key to verify tokens issued by Auth0 for our application.
-- The name of the token should match the custom claim that we configured before.
DEFINE ACCESS auth0 ON DATABASE TYPE RECORD
    -- We verify the token using the public keys hosted by Auth0.
    WITH JWT URL "https://<YOUR_AUTH0_DOMAIN>/.well-known/jwks.json"
    -- We check the token claims and map the email address to a record user.
    AUTHENTICATE {
        IF (
            -- The JWT specification allows the audience claim to be an array or a string.
            -- In this example, we ensure that it is provided as an array by Auth0.
            $token.aud.is_array()
            -- The audience claim must contain the audience of you application.
            -- This is the value that you defined when creating the API in Auth0.
            AND $token.aud CONTAINS "<YOUR_AUTH0_AUDIENCE_VALUE>"
            -- The audience claim must contain your Auth0 user information endpoint.
            -- It contains the domain generated when when creating the application in Auth0.
            AND $token.aud CONTAINS
              "https://<YOUR_AUTH0_DOMAIN>/userinfo"
            -- The email address in the token must be verified as belonging to the user.
            AND $token['https://surrealdb.com/email_verified'] = true
        ) {
            -- We return the only user that matches the email address claim found in the token.
            RETURN SELECT * FROM user
              WHERE email = $token['https://surrealdb.com/email']
        }
    }
;
```

In the example above, replace the placeholder with the domain value defined for your Auth0 application.

Note that [validating the audience of the token is a requirement of Auth0](https://auth0.com/docs/secure/tokens/access-tokens/validate-access-tokens), other providers may require validating additional claims (e.g. `iss`, `sub`) to ensure that the token is being used as intended. With Auth0, you can also make use of [OpenID Connect scopes](https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes), which can be accessed through the `scopes` claim via `$token.scopes` and contain the OIDC scopes requested by the application and granted by the user, which we will not do for this example.

> [!IMPORTANT]
> In order to allow SurrealDB to establish a connection with Auth0 to download the JWKS object, you will require running it with the network <a href="/docs/learn/security/authorization/capabilities.md">capability</a>. For the strongest security, provide your specific Auth0 domain when starting SurrealDB with <code>--allow-net</code>. For example: <code>--allow-net example.eu.auth0.com</code>.

**Using Scope and Token (1.x only)**

### Defining a token verification method in SurrealDB

Next, we should configure SurrealDB so that it can verify tokens sent to it through the [HTTP REST API](/docs/reference/rest-api/http-protocol.md) via the “Authorization” header or through any of the [SDKs](/docs/languages.md) via the “Authenticate” methods.

To do that, we will use the JWKS support in SurrealDB in order to define a token verification mechanism pointing to a JWKS object served by Auth0. This JWKS object can be found in a [dedicated endpoint for your Auth0 domain](https://auth0.com/docs/secure/tokens/json-web-tokens/locate-json-web-key-sets).

Pointing to a JWKS file ensures that token verification will work even after [rotating the signing keys](https://auth0.com/docs/get-started/tenant-settings/signing-keys/rotate-signing-keys) and that tokens signed with revoked keys will no longer be accepted by SurrealDB.

To understand how revocation is handled by SurrealDB, read the [JSON Web Key Set documentation](/docs/reference/query-language/statements/define/token.md#json-web-key-set-jwks) under `DEFINE TOKEN`.

The following queries will create the required resources to authenticate a token for a scope:

```surql
-- Specify the namespace and database that will be used.
-- These values should match the custom claims that we configured before.
USE NS main DB main;

-- Define the scope where the token will be used.
-- The name of the scope should match the custom claim that we configured before.
DEFINE SCOPE user;

-- Define the public key to verify tokens issued by Auth0 for our application.
-- The name of the token should match the custom claim that we configured before.
DEFINE TOKEN auth0 ON SCOPE user
  TYPE JWKS VALUE "https://<YOUR_AUTH0_DOMAIN>/.well-known/jwks.json";
```

In the example above, replace the placeholder with the domain value defined for your Auth0 application.

> [!IMPORTANT]
> In order to allow SurrealDB to establish a connection with Auth0 to download the JWKS object, you will require running it with the network <a href="/docs/learn/security/authorization/capabilities.md">capability</a>. For the strongest security, provide your specific Auth0 domain when starting SurrealDB with <code>--allow-net</code>. For example: <code>--allow-net example.eu.auth0.com</code>.

### Defining authorization criteria in SurrealDB
For this simple example, we will create a single table named “user”, where any user that authenticates through Auth0 using your application with a verified email address will be able to register, view and update their data. For this to work as intended, we will need to verify some information in the token claims.

```surql
DEFINE TABLE user SCHEMAFULL
  -- Authorised users can select, update, delete and create user records.
  -- Records that do not match the permissions will not be modified nor returned.
  PERMISSIONS FOR select, update, delete, create
  WHERE
    -- The token scope must match the scope that we defined.
    -- The name of the scope should match the scope that we defined before.
    $scope = "user"
    -- The audience claim must contain the audience of you application.
    -- This is the value that you defined when creating the API in Auth0.
    AND $token.aud CONTAINS "<YOUR_AUTH0_AUDIENCE_VALUE>"
    -- The audience claim must contain your Auth0 user information endpoint.
    -- It contains the domain generated when when creating the application in Auth0.
    AND $token.aud CONTAINS "https://<YOUR_AUTH0_DOMAIN>/userinfo"
    -- The email claim must match the email of the user being queried.
    AND email = $token['https://surrealdb.com/email']
    -- The email must be verified as belonging to the user.
    AND $token['https://surrealdb.com/email_verified'] = true
;

-- In this example, we will use the email as the primary identifier for a user.
DEFINE INDEX email ON user FIELDS email UNIQUE;
DEFINE FIELD email
  ON user TYPE string ASSERT string::is_email($value);
-- We define some other information present in the token that we want to store.
DEFINE FIELD name ON user TYPE string;
DEFINE FIELD nickname ON user TYPE string;
DEFINE FIELD picture ON user TYPE string;
```
It is important to know that [validating the audience of the token is a requirement of Auth0](https://auth0.com/docs/secure/tokens/access-tokens/validate-access-tokens), other providers may require validating additional claims (e.g. iss, sub) to ensure that the token is being used as intended. With Auth0, you can also make use of [OpenID Connect scopes](https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes), which can be accessed through the `scopes` claim via `$token.scopes` and contain the OIDC scopes requested by the application and granted by the user, which we will not do for this example.

Note that the `$auth` variable accessible from SurrealQL will not contain any values in this case, as it requires the `id` claim to be added to the JWT, containing the value of the identifier of a SurrealDB record. For the current example, the `$auth` variable will not be necessary.

## Configuring the application

Now that we have everything ready for Auth0 to generate tokens and for SurrealDB to receive and verify them, we will modify our simple web application to use the token issued by Auth0 to register a new user or to update its information if the user already exists.

Because of the diversity of SDKs that both SurrealDB and Auth0 support, it is difficult to provide examples that will work for everyone. In this guide, we will provide code examples for the most general use case of using the [SurrealDB HTTP REST API](/docs/reference/rest-api/http-protocol.md) and [Auth0 SPA JS](https://github.com/auth0/auth0-spa-js).

This code will run entirely on the client and can be added to the static website that you created while following the Auth0 quick start documentation linked at the beginning of this guide.

With the aforementioned interfaces, we can use the following functions to authenticate users:

```js
// Returns users in that the token is authorised to select.
// Should return only the user matching the email in the token.
const getUser = async () => {
	// We fetch an access token from Auth0 with the ID token.
	const auth0Token = await auth0Client.getTokenSilently();

		const response = await fetch(surrealDbConfig.endpoint + "/key/user",
	    {
		method: "GET",
		headers: {
			"Accept": "application/json",
			"Authorization": "Bearer " + auth0Token
		}
	});

	return response.json();
};

// Creates a user matching the information in the token.
// If the user already exists,
    updates the existing user with the new data.
const createUpdateUser = async () => {
	// We collect the user data from the Auth0 ID token.
	const auth0User = await auth0Client.getUser();
	// We fetch an access token from Auth0 with the ID token.
	const auth0Token = await auth0Client.getTokenSilently();

	// We define the general query to create or update a user.
	// We leave the method to be defined later.
	let query = {
		body: JSON.stringify({
			email: auth0User.email,
			name: auth0User.name,
			nickname: auth0User.nickname,
			picture: auth0User.picture
		}),
		headers: {
			"Accept": "application/json",
			"Authorization": "Bearer " + auth0Token
		}
	};
	// We get the user that the token is authorised to access.
	const surrealDbUser = await getUser();
	if (surrealDbUser[0].result.length == 0) {
		// If a user for the token does not exist, we create the record.
		console.log("Token user does not exist in database. Creating record.");
		query.method = "POST";
	} else {
		// If a user for the token already exists, we update the record.
		console.log("Token user already exists in database. Updating record.");
		query.method = "PUT";
	}

	// We perform the query and return the created/updated record.
		let response = await fetch(surrealDbConfig.endpoint + "/key/user",
	    query);
	return response.json();
};
```

If you want to see how this code would fit inside the web application that you built, you can view this [minimal example created by SurrealDB](https://github.com/surrealdb/examples/tree/main/auth0).

To learn about other potential uses for the SurrealDB token functionality, you can read the [`DEFINE ACCESS ... TYPE JWT` documentation page](/docs/reference/query-language/statements/define/access/jwt.md).

## Annex

In this section, we will provide a few examples of how to configure the application to work with Auth0.

## Example single page application

You can view and download a minimal example of an SPA using Auth0 and SurrealDB [in this repository](https://github.com/surrealdb/examples/tree/main/auth0).

## Alternative: using HMAC

Using public key cryptography algorithms for signing tokens prevents you from having to store any secrets at all in SurrealDB. However, some SurrealDB administrators may prefer to use HMAC algorithms, which use the same secret to both sign and verify the signature of the JWT. This secret will be stored in SurrealDB when provided as value for the `DEFINE ACCESS` statement and its access should be restricted, as it can be used to issue arbitrary tokens which will be trusted by SurrealDB. However, **we do not recommend this method** over using public cryptography, as the latter significantly reduces the burden of creating strong secrets, keeping them secret and managing their lifecycle.

According to the OAuth 2.0 specification (part of OpenID Connect), only confidential (as opposed to public) applications should be allowed to use HMAC algorithms, as it requires being able to keep the secret secure. For this reason, Auth0 will not allow using HMAC algorithms with applications of the SPA (Single-Page Application) type, as they generally would have to store the secret in the client, which is publicly accessible. However, because of the particular case of SurrealDB, the secret will not need to be exposed to the client and will instead be stored in SurrealDB.

Auth0 can support scenarios like these through the option to [disable OIDC-conformant authentication](https://auth0.com/docs/authenticate/login/oidc-conformant-authentication). Once disabled, Auth0 will allow selecting HMAC algorithms for SPA. Keep in mind that other options may become available in Auth0 after this change which, if modified without proper care, may compromise the security of your application.

If the choice of using HMAC is made, the rest of the guide can be followed as is, with the exception of specifying the proper HMAC algorithm and placing the secret as the value when defining a token. In Auth0, the secret used to sign using HMAC algorithm corresponds to the [Auth0 Client Secret](https://auth0.com/docs/secure/application-credentials#client-secret-authentication) string. The example below shows how a token using the HS256 algorithm can be defined:

**Using DEFINE ACCESS**

  ```surql
-- Define the secret to verify tokens issued by Auth0 for our application.
-- The name of the access method should match the custom claim that we configured before.
DEFINE ACCESS auth0 ON DATABASE TYPE RECORD
  WITH JWT ALGORITHM HS256 KEY "<YOUR_AUTH0_CLIENT_SECRET_VALUE>"
;
```

**Using Scope and Token (1.x only)**

  ```surql
-- Define the secret to verify tokens issued by Auth0 for our \
  application.
-- The name of the token should match the custom claim that we \
  configured before.
DEFINE TOKEN auth0 ON SCOPE user TYPE HS256 VALUE \
  "<YOUR_AUTH0_CLIENT_SECRET_VALUE>";
```

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/aws-cognito-integration

# Integrate AWS Cognito with SurrealDB

Use AWS Cognito as the authentication provider for a client-side application backed only by SurrealDB.

This guide will cover using [AWS Cognito](https://aws.amazon.com/cognito/) as the authentication provider for client-side web applications using SurrealDB as the only backend.

This guide uses [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md), the method for every current version of SurrealDB. The tabs also carry the older [Scope](/docs/reference/query-language/statements/define/scope.md) and [Token](/docs/reference/query-language/statements/define/token.md) statements for readers still on `v1.x` - both were removed in later versions.

In this guide you will learn how to:

- Configure AWS Cognito to issue tokens that can be used with SurrealDB.
- Configure SurrealDB to accept tokens issued by AWS Cognito.
- Define user-level authorization using SurrealDB [record users](/docs/learn/security/authentication/users.md#record-users).
- Authenticate users with AWS Cognito in a client-side web application.
- Retrieve and update information from SurrealDB using the authenticated user.

This guide will cover the most general case, in which SurrealDB is the only backend for your application. You can still follow this guide even if you have additional backends, but in that case you may have other options available to request and validate tokens issued by AWS Cognito.

## Prerequisites

This guide assumes the following:

- You have a [fresh instance of SurrealDB running](/docs/running/overview.md).

- You can [use a local Docker container](/docs/running/docker.md) without volumes for the purposes of this guide.

```bash
docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest \
  start --user root --pass secret
```

To run the SurrealQL statements mentioned in this guide, you will also need an interactive shell.

```bash
surreal db sql -u root -p secret --pretty
```

You will also need to [have an Amazon Web Services account](https://aws.amazon.com/resources/create-account/). By following this guide you will be subject to **at least** the [AWS Cognito](https://aws.amazon.com/cognito/pricing/) and [AWS Lambda](https://aws.amazon.com/lambda/pricing/) pricing plans. Although the resources used in this guide should be well within the free tier for both, we cannot guarantee that this will be the case in your particular situation.

## Configuring AWS Cognito

In this section, we will create a user pool and a client that will be used to authenticate users with AWS Cognito.

### Creating a user pool and a client

Cognito offers [user pools](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html) as a user directory for mobile and web applications. This directory can hold users defined directly within Cognito, but can also integrate with third-party identity providers like Google and Facebook. Additionally, it provides the ability to handle user registrations directly in the [Cognito Hosted UI](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-integration.html), which can also take care of requiring specific information, verifying user email addresses and phone numbers or even enforcing multi-factor authentication. Within a user pool, [creating a client](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html#cognito-user-pools-app-idp-settings-console-create) establishes a method (in our case a client-side web application) to authenticate with the user pool.

You will need to [create a user pool](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-as-user-directory.html) that meets your requirements. In order to be able to follow along with this guide, the following configuration options are strongly recommended:

- In the "Configure sign-in experience" step:
  - Select at least "Email" in "Cognito user pool sign-in options".
- In the "Configure sign-up experience" step:
  - Select at least "email" in "Required attributes".
  - Select "Allow Cognito to automatically send messages to verify and confirm".
- In the "Configure message delivery" step:
  - Select "Send email with Cognito" in "Email".
- In the "Integrate your app" step:
  - Select "Use the Cognito Hosted UI" in "Hosted authentication pages".
  - Select "Use a Cognito domain" in "Domain" and set a name for it.
  - Select "Public client" in "Initial app client".
  - Select "Don't generate a client secret" in "Initial app client".
  - Under "Allowed callback URLs" in "Initial app client", set the URL of your web application.
  - Under "Advanced app client settings" in "Initial app client", ensure "Authorization code grant" is selected.
  - Under "Attribute read and write permissions" in "Initial app client", ensure "email" is selected for "Read".
  - Under "Attribute read and write permissions" in "Initial app client", ensure "email_verified" is selected for "Read".

> [!IMPORTANT]
> These suggested configuration options may not provide the strongest security or match your specific requirements, but will ensure that you are able to follow with this guide to understand the simplest example of an integration between SurrealDB and AWS Cognito, which you should later update to meet your requirements. Changes in these options may require changes in the other steps outlined in this guide. Other configuration options can be left with their default values or may be changed to fit your requirements.

Once your user pool has been created, open it in the AWS Console and take note of the following values:

- "User pool ID" from the "User pool overview" section in the main user pool view.
- "Cognito domain" from the "Configuration for all app clients" section under the "App integration" tab.
- "Client ID" from the only client in the "App client list" under the "App integration" tab.

### Creating a pre-token generation lambda

Cognito is now ready to perform authentication and issue tokens for your application. However, SurrealDB expects these tokens to contain some specific claims. Cognito allows modifying token claims through [pre-token generation lambda](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html#cognito-user-pools-lambda-trigger-syntax-pre-token-generation) triggers. Specifically, we will be [adding custom claims](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html#aws-lambda-triggers-pre-token-generation-example-2) to the token.

To create the trigger, just [create an AWS Lambda function](https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html#getting-started-create-function) with the following code:

**Using DEFINE ACCESS**

  ```js
const handler = async (event) => {
  event.response = {
    claimsOverrideDetails: {
      claimsToAddOrOverride: {
        ac: "cognito", // The access method that has been defined using DEFINE ACCESS.
        ns: "main", // The namespace selected when calling DEFINE ACCESS.
        db: "main", // The database selected when calling DEFINE ACCESS.
      },
    },
  };

  return event;
};

export { handler };
```

**Using Scope and Token (1.x only)**

  ```js
  const handler = async (event) => {
  event.response = {
    claimsOverrideDetails: {
      claimsToAddOrOverride: {
        tk: "cognito", // The name of the token given when defining it with DEFINE TOKEN.
        sc: "user", // The scope that the token has been defined for \
          with DEFINE TOKEN.
        ns: "main", // The namespace selected when calling DEFINE \
          TOKEN.
        db: "main", // The database selected when calling DEFINE \
          TOKEN.
      },
    },
  };

  return event;
};

export { handler };
```

Note that, in order to use the suggested code, the function must be configured as "Node.js 20.x" or equivalent.

After the Lambda trigger has been created, we will need to [associate it with our client integration](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html). Visit your user pool in Cognito and, under the "User pool properties" tab, in the "Lambda triggers" section, click on "Add Lambda trigger". For this guide, the trigger type will be "Authentication", specifically a "Pre token generation trigger". Select the function that you created.

### Creating a test user

Depending on how you configured your user pool, users will be able to register by different means. To ensure this guide can be followed easily, we will create a test user to authenticate in our web application. Open your user pool and click on the "Create user" button under the "Users" section in the main user pool view. Provide an email address for that user and check "Mark email address as verified" to avoid having to do so manually. Set a password that matches the requirements that you configured.

## Configuring SurrealDB

**Using DEFINE ACCESS**

### Defining permissions and fields in SurrealDB

For this simple example, we will create a single table named “user”, where any user that authenticates through AWS Cognito using your application will be granted complete permissions over their data. For this to work as intended, we will need to ensure that the email address is unique between users and that users are granted permissions to access their own record as long as they authenticated with the access method that we will define.

```surql
DEFINE TABLE user SCHEMAFULL
  -- Authorised users can select, update, delete and create user records.
  -- Records that do not match the permissions will not be modified nor returned.
  PERMISSIONS FOR select, update, delete, create
  WHERE
    -- The access method must match the method that we will define.
    $access = "cognito"
    -- The record identifier must match that of the authenticated user.
    AND id = $auth
;

-- In this example, we will use the email as the primary identifier for a user.
DEFINE INDEX email ON user FIELDS email UNIQUE;
DEFINE FIELD email
  ON user TYPE string ASSERT string::is_email($value);
-- We define some other information present in the token that we want to store.
DEFINE FIELD cognito_username ON user TYPE string;
```

### Defining a token verification method in SurrealDB

Next, we should configure SurrealDB so that it can verify tokens sent to it through the [HTTP REST API](/docs/reference/rest-api/http-protocol.md) via the “Authorization” header or through any of the [SDKs](/docs/languages.md) via the “Authenticate” methods.

To do that, we will use the [JWKS support in SurrealDB](/docs/reference/query-language/statements/define/token.md#json-web-key-set-jwks) in order to define a token verification mechanism pointing to a JWKS object served by AWS for your Cognito user pool. This JWKS object can be found in an endpoint [built from your AWS region and user pool](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html) in the format `https://cognito-idp.<Region>.amazonaws.com/<userPoolId>/.well-known/jwks.json`. Pointing to a remote JWKS object ensures that token verification will work even in the case that AWS rotates their encryption keys and that tokens signed with revoked keys will no longer be accepted by SurrealDB. To understand how revocation is handled by SurrealDB, read the [JSON Web Key Set documentation](/docs/reference/query-language/statements/define/token.md#json-web-key-set-jwks) under `DEFINE ACCESS ... TYPE JWT`.

We will also use the [`AUTHENTICATE`](/docs/reference/query-language/statements/define/access/record.md#with-authenticate-clause) clause in order to check that any necessary token claims have the expected values before returning the user matching the email address provided by AWS Cognito. This is required because AWS Cognito has no knowledge of the record identifiers that are used in SurrealDB, so we need to use an identifier that can actually be provided by AWS Cognito in order to retrieve the corresponding record user.

The following queries will create the required resources to authenticate a token for a record using JWKS:

```surql
-- Specify the namespace and database that will be used.
-- These values should match the custom claims that we configured before.
USE NS main DB main;

-- Define the public key to verify tokens issued by your AWS Cognito user pool.
-- The name of the access method should match the custom claim that we configured before.
DEFINE ACCESS cognito ON DATABASE TYPE RECORD
-- We verify the token using the public keys hosted by AWS.
    WITH JWT URL
      "https://cognito-idp.<YOUR_AWS_REGION>.amazonaws.com/<YOUR_COGNITO_USER_POOL_ID>/.well-known/jwks.json"
    -- We check the token claims and map the email address to a record user.
    AUTHENTICATE {
        IF (
            -- The issuer claim must match the URL of your AWS Cognito user pool.
            $token.iss =
              "https://cognito-idp.<YOUR_AWS_REGION>.amazonaws.com/<YOUR_COGNITO_USER_POOL_ID>"
            -- The audience claim must match you AWS Cognito Client ID.
            AND $token.aud = "<YOUR_COGNITO_CLIENT_ID>"
            -- The email address in the token must be verified as belonging to the user.
            AND $token.email_verified = true
        ) {
            -- We return the only user that matches the email address claim found in the token.
            RETURN SELECT * FROM user WHERE email = $token.email
        }
  }
;
```

In the example above, replace the placeholders with values applicable to your Cognito user pool.

It is important to know that [validating the issuer and audience of the token is a requirement of AWS Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html), other providers may require validating additional claims to ensure that the token is being used as intended.

In order to allow SurrealDB to establish a connection with AWS Cognito to download the JWKS object, you will require running it with the network <a href="/docs/learn/security/authorization/capabilities.md">capability</a>.

For the strongest security, provide your specific Cognito user pool domain when starting SurrealDB with `--allow-net`. For example: `--allow-net cognito-idp.eu-west-1.amazonaws.com`.

**Using Scope and Token (1.x only)**

### Defining a token verification method in SurrealDB

Next, we should configure SurrealDB so that it can verify tokens sent to it through the [HTTP REST API](/docs/reference/rest-api/http-protocol.md) via the “Authorization” header or through any of the [SDKs](/docs/languages.md) via the “Authenticate” methods.

To do that, we will use the [JWKS support in SurrealDB](/docs/reference/query-language/statements/define/token.md#json-web-key-set-jwks) in order to define a token verification mechanism pointing to a JWKS object served by AWS for your Cognito user pool. This JWKS object can be found in an endpoint [built from your AWS region and user pool](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html) in the format `https://cognito-idp.<Region>.amazonaws.com/<userPoolId>/.well-known/jwks.json`.

Pointing to a remote JWKS object ensures that token verification will work even in the case that AWS rotates their encryption keys and that tokens signed with revoked keys will no longer be accepted by SurrealDB.

To understand how revocation is handled by SurrealDB, read the [JSON Web Key Set documentation](/docs/reference/query-language/statements/define/token.md#json-web-key-set-jwks) under `DEFINE TOKEN`.

The following queries will create the required resources to authenticate a token for a scope using JWKS:

```surql
-- Specify the namespace and database that will be used.
-- These values should match the custom claims that we configured before.
USE NS main DB main;

-- Define the scope where the token will be used.
-- The name of the scope should match the custom claim that we configured before.
DEFINE SCOPE user;

-- Define the public key to verify tokens issued by your AWS Cognito user pool.
-- The name of the token should match the custom claim that we configured before.
DEFINE TOKEN cognito ON SCOPE user TYPE JWKS
  VALUE
    "https://cognito-idp.<YOUR_AWS_REGION>.amazonaws.com/<YOUR_COGNITO_USER_POOL_ID>/.well-known/jwks.json";
;
```

In the example above, replace the placeholders with values applicable to your Cognito user pool.

In order to allow SurrealDB to establish a connection with AWS Cognito to download the JWKS object, you will require running it with the network <a href="/docs/learn/security/authorization/capabilities.md">capability</a>.

For the strongest security, provide your specific Cognito user pool domain when starting SurrealDB with `--allow-net`. For example: `--allow-net cognito-idp.eu-west-1.amazonaws.com`.

### Defining authorization criteria in SurrealDB

For this example, we will create a single table named `user`, where any user that authenticates through AWS Cognito using your application with a verified email address will be able to register, view and update their data. For this to work as intended, we will need to verify some information in the token claims.

```surql
DEFINE TABLE user SCHEMAFULL
  -- Authorised users can select, update, delete and create user records.
  -- Records that do not match the permissions will not be modified nor returned.
  PERMISSIONS FOR select, update, delete, create
  WHERE
    -- The token scope must match the scope that we defined.
    -- The name of the scope should match the scope that we defined before.
    $scope = "user"
    -- The issuer claim must match the URL of your AWS Cognito user pool.
    AND $token.iss =
      "https://cognito-idp.<YOUR_AWS_REGION>.amazonaws.com/<YOUR_COGNITO_USER_POOL_ID>"
    -- The audience claim must match you AWS Cognito Client ID.
    AND $token.aud = "<YOUR_COGNITO_CLIENT_ID>"
    -- The email claim must match the email of the user being queried.
    AND email = $token.email
    -- The email must be verified as belonging to the user.
    AND $token.email_verified = true
;

-- In this example, we will use the email as the primary identifier for a user.
DEFINE INDEX email ON user FIELDS email UNIQUE;
DEFINE FIELD email
  ON user TYPE string ASSERT string::is_email($value);
-- We define some other information present in the token that we want to store.
DEFINE FIELD cognito_username ON user TYPE string;
```

It is important to know that [validating the issuer and audience of the token is a requirement of AWS Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html), other providers may require validating additional claims to ensure that the token is being used as intended.

Note that the `$auth` variable accessible from SurrealQL will not contain any values in this case, as it requires the `id` claim to be added to the JWT, containing the value of the identifier of a SurrealDB record. For the current example, the `$auth` variable will not be necessary.

## Creating a simple web application

For this guide, we will create a simple client-side web application that will direct the user to log in using the Cognito Hosted UI, redirect the user back to our application with a query parameter containing an authorization code and exchange that code to Cognito for the user identity token which we will use to authenticate with SurrealDB. The application will create or update a SurrealDB user using data from the token claims. This user will later be able to visit our web application and retrieve their information from SurrealDB.

> [!NOTE]
> As most other authentication providers using OpenID Connect (OIDC), AWS Cognito issues both identity and access tokens. In this example, we will be using the identity token as it can include custom claims (which are required by SurrealDB) by default. Note that identity tokens should only be used to assert identity claims as opposed to access claims. In this case, we trust the identity token to provide information about the identity of the user. If we wanted the token to contain authorization claims (e.g. OAuth scopes) we should instead rely on the access token. Customising access token claims has been <a href="https://aws.amazon.com/about-aws/whats-new/2023/12/amazon-cognito-user-pools-customize-access-tokens/">recently supported by AWS</a> and requires enabling <a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-advanced-security.html#cognito-user-pool-settings-advanced-security.title">advanced security features</a>. This guide makes the deliberate decision of using the identity token to simplify the process.

We have developed an [example application](https://github.com/surrealdb/examples/tree/main/aws-cognito) that uses plain JavaScript to authenticate with Cognito using basic HTTP requests against the [login endpoint](https://docs.aws.amazon.com/cognito/latest/developerguide/login-endpoint.html) of the Cognito Hosted UI and the Cognito [token endpoint](https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html). For the purposes of following this guide, we recommend using our example code. However, keep in mind that this code aims to be as simple as possible and is not suitable for production applications. Alternatively, you can develop this application yourself using the [Cognito SDK](https://docs.aws.amazon.com/cognito/latest/developerguide/service_code_examples_cognito-identity-provider.html) or the new [Amplify SDK](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-integrate-apps.html#cognito-integrate-apps-amplify).

When using our example code, you will only need to update the `config.json` file with the values that you saved after creating your user pool and run the web application using the `start.sh` script or any equivalent web server. Once in the web application, clicking the "Log in" button should take you to the Cognito Hosted UI to log in with your test user, after which you should be redirected back to your web application. For this to work, the URL of your web application (without a trailing slash) should be present in the "Allowed callback URLs" list that you defined when configuring the user pool client. After redirection, the web application will show some information about the authenticated user and attempt to create it in SurrealDB via the configured endpoint. After the user is created, subsequent logins will retrieve its information from SurrealDB and display it in the web application. If that is not the case, use the developer console of your browser together with the SurrealDB logs (running with `--log trace` during the debugging for maximum verbosity) to understand why.

Once the example web application is working, you can inspect the simple code under `app.js` to understand how it works.

## Annex

In this section, we will provide a few examples of how to configure the application to work with AWS Cognito.

## Example single page application

You can view and download a minimal example of a web application using AWS Cognito and SurrealDB [in the AWS cognito example project](https://github.com/surrealdb/examples/tree/main/aws-cognito).

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/build-a-real-time-presence-app

# Build a real-time presence app

In this guide, you'll learn how to implement real-time presence tracking that can be integrated in any application including chat applications, multiplayer games, and more.

SurrealDB offers various features including a real-time notification mechanism called Live Queries. This feature allows you to subscribe to changes in your database and receive notifications in real time. In this guide, you’ll learn how to implement real-time presence tracking that can be integrated in any application including chat applications, multiplayer games, and more. The demo project is available on [GitHub](https://github.com/Odonno/surrealdb-presence-demo) using the following tech stack:

- [SurrealDB](/) our database
- [React](https://react.dev/) for the frontend with [Vite](https://vite.dev/) as a bundler
- [TanStack Query](https://tanstack.com/query) to fetch and store data retrieved from the database
- [Tailwind](https://tailwindcss.com/) for styling our application

## Hello, are you there?

![room-users.png](https://github.com/Odonno/surrealdb-presence-demo/raw/main/img/room-users.png)

The demo application looks like a simple chat application with basic features allowing users to join a room and send messages. The application also displays the number of users in the room and their presence status. The presence status is updated in real time using SurrealDB Live Queries. The mechanism used to detect the presence of a user is a periodic ping sent by the client to the server.

The following configuration is used to setup the project:

- Signal user presence in room periodically every **10 seconds**
- Display status badge based on idle time
	- 🟩 < **2 minutes** of inactivity
	- 🟨 < **10 minutes** of inactivity
	- ⬜ beyond **10 minutes** of inactivity

Note: Those values are completely arbitrary and can be changed to fit your needs.

## Architecture

This project is using the following folder structure:

- `/schemas` - list of SurrealDB tables
- `/events` - list of SurrealDB events
- `/migrations` - list of db migrations that will be automatically applied
- `/src`
	- `/api` - TanStack query hooks
	- `/components`
	- `/constants`
	- `/contexts` - Theme and SurrealDB providers
	- `/hooks` - custom React hooks
	- `/lib` - functions and app models
	- `/mutations` - surql query files to create or update data, using SurrealDB events
	- `/pages`
	- `/queries` - surql query files to query the database, using SurrealDB tables

## Prerequisites

Before you begin this tutorial you’ll need the following:

a. [SurrealDB](/docs/running/installation.md) installed on your machine (Make sure you upgrade to the latest version if you already have SurrealDB installed on your machine)

b. The [Bun](https://bun.sh/docs/installation) runtime

c. Optional: [surrealdb-migrations](https://github.com/Odonno/surrealdb-migrations) to manage and automate the deployment of your SurrealDB schema

d. A basic understanding of React and TanStack Query

## Step 0: Setup the project

Once everything is installed, clone the project and navigate to it. Then:

1. Start a new SurrealDB instance locally

```bash
surreal db start --log debug --user root --pass secret \
  --allow-guests
```

2. Apply migrations to the database

Either apply schema and migrations automatically by running the following command:

```bash
surrealdb-migrations apply
```

Or manually apply each file stored in the following folders:

- `schemas`
- `events`
- `migrations`

3. Install dependencies and run the web app

```bash
bun install
bun start
```

4. Launch your web browser on the generated url (eg. http://localhost:5173/) and play with the app: create new accounts, join rooms, leave rooms, etc.

## Step 1: Authentication

For users to join rooms and interact with the app, we need users. And thankfully, SurrealDB also offers authentication mechanism. We will need some basic authentication such as a registration form, a login form, and a way to sign out.

The `user` table will look like this:

```surql
DEFINE TABLE user SCHEMALESS;

DEFINE FIELD username ON user TYPE string;
DEFINE FIELD email ON user TYPE string PERMISSIONS FOR select NONE;
DEFINE FIELD passcode ON user TYPE string PERMISSIONS FOR select NONE;
DEFINE FIELD registered_at ON user TYPE datetime DEFAULT time::now();
DEFINE FIELD avatar ON user TYPE option<string>;

DEFINE INDEX unique_username ON user FIELDS username UNIQUE;
DEFINE INDEX unique_email ON user FIELDS email UNIQUE;

DEFINE ACCESS user_access
    ON DATABASE TYPE RECORD
    SIGNUP (
        CREATE user
        SET
            username = $username,
            email = $email,
            avatar = "https://www.gravatar.com/avatar/" +
              crypto::md5($email) + "?d=identicon",
            passcode = fn::create_passcode($email)
    )
    SIGNIN (
        SELECT *
        FROM user
        WHERE email = $email AND passcode = $passcode
    );
```

We can then create a [login form](https://github.com/Odonno/surrealdb-presence-demo/blob/main/src/components/SignInPopover.tsx) and a [sign up dialog](https://github.com/Odonno/surrealdb-presence-demo/blob/main/src/components/SignUpDialog.tsx).

> [!NOTE]
>We use a passcode to ensure a minimum security authentication. This passcode is generated by the signup function and is stored in the user record. The signin function checks if the passcode is correct. This is for a demonstration purpose only. In a real-world application, you should use a more secure authentication mechanism.

## Step 2: Display room information

We first start by writing the query that will be used to display room information.

```surql
SELECT
    id,
    name,
    created_at,
    (
        RETURN $auth.id IN $parent.users
    ) AS is_in_room,
    array::len(
        SELECT id
        FROM $parent.users
        WHERE time::now() - ((
            SELECT VALUE at
            FROM last_presence
            WHERE user == $parent.id
        )[0] ?? time::from_secs(0)) < 5m
    ) AS number_of_active_users,
    owner.id != $auth.id AS can_leave
FROM room
ORDER BY created_at DESC;
```

Each `*.surql` query file can then be linked to a TanStack Query query, like this one:

```ts
import roomsQuery from "@/queries/rooms.surql?raw"; // importing raw text file query written in SurrealQL

export const useRooms = () => {
	const dbClient = useSurrealDbClient();

	const getRoomsAsync = async () => {
		const response = await dbClient.query<[Room[]]>(roomsQuery);
		return response[0];
	};

	return useQuery({
		...queryKeys.rooms.list,
		queryFn: getRoomsAsync,
	});
};
```

Here, we will expose a new hook that encapsulates a `useQuery` hook underneath. The same can be done with TanStack query mutations.

## Step 3: Signal user presence

Signaling a presence from the client is almost too easy.

We use the `usePageVisibility` hook to ensure the user is still looking at our app, meaning he did not put the app in the background. Note: this hook is using the [Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API) underneath.

And if the page is visible, we use the `useInterval` hook to trigger the signal event every 10 seconds via a TanStack Query mutation.

```ts
const SIGNAL_PRESENCE_INTERVAL = 10 * SECOND;

const SignalPresence = () => {
	const isPageVisible = usePageVisibility();
	const canSignalPresence = isPageVisible;

	const dbClient = useSurrealDbClient();

	const signalPresence = useMutation({
		mutationKey: ["signalPresence"],
		mutationFn: async () => {
			await dbClient.query(signalPresenceQuery);
		},
	});

	useInterval(
		() => {
			signalPresence.mutate();
		},
		canSignalPresence ? SIGNAL_PRESENCE_INTERVAL : null,
	);

	useEffect(() => {
		if (canSignalPresence) {
			signalPresence.mutate();
		}
	}, [isPageVisible]);

	return null;
};
```

The mutation will trigger the following SurrealDB event:

```surql
DEFINE EVENT signal_presence
  ON TABLE signal_presence WHEN $event == "CREATE" THEN (
    CREATE presence SET user = $auth.id
);
```

The `presence` table will store every presence detection event triggered by our application.

## Step 4: Display real-time presence status

Storing every presence detection event is only the first step. We want to display the presence status of each user in real time efficiently. To do so, we will create a new table called `last_presence` to retrieve the last presence detection event for each user. We will then be able to use this table to display the presence status of each user in real time.

```surql
DEFINE TABLE last_presence AS
	SELECT
        user,
        time::max(updated_at) AS at
    FROM presence
	GROUP BY user;
```

Displaying the presence status badge of a user is quite simple. We just need to retrieve the last presence detection event for the user and display the presence status badge based on the time difference between the last presence detection event and the current time.

```ts
const GREEN_STATUS_THRESHOLD = 2 * MINUTE;
const ORANGE_STATUS_THRESHOLD = 10 * MINUTE;

const getPresenceBackgroundClass = (
	lastPresenceDate: Date | undefined,
	now: Date,
) => {
	if (!lastPresenceDate) {
		return "bg-gray-500";
	}

	const diffTimeInSeconds = now.getTime() - lastPresenceDate.getTime();

	if (diffTimeInSeconds < GREEN_STATUS_THRESHOLD) {
		return "bg-green-500";
	}

	if (diffTimeInSeconds < ORANGE_STATUS_THRESHOLD) {
		return "bg-yellow-500";
	}

	return "bg-gray-500";
};

export type PresenceProps = {
	lastPresenceDate?: Date;
	className?: string;
};

const Presence = (props: PresenceProps) => {
	const { lastPresenceDate, className } = props;

	const [now, setNow] = useState(new Date());

	useInterval(() => {
		setNow(new Date());
	}, SECOND);

	const bgClass = getPresenceBackgroundClass(lastPresenceDate, now);

	return (
		<span className={cn(className, "w-2.5 h-2.5 rounded-full", bgClass)} />
	);
};
```

> [!NOTE]
> We trigger a re-render every second to update the presence status. This may not be the most efficient way to do it, but it's enough for this demo. We could use a more specific interval, or use a more efficient way to notify the client when the presence status changes, but it's not the point of this demo.

Now, this component can be easily integrated into other components, like this one:

```ts
const CurrentUserPresence = () => {
	const lastPresenceDate = useRealtimeCurrentUserPresence();

	return (
		<Presence lastPresenceDate={lastPresenceDate} className="-ml-1 mt-1" />
	);
};
```

For reference, we query the last presence of the current user with this query:

```surql
SELECT VALUE at
FROM last_presence
WHERE user == $auth.id;
```

The `useRealtimeCurrentUserPresence` hook retrieves the last presence of the current user and is composed of multiple hooks:

- `useCurrentUserPresence` - the base hook to retrieve the current user presence (without real-time capability)
- `useCurrentUserPresenceLive` - the base hook that is notified by each change in the database (pure real-time capability)
- `useRealtimeCurrentUserPresence` - the hook itself that combines both previous hooks (current value + upcoming changes)

```ts
const useCurrentUserPresence = () => {
	const dbClient = useSurrealDbClient();

	const getCurrentUserPresenceAsync = async () => {
		const response = await dbClient.query<[string]>(currentUserPresenceQuery);

		if (!response?.[0]) {
			throw new Error();
		}

		return new Date(response[0]);
	};

	return useQuery({
		...queryKeys.users.current._ctx.presence,
		queryFn: getCurrentUserPresenceAsync,
	});
};

const useCurrentUserPresenceLive = (enabled: boolean) => {
	const dbClient = useSurrealDbClient();

	const getCurrentUserPresenceLiveAsync = async () => {
		const query = `LIVE ${currentUserPresenceQuery}`;
		const response = await dbClient.query<[Uuid]>(query);
		return response?.[0];
	};

	return useQuery({
		...queryKeys.users.current._ctx.presence._ctx.live,
		queryFn: getCurrentUserPresenceLiveAsync,
		enabled,
	});
};

export const useRealtimeCurrentUserPresence = () => {
	const queryClient = useQueryClient();

		const { data: lastPresenceDate,
	    isSuccess } = useCurrentUserPresence();
	const { data: liveQueryUuid } = useCurrentUserPresenceLive(isSuccess);

	useLiveQuery({
		queryUuid: liveQueryUuid,
		callback: (action, result) => {
			if (action === "CREATE" || action === "UPDATE") {
				queryClient.setQueryData(
					queryKeys.users.current._ctx.presence.queryKey,
					new Date(result as unknown as string),
				);
			}
		},
		enabled: Boolean(liveQueryUuid),
	});

	useMount(() => {
		return () => {
			queryClient.invalidateQueries({
				queryKey: queryKeys.users.current._ctx.presence.queryKey,
			});
		};
	});

	return lastPresenceDate;
};
```

## Step 5: Refactoring with the `useLiveQuery` hook

One can notice the presence of the `useLiveQuery` hook. This hook is a custom hook that we created to simplify the lifecycle of a Live Query. It automatically subscribes to the Live Query when enabled and kills the Live Query on cleanup (when the component is unmounted). Cleaning up Live Queries correctly prevents memory leaks.

```typescript
export type UseLiveQueryProps<
	T extends Record<string, unknown> = Record<string, unknown>,
> = {
	queryUuid: Uuid | undefined;
	callback: LiveHandler<T>;
	enabled?: boolean;
};

export const useLiveQuery = ({
	queryUuid,
	callback,
	enabled = true,
}: UseLiveQueryProps) => {
	const dbClient = useSurrealDbClient();

	useEffect(() => {
		if (enabled && !!queryUuid) {
			const runLiveQuery = async () => {
				await dbClient.subscribeLive(queryUuid, callback);
			};

			const clearLiveQuery = async () => {
				await dbClient.kill(queryUuid);
			};

			const handleBeforeUnload = () => {
				clearLiveQuery();
			};

			window.addEventListener("beforeunload", handleBeforeUnload);
			runLiveQuery();

			return () => {
				clearLiveQuery();
				window.removeEventListener("beforeunload", handleBeforeUnload);
			};
		}
	}, [queryUuid, enabled]);
};
```

## Bonus: the simulator

Being alone is not really that fun, isn't it? We can't really test the real-time presence feature without having multiple users connected to the same room. That's why we can use the simulator script built to generate some fake users that will interact with the app while active. You can run the following command to start the simulator:

```bash
bun run .\simulator.ts
```

And then let's enjoy the nature of randomness to make the app alive!

## Resources

- [Live demo](https://surrealdb-presence-demo.vercel.app/) - The live demo of the application
- [GitHub repository](https://github.com/Odonno/surrealdb-presence-demo) - The GitHub repository of the application
- [SurrealDB Live Queries](/docs/reference/query-language/statements/live-select.md) - The SurrealDB Live Queries documentation
- [SurrealQL Documentation](/docs/reference/query-language.md) - The SurrealQL documentation
- [Javascript SDK documentation](/docs/reference/javascript.md) - The Javascript SDK documentation

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/build-an-ai-agent

# Build an AI agent with Python

In this tutorial, you will learn three ways to build an AI agent in Python that uses SurrealDB.

This page details a number of ways that you can build an AI agent. As there are many frameworks and programming languages to choose from, this page details three Python frameworks to get you started.

## Setup

You can run SurrealDB locally or start with a [free SurrealDB Cloud account](/docs/manage/instances.md).

For local, you have two options:

1. [Install SurrealDB](/docs/running/installation.md) and run [SurrealDB](/docs/running/in-memory.md). Run in-memory with:

```sh
surreal db start -u root -p secret
```

2. [Run with Docker](/docs/running/docker.md).

```sh
docker run --rm \
  --pull always \
  -p 8000:8000 \
  surrealdb/surrealdb:latest \
  start \
  --user root \
  --pass secret
```

## Frameworks

Choose one of the following frameworks to view the tutorial and sample code.

**Pydantic AI**

## Getting started

This is a simple RAG application that uses Pydantic AI and embedded SurrealDB. The integration is done by providing the agent with a custom retrieval tool, which takes a search query, executes a SurrealDB vector-search query, and returns the results.

**To run the example:**

Set up your OpenAI API key:

```bash
export OPENAI_API_KEY=your-api-key
```

Or, store it in a .env file and add `--env-file .env` to your `uv run` commands.

Build the vector store:

```bash
uv run --env-file .env -m pydantic_ai_examples.rag_surrealdb build
```

Ask the agent a question:

```bash
uv run --env-file .env -m pydantic_ai_examples.rag_surrealdb search \
  "How do I register a function as a custom tool for my agent?"
```

Or use the web UI:

```bash
uv run --env-file .env -m pydantic_ai_examples.rag_surrealdb web
```

### Code

```python
from __future__ import annotations as _annotations

import asyncio
import re
import sys
import unicodedata
from collections.abc import Sequence
from contextlib import asynccontextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import TypeVar

import httpx
import logfire
import uvicorn
from pydantic import BaseModel, TypeAdapter
from surrealdb import (
    AsyncEmbeddedSurrealConnection,
    AsyncHttpSurrealConnection,
    AsyncSurreal,
    AsyncWsSurrealConnection,
    RecordID,
    Value,
)
from typing_extensions import AsyncGenerator

from pydantic_ai import Agent, Embedder

SurrealConn = (
    AsyncWsSurrealConnection
    | AsyncHttpSurrealConnection
    | AsyncEmbeddedSurrealConnection
)

# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
logfire.configure(send_to_logfire='if-token-present')
logfire.instrument_pydantic_ai()
logfire.instrument_surrealdb()

THIS_DIR = Path(__file__).parent

SURREALDB_NS = 'pydantic_ai_examples'
SURREALDB_DB = 'rag_surrealdb'
SURREALDB_USER = 'root'
SURREALDB_PASS = 'secret'

embedder = Embedder('openai:text-embedding-3-small')
agent = Agent('openai:gpt-5.2')

RecordType = TypeVar('RecordType')


class RetrievalQueryResult(BaseModel):
    url: str
    title: str
    content: str
    dist: float


async def query(
    conn: SurrealConn,
    query_: str,
    vars_: dict[str, Value],
    record_type: type[RecordType],
) -> list[RecordType]:
    result = await conn.query(query_, vars_)
    result_ta = TypeAdapter(list[record_type])
    rows = result_ta.validate_python(result)
    return rows


@agent.tool_plain
async def retrieve(search_query: str) -> str:
    """Retrieve documentation sections based on a search query.

    Args:
        search_query: The search query.
    """
    with logfire.span(
                'create embedding for {search_query=}',
            search_query=search_query
    ):
        result = await embedder.embed_query(search_query)
        embedding = result.embeddings

    # Embedder method guarantees there's one item here
    embedding_vector = embedding[0]

    # SurrealDB vector search using HNSW index
    async with database_connect(False) as db:
        rows = await query(
            db,
            """
            SELECT url, title, content, vector::distance::knn() AS dist
            FROM doc_sections
            WHERE embedding <|8, 40|> $vector
            ORDER BY dist ASC
            """,
            {'vector': list(embedding_vector)},
            RetrievalQueryResult,
        )

    return '\n\n'.join(
        f'# {row.title}\nDocumentation URL:{row.url}\n\n{row.content}' for row in rows
    )


async def run_agent(question: str):
    """Entry point to run the agent and perform RAG based question answering."""
    logfire.info('Asking "{question}"', question=question)
    answer = await agent.run(question)
    print(answer.output)


# Web chat UI
app = agent.to_web()

#######################################################
# The rest of this file is dedicated to preparing the #
# search database, and some utilities.                #
#######################################################

# JSON document from
# https://gist.github.com/samuelcolvin/4b5bb9bb163b1122ff17e29e48c10992
DOCS_JSON = (
    'https://gist.githubusercontent.com/'
    'samuelcolvin/4b5bb9bb163b1122ff17e29e48c10992/raw/'
    '80c5925c42f1442c24963aaf5eb1a324d47afe95/logfire_docs.json'
)


def build_doc_rec_id(url: str) -> RecordID:
    return RecordID('doc_sections', slugify(url, '_'))


async def build_search_db():
    """Build the search database."""
    async with httpx.AsyncClient() as client:
        response = await client.get(DOCS_JSON)
        response.raise_for_status()
    sections = sections_ta.validate_json(response.content)

    async with database_connect(True) as db:
        missing_sections: list[DocsSection] = []
        for section in sections:
            url = section.url()
            record_id = build_doc_rec_id(url)
            existing = await db.select(record_id)
            if existing:
                logfire.info('Skipping {url=}', url=url)
                continue
            missing_sections.append(section)

        if missing_sections:
            with logfire.span('create embeddings'):
                result = await embedder.embed_documents(
                    [section.embedding_content() for section in missing_sections]
                )
                embeddings = result.embeddings

            for section, embedding_vector in zip(
                missing_sections, embeddings, strict=True
            ):
                await insert_doc_section(db, section, embedding_vector)
        else:
            logfire.info('All documents already exist; skipping embedding generation')


async def insert_doc_section(
    db: SurrealConn,
    section: DocsSection,
    embedding_vector: Sequence[float],
) -> None:
    url = section.url()
    record_id = build_doc_rec_id(url)

    # Create record with embedding, using record ID directly
    res = await db.create(
        record_id,
        {
            'url': url,
            'title': section.title,
            'content': section.content,
            'embedding': list(embedding_vector),
        },
    )
    if not isinstance(res, dict):
        raise ValueError(f'Unexpected response from database: {res}')


@dataclass
class DocsSection:
    id: int
    parent: int | None
    path: str
    level: int
    title: str
    content: str

    def url(self) -> str:
        url_path = re.sub(r'\.md$', '', self.path)
        return (
            f'https://logfire.pydantic.dev/docs/{url_path}/#{slugify(self.title, "-")}'
        )

    def embedding_content(self) -> str:
                return '\n\n'.join((f'path: {self.path}',
            f'title: {self.title}', self.content))


sections_ta = TypeAdapter(list[DocsSection])


@asynccontextmanager
async def database_connect(
    create_db: bool = False,
) -> AsyncGenerator[SurrealConn, None]:
    # Running SurrealDB embedded
    db_path = THIS_DIR / f'.{SURREALDB_DB}'
    db_url = f'file://{db_path}'
    requires_auth = False

    # Running SurrealDB in a separate process, connect with URL
    # db_url = 'ws://localhost:8000/rpc'
    # requires_auth = True

    async with AsyncSurreal(db_url) as db:
        # Sign in to the database
        if requires_auth:
            await db.signin({'username': SURREALDB_USER, 'password': SURREALDB_PASS})

        # Set namespace and database
        await db.use(SURREALDB_NS, SURREALDB_DB)

        # Initialize schema if creating database
        if create_db:
            with logfire.span('create schema'):
                await db.query(DB_SCHEMA)

        yield db


DB_SCHEMA = """
DEFINE TABLE doc_sections SCHEMALESS;

DEFINE FIELD embedding ON doc_sections TYPE array<float>;

DEFINE INDEX hnsw_idx_doc_sections ON doc_sections
    FIELDS embedding
    HNSW DIMENSION 1536
    DIST COSINE
    TYPE F32;
"""


def slugify(value: str, separator: str, unicode: bool = False) -> str:
    """Slugify a string, to make it URL friendly."""
    # Taken unchanged from https://github.com/Python-Markdown/markdown/blob/3.7/markdown/extensions/toc.py#L38
    if not unicode:
                # Replace Extended Latin characters with ASCII,
            i.e. `žlutý` => `zluty`
        value = unicodedata.normalize('NFKD', value)
        value = value.encode('ascii', 'ignore').decode('ascii')
    value = re.sub(r'[^\w\s-]', '', value).strip().lower()
    return re.sub(rf'[{separator}\s]+', separator, value)


if __name__ == '__main__':
    action = sys.argv[1] if len(sys.argv) > 1 else None
    if action == 'build':
        asyncio.run(build_search_db())
    elif action == 'search':
        if len(sys.argv) == 3:
            q = sys.argv[2]
        else:
            q = 'How do I configure logfire to work with FastAPI?'
        asyncio.run(run_agent(q))
    elif action == 'web':
        uvicorn.run(app, host='127.0.0.1', port=7932)
    else:
        print(
            'uv run --extra examples -m pydantic_ai_examples.rag_surrealdb build|search|web',
            file=sys.stderr,
        )
        sys.exit(1)
```

**LangChain**

## Getting started

Begin by installing the following dependencies.

```bash
# -- Using pip
pip install -U langchain-surrealdb langchain_ollama surrealdb
# -- Using poetry
poetry add langchain-surrealdb langchain_ollama surrealdb
# -- Using uv
uv add --upgrade langchain-surrealdb langchain_ollama surrealdb
```

* `surrealdb` → [SurrealDB Python SDK](/docs/reference/python/)
* `langchain-surrealdb` → houses `SurrealDBVectorStore`
* `langchain_ollama`, `langchain-openai` (or HF, Cohere, etc.) → embeddings

Once this is done, you can create a vector store, add documents with embeddings, and do a similarity search.

```python
from langchain_core.documents import Document
from langchain_surrealdb.vectorstores import SurrealDBVectorStore
from langchain_ollama import OllamaEmbeddings
from surrealdb import Surreal

conn = Surreal("ws://localhost:8000/rpc")
conn.signin({"username": "root", "password": "secret"})
conn.use("langchain", "demo")
vector_store = SurrealDBVectorStore(OllamaEmbeddings(model="llama3.2"), conn)

doc_1 = Document(page_content="foo",
    metadata={"source": "https://surrealdb.com"})
doc_2 = Document(page_content="SurrealDB",
    metadata={"source": "https://surrealdb.com"})

vector_store.add_documents(documents=[doc_1, doc_2], ids=["1", "2"])

results = vector_store.similarity_search_with_score(
        query="surreal", k=1,
        custom_filter={"source": "https://surrealdb.com"}
)

for doc, score in results:
    print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]")
```

Internally the helper will:

1. Create table **`documents`** (if it doesn’t exist).
2. Add an **HNSW** index with the correct dimensionality, using cosine distance and `F32` vectors.
3. Insert each text with its freshly generated embedding.

## Similarity search

```python
query = "How do I enable vector search in SurrealDB?"
docs = vector_store.similarity_search(
        query=query, k=1,
        custom_filter={"source": "https://surrealdb.com"}
)
for doc in results:
    print(f"{doc.page_content} [{doc.metadata}]")
```

```text
The Vector Search feature of SurrealDB... [{'source': \
  'https://surrealdb.com'}]
```

If you want to get the score with the results, use `similarity_search_with_score` instead.

You can also transform the vector store into a retriever for easier usage in your chains.

```python
query = "How do I enable vector search in SurrealDB?"
docs = vector_store.similarity_search(
retriever = vector_store.as_retriever(
    search_type="mmr", search_kwargs={"k": 1, "lambda_mult": 0.5}
)
retriever.invoke(query)
```

```text
[Document(id='4', metadata={'source': 'https://surrealdb.com'}, \
  page_content='The Vector Search feature of SurrealDB...')]
```

**Agno**

## Getting started

```python
from agno.agent import Agent
from agno.embedder.openai import OpenAIEmbedder
from agno.knowledge.pdf_url import PDFUrlKnowledgeBase
from agno.vectordb.surrealdb import SurrealDb
from surrealdb import Surreal

# SurrealDB connection parameters
SURREALDB_URL = "ws://localhost:8000"
SURREALDB_USER = "root"
SURREALDB_PASSWORD = "secret"
SURREALDB_NAMESPACE = "main"
SURREALDB_DATABASE = "main"

# Create a client
client = Surreal(url=SURREALDB_URL)
client.signin({"username": SURREALDB_USER,
    "password": SURREALDB_PASSWORD})
client.use(namespace=SURREALDB_NAMESPACE, database=SURREALDB_DATABASE)

surrealdb = SurrealDb(
    client=client,
    collection="recipes",  # Collection name for storing documents
    efc=150,  # HNSW construction time/accuracy trade-off
    m=12,  # HNSW max number of connections per element
    search_ef=40,  # HNSW search time/accuracy trade-off
)


def sync_demo():
    """Demonstrate synchronous usage of SurrealDb"""
    knowledge_base = PDFUrlKnowledgeBase(
        urls=["https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf"],
        vector_db=surrealdb,
        embedder=OpenAIEmbedder(),
    )

    # Load data synchronously
    knowledge_base.load(recreate=True)

    # Create agent and query synchronously
    agent = Agent(knowledge=knowledge_base, show_tool_calls=True)
    agent.print_response(
        "What are the 3 categories of Thai SELECT is given to restaurants overseas?",
        markdown=True,
    )


if __name__ == "__main__":
    # Run synchronous demo
    print("Running synchronous demo...")
    sync_demo()
```

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/connect-via-ngrok

# Connect to SurrealDB via ngrok

Expose a local SurrealDB instance to the internet with ngrok for remote development and testing.

This guide will walk you through connecting a local SurrealDB instance to the internet using ngrok, making it accessible remotely.

[Ngrok](https://ngrok.com/) is a cross-platform application that allows developers to expose their local web servers to the internet. It hosts a local web server on its own sub-domain and makes your local development box available on the internet through Tunneling.

This setup is beneficial for remote development and quick testing of applications built locally on SurrealDB.

## Prerequisites

This guide assumes the following:

- You have [SurrealDB installed](/docs/running/installation.md) and a [fresh instance of SurrealDB running.](/docs/running/overview.md)
- Downloaded [ngrok](https://ngrok.com/download) to be used as a tunnel.

> [!IMPORTANT]
> Tunneling refers to the process of using a network protocol to encapsulate a different payload protocol, enabling data to pass securely through a network or the internet

## Steps

There are two ways to connect to a SurrealDB instance via ngrok tunnel.

### Start SurrealDB instance

Open your command line or terminal and run the following command to [start SurrealDB](/docs/running/overview.md).

```bash
surreal db start memory -A --user root --pass secret
```

We use the default username `root` and password `secret`. You can replace it with your own credentials if you have set them up.

### Set up ngrok tunnel

Open another command line or terminal window (do not close the SurrealDB one) and run the following command to expose SurrealDB’s default port (8000) to the internet:

```bash
ngrok http 8000
```

Note the forwarding address provided by ngrok. For example, **`25f6-2402-e280-2189-38e-9c15-d08-2f83-779e.ngrok-free.app`**.

> [!IMPORTANT]
> Keep this address handy as we will use it in the next step.

### Connect and verify

Connect using the forwarding address from ngrok, replacing **`[ngrok-address]`** with the address you noted earlier. You can open an interactive REPL:

```bash
surreal db sql --endpoint wss://[ngrok-address] --user root --pass secret --ns main --db main --pretty
```

Or run a one-shot check that [creates a record](/docs/reference/query-language/statements/create.md) and [selects it](/docs/reference/query-language/statements/select.md):

**Bash**

```bash
echo "CREATE registration SET full_name = 'John Doe', email = 'johndoe@gmail.com', address_line1 = 'Room number 1, Hogwarts', address_line2 = 'Near Diagon Alley', city = 'Hogwarts', country = 'England'; SELECT * FROM registration;" | surreal db sql --endpoint wss://[ngrok-address] --user root --pass secret --ns main --db main --pretty --hide-welcome
```

**PowerShell**

```powershell
"CREATE registration SET full_name = 'John Doe', email = 'johndoe@gmail.com', address_line1 = 'Room number 1, Hogwarts', address_line2 = 'Near Diagon Alley', city = 'Hogwarts', country = 'England'; SELECT * FROM registration;" | surreal db sql --endpoint wss://[ngrok-address] --user root --pass secret --ns main --db main --pretty --hide-welcome
```

## Conclusion

In this guide, we have looked at how we can connect to a local instance of SurrealDB using tunnels like ngrok which can help with testing applications. ngrok provides a random or custom subdomain for your tunnel URL every time you start the tunnel, which is difficult to predict. The data transmitted over the ngrok tunnel is encrypted, ensuring the information remains secure while it travels over the internet.

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/define-a-schema

# Define a schema in SurrealDB

In this tutorial, you will learn how to define a schema in SurrealDB, what using either schema type means for data retrieval, and how to use what you need as your product grows.

When starting a new database project, you need to make a few early decisions: which tables to create, which fields they hold and what data types those fields take, and how the tables relate to each other, including how data is shared or manipulated between them.

A schema defines the structure and organisation of data. It dictates how data is stored, organised, and manipulated. Schemas can specify tables, fields (columns), data types, constraints, and relationships between tables.

A schema is therefore the primary way to ensure your data acts as expected.

There are two main types of schemas:

1. **Schemafull (structured)**
2. **Schemaless (unstructured)**

When starting a new project in SurrealDB, you can define your schema using either method depending on your application's requirements.

## Prerequisites

Before you start, this guide assumes the following:

- Basic knowledge of databases and data modelling.
- SurrealDB is installed on your machine. You can download and install SurrealDB from the [installation page](/docs/running/installation.md).
- [A Command line interface (CLI)](/docs/reference/cli/db/commands/sql.md) for interacting with SurrealDB or a [SurrealDB Studio sandbox](/docs/explore/studio.md).

## Schemafull (structured) databases

A schemafull database requires the upfront definition of the structure of your data, including collections (tables) and fields (columns). This approach enforces consistency and integrity, making it suitable for applications with well-defined data models.

In SurrealDB, the schemafull approach is realised through [Define statements](/docs/reference/query-language/statements/define/overview.md), that provide instructions on parts of your database, such as authentication access and behaviour, global parameters, table configurations, table events, analyzers, and indexes. You can set a schemafull table in the following steps:

### Creating a schemafull table

1. Define a Table: To start a schemafull table, specifically use the Define Table statement.

```surql
-- Create a schemafull user table.
DEFINE TABLE user SCHEMAFULL;
```

2. Define Fields: Now that the table is schemafull, no fields can be set unless first defined through a `DEFINE FIELD` statement.

```surql
-- Define some fields.
DEFINE FIELD firstName ON TABLE user TYPE string;
DEFINE FIELD lastName ON TABLE user TYPE string;
DEFINE FIELD email ON TABLE user TYPE string
  ASSERT string::is_email($value);
```

In the code above you may notice the `ASSERT` clause. This can be used to validate any restrictions you want on a field. In the example above the [`string::is_email`](/docs/reference/query-language/functions/database-functions/string.md#stringis_email) function is used to check whether the [value](/docs/reference/query-language/language-primitives/parameters.md#reserved-variable-names) is an email.

### Adding data to a schemafull table

Now that you have defined all the fields needed, you can start populating them. To do this, you can use the [CREATE statement](/docs/reference/query-language/statements/create.md). For example, add a new user:

```surql
-- 1: Add a user with all required fields.
CREATE user CONTENT {
    firstName: 'John',
    lastName: 'Doe',
    email: 'JohnDoe@someemail.com',
};
```

This will return the data in an object. In the case where the email wasn’t a real email for example:

```surql
-- Using the CREATE statement to populate the table
CREATE user CONTENT {
    firstName: 'John',
    lastName: 'Doe',
    email: 'JohnDoe.com',
};
```

The above will return an error because the field must conform to `string::is_email($value)`

### Inserting fields that don't exist in the schema

In a schemafull table, since the fields need to be defined before you can populate them, if you add a field that doesn’t exist, your data will be ignored. For example, in the user table, you have only defined the `firstname`, `lastname` and `email` fields. If you introduce a `photoURI` field without defining the field in the `user` table, it will return an error.

```surql
-- 2: Add a user with all required fields and an undefined one, 'photoURI'.
CREATE user CONTENT {
    firstName: 'John',
    lastName: 'Doe',
    email: 'JohnDoe@someemail.com',
    photoURI: 'photo/yxCFi22Jw2.webp'
};
```

## Schemaless (unstructured) databases

A schemaless database does not require predefined structures, allowing for more flexible and dynamic data storage. This approach is ideal for applications with evolving data models or when dealing with diverse and unpredictable data formats.

In SurrealDB there are two ways you can define a schemaless table. You can either use any of the data definition statements such as [`CREATE`](/docs/reference/query-language/statements/create.md) or [`UPDATE`](/docs/reference/query-language/statements/update.md)  and that will make a table based on the [record ID](/docs/reference/query-language/language-primitives/data-types/record-ids.md#array-based-record-ids) specified. For example:

```surql
-- Using the CREATE statement
CREATE IC_directory:['John', 'Doe'] CONTENT {
	username: 'johndoe',
	full_name: 'John Doe',
	email: 'johndoe@example.com',
	date_of_birth: "1990-01-01",
	join_date: "2024-05-30",
	department: 'Engineering',
	role: 'Software Engineer',
	skills: ['Python', 'JavaScript', 'surql'],
	manager: manager_directory:janesmith,
	tags: ['full-time', 'remote']
};

-- Using the UPDATE statement
UPDATE manager_directory:janesmith CONTENT {
	username: 'janesmith',
	full_name: 'Jane Smith',
	email: 'janesmith@example.com',
	date_of_birth: "1985-01-01",
	join_date: "2019-05-30",
	department: 'Engineering',
    role: 'Software Engineer Manager',
	skills: ['Python', 'JavaScript', 'surql'],
	report: IC_directory:['John', 'Doe'],
	tags: ['full-time', 'remote']
};
```

In the example above, you used the `CREATE` statement to make an `IC_directory` table and created an Object ID for this table. Record IDs can be specified by you [in a large number of formats](/docs/reference/query-language/language-primitives/data-types/record-ids.md), defaulting to a random UUID if you don't specify your own format for the ID. You have also used the UPDATE statement to make a `manager_directory`, which has `janesmith` as the ID. Learn more about [Record IDs in the documentation](/docs/reference/query-language/language-primitives/data-types/record-ids.md#array-based-record-ids).

Notice how we have linked these two tables with the manager and report fields in the `IC_directory` and `manager_directory` tables, respectively.

You can also start defining a schemaless table using the `DEFINE TABLE` statement.

```surql
-- Create schemaless user table.
DEFINE TABLE user SCHEMALESS;

-- Define some fields.
DEFINE FIELD firstName ON TABLE user TYPE string;
DEFINE FIELD lastName ON TABLE user TYPE string;
DEFINE FIELD email ON TABLE user TYPE string;
```

In the example above you have created a schemaless table using the `SCHEMALESS` clause.

### Inserting fields that don't exist in the schema

Since a schemaless table doesn’t have any restrictions on the structure if you introduce a new field to an existing table, the field will be added even if other records don’t have the value. For example, take the schemafull example of introducing a `photoURI` field without defining the field in the `user` table:

```surql
CREATE user CONTENT {
    firstName: 'John',
    lastName: 'Doe',
    email: 'JohnDoe@someemail.com',
    photoURI: 'photo/yxCFi22Jw2.webp'
};
```

Since the `user` table is schemaless the `photoURI` field will be added for `John` without an error.

## Combining schemafull and schemaless

Now that you have seen both ways to make a table in SurrealDB, the choice between them depends on the restrictions you want to have on your schema.

Since SurrealDB is multi-model, if some tables require less restrictions you can make them schemaless and if you want to ensure that the structure remains the same for each entry then go with the schemafull option.

### Adding flexible fields in a schemafull table

In SurrealDB, you can add [flexible fields](/docs/reference/query-language/statements/define/field.md#flexible-data-types) to a schemafull table using the `FLEXIBLE` field clause on `DEFINE FIELD`. The clause comes after `TYPE` and relaxes object validation for that field. The table must be defined as `SCHEMAFULL` and the field must also contain an object for the statement to parse. For example:

```surql
DEFINE TABLE user SCHEMAFULL;

-- Define a flexible object field in a schemafull table.
DEFINE FIELD interactions ON TABLE user TYPE object FLEXIBLE;

-- Will not parse, must be an object
-- DEFINE FIELD wont_parse ON TABLE user TYPE string FLEXIBLE;

-- This would parse because an 'object' is present
-- DEFINE FIELD metadata ON TABLE user TYPE {
--     name: string,
--     other: object
-- } FLEXIBLE;
```

In the example above, you have added a flexible field `interactions` to the `user` table. This field can store any type of data, making it schemaless within a schemafull table. For example, you can store chat logs, emails, phone call records, or any other unstructured data in this field as a JSON object.

```surql
-- Add an interaction to the user table.
UPDATE user:wd99oovq358zfdmajnt7 CONTENT {
    firstName: 'John',
    lastName: 'Doe',
    email: 'JohnDoe@someemail.com',
    interactions: {
        type: 'email',
        subject: 'Welcome to our platform',
        body: 'Thank you for joining our platform. We hope you enjoy your experience.',
        date: '2024-05-30',
    },
};
```

In the example above, you have added an interaction to the `user` table using the `UPDATE` statement. The `interactions` field stores an email interaction with the user, including the type, subject, body, and date.

### Use case: Customer Relationship Management (CRM) system

A Customer Relationship Management (CRM) system is a prime example of an application that can benefit from both schemafull and schemaless tables.

In this system, schemafull tables are essential for storing structured and consistent data such as customer details, orders, and products.

For instance, a table for customers would include fields like customer ID, first name, last name, email, phone number, and the date they joined, ensuring data integrity and facilitating reliable reporting and analysis.

Similarly, tables for orders and products would maintain strict schemas to track orders accurately and manage product inventories effectively.

On the other hand, schemaless tables offer flexibility for handling unstructured or semi-structured data, which can vary widely. This is particularly useful for storing customer interactions such as emails, chat logs, phone call records, and social media messages, as well as customer feedback and reviews.

These types of data do not fit neatly into a rigid schema due to their diverse formats and content.

By utilising both schemafull and schemaless tables, a CRM system can achieve a balance between maintaining reliable, structured data for critical operations and providing the flexibility to capture and analyse a wide range of customer-related information.

## Conclusion

A schema is the structure of tables in your database. The most important consideration when deciding whether to use a schemafull, schemaless, or both approaches is how flexible you want the content to be. Choose schemafull tables for structured data with strict validation requirements. Use schemaless tables for flexible, dynamic data that might evolve.

Combine both approaches to use the strengths of each based on your application's needs.

For performance, you can use the `DEFINE INDEX` statement to create indexes on either form of table. This can also optimise storage and retrieval.

Learn more about setting up a schema in the [`DEFINE` statement documentation](/docs/reference/query-language/statements/define/overview.md).

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/gen-ai-chatbot

# Build a GenAI chatbot with Graph RAG

In this guide, you'll learn how to use LangChain Python components, Ollama, and SurrealDB to make a GenAI chatbot.

This tutorial builds a GenAI chatbot from LangChain Python components, Ollama and SurrealDB, retrieving context through the graph rather than through vector similarity alone. It opens with how traditional RAG works, so that the difference is clear before any code.

## What is traditional RAG

Retrieval-Augmented Generation (RAG) is an AI technique that enhances the capabilities of large language models (LLMs) by allowing them to retrieve relevant information from a knowledge base *before* generating a response. It uses vector similarity search to find the most relevant document chunks, which are then provided as additional context to the LLM, enabling the LLM to produce more accurate and grounded responses.

## What is Graph RAG

Graph RAG uses the structured, interconnected nature of knowledge graphs to give the LLM a more contextualised understanding of the information, leading to more accurate, coherent, and less “hallucinated” responses.

**Python**

For the Python example we are going to use LangChain Python components, Ollama, and SurrealDB.

## Flow overview

0. Ingest data (categorised health symptoms and common treatments)
1. Ask the user about their symptoms
2. Find relevant documents in the DB by similarity
3. Execute vector search in DB
4. Invoke chain to find related common treatments
5. Chain asks the LLM to generate graph query
6. Chain executes the query
7. Query graph
8. Chain asks the LLM to summarise the results and generate an answer
9. Respond to the user

<img src="~/assets/img/tutorials/gen-ai-chatbot-graphrag-flow-light.png" darkSrc="~/assets/img/tutorials/gen-ai-chatbot-graphrag-flow.png" alt="Communication diagram for the Graph RAG solution and the flow listed above" />

<br/>

## First step: ingest the data

For this example we have a YAML file with categorised symptoms and their common treatments.

We want to store this in a vector store so we can query it using vector similarity search.

We also want to represent the data relations in a graph store, so we can run graph queries to retrieve those relationships (e.g. treatments related to symptoms).

```yaml
- category: General Symptoms
  symptoms:
    - name: Fever
      description: Elevated body temperature, usually above 100.4°F (38°C).
      medical_practice: General Practice, Internal Medicine, Pediatrics
      possible_treatments:
        - Antipyretics (e.g., ibuprofen, acetaminophen)
        - Rest
        - Hydration
        - Treating the underlying cause

```

<br />

Let’s instantiate the following LangChain python components:

- **Vector Store** ([SurrealDBVectorStore](https://python.langchain.com/docs/integrations/vectorstores/surrealdb/))
- **Graph Store** (SurrealDBGraph)
- **Embeddings** ([OllamaEmbeddings](https://python.langchain.com/docs/integrations/text_embedding/ollama/), or any other model from the [Embedding models](https://python.langchain.com/docs/integrations/text_embedding/))

...and create a SurrealDB connection:

```python
# DB connection
conn = Surreal(url)
conn.signin({"username": user, "password": password})
conn.use(ns, db)

# Vector Store
vector_store = SurrealDBVectorStore(
    OllamaEmbeddings(model="llama3.2"),
    conn
)

# Graph Store
graph_store = SurrealDBGraph(conn)
```

Note that the `SurrealDBVectorStore` is instantiated with `OllamaEmbeddings`. This LLM model will be used when inserting documents to generate their embeddings vector.

## Populating the vector store

With the vector store instantiated, we are now ready to populate it.

```python
# Parsing the YAML into a Symptoms dataclass
with open("./symptoms.yaml", "r") as f:
    symptoms = yaml.safe_load(f)
    assert isinstance(symptoms, list), "failed to load symptoms"
    for category in symptoms:
        parsed_category = Symptoms(category["category"], category["symptoms"])
        for symptom in parsed_category.symptoms:
            parsed_symptoms.append(symptom)
            symptom_descriptions.append(
                Document(
                    page_content=symptom.description.strip(),
                    metadata=asdict(symptom),
                )
            )

# This calculates the embeddings and inserts the documents into the DB
vector_store.add_documents(symptom_descriptions)

```

<br />

## Stitching the graph together

```python
# Find nodes and edges (Treatment -> Treats -> Symptom)
for idx, category_doc in enumerate(symptom_descriptions):
    # Nodes
    treatment_nodes = {}
    symptom = parsed_symptoms[idx]
    symptom_node = Node(id=symptom.name, type="Symptom", properties=asdict(symptom))
    for x in symptom.possible_treatments:
        treatment_nodes[x] = Node(id=x, type="Treatment", properties={"name": x})
    nodes = list(treatment_nodes.values())
    nodes.append(symptom_node)

    # Edges
    relationships = [
        Relationship(source=treatment_nodes[x], target=symptom_node, type="Treats")
        for x in symptom.possible_treatments
    ]
    graph_documents.append(
        GraphDocument(nodes=nodes, relationships=relationships, source=category_doc)
    )

# Store the graph
graph_store.add_graph_documents(graph_documents, include_source=True)

```

<br />

## Data ready, let’s chat

LangChain provides different [chat models](https://python.langchain.com/docs/integrations/chat/). We are going to use `ChatOllama` with `llama3.2` to generate a graph query and to explain the result in natural language.

```python
chat_model = ChatOllama(model="llama3.2", temperature=0)
```

To generate the graph query based on the user’s prompt, we need to instantiate a QA (Questioning and Answering) Chain component. In this case we are using `SurrealDBGraphQAChain`.

But before querying the graph, we need to find the symptoms in our vector store by doing a similarity search based on the user’s prompt.

```python
query = click.prompt(
    click.style("\\nWhat are your symptoms?", fg="green"), type=str
)

# -- Find relevant docs
docs = vector_search(query, vector_store, k=3)
symptoms = get_document_names(docs)

# -- Query the graph
chain = SurrealDBGraphQAChain.from_llm(
    chat_model,
    graph=graph_store,
    verbose=verbose,
    query_logger=query_logger,
)
ask(f"what medical practices can help with {symptoms}", chain)
ask(f"what treatments can help with {symptoms}", chain)
```

<br />

## Running

Clone the [repository](https://github.com/surrealdb/langchain-surrealdb) and follow the instructions in the README of the [graph example](https://github.com/surrealdb/langchain-surrealdb/tree/main/examples/graph).

Running the program will look like this:

```text
What are your symptoms?: i have a runny nose and itchy eyes
```

<br />

The script tries marginal relevance and similarity searches in the vector store to compare the results, which helps to choose the right one for your specific use case.

```text
max_marginal_relevance_search:
- Stuffy nose due to inflamed nasal passages or a dripping nose with mucus discharge.
- An uncomfortable sensation that makes you want to scratch, often without visible skin changes.
- Feeling lightheaded, unsteady, or experiencing a sensation that the room is spinning.

similarity_search_with_score
- [40%] Stuffy nose due to inflamed nasal passages or a dripping nose with mucus discharge.
- [33%] Feeling lightheaded, unsteady, or experiencing a sensation that the room is spinning.
- [32%] Pain, irritation, or scratchiness in the throat, often made worse by swallowing.
```

<br />

Then, the QA chain will generate and run a graph query behind the scenes, and generate the responses.

This script is asking our AI two questions based on the user's symptoms:

- Question: what medical practices can help with Nasal Congestion/Runny Nose, Dizziness/Vertigo, Sore Throat
- Question: what treatments can help with Nasal Congestion/Runny Nose, Dizziness/Vertigo, Sore Throat

For the first question the QA chain component generated this graph query:

```surql
SELECT <-relation_Attends<-graph_Practice AS practice
FROM graph_Symptom
WHERE name IN
    ["Nasal Congestion/Runny Nose", "Dizziness/Vertigo", "Sore Throat"];
```

<br />

The result of this query - a Python list of dictionaries containing the medical practice names - is fed to the LLM to generate a nice human-readable answer:

```text
Here is a summary of the medical practices that can help with Nasal
Congestion/Runny Nose, Dizziness/Vertigo, and Sore Throat:

Several medical practices may be beneficial for individuals experiencing
symptoms such as Nasal Congestion/Runny Nose, Dizziness/Vertigo, and Sore
Throat. These include Neurology, ENT (Otolaryngology), General Practice, and
Allergy & Immunology.

Neurology specialists can provide guidance on managing conditions that affect
the nervous system, which may be related to dizziness or vertigo.
ENT (Otolaryngology) specialists focus on ear, nose, and throat issues, making
them a good fit for addressing nasal congestion and runny nose symptoms.
General Practice physicians offer comprehensive care for various health
concerns, including those affecting the respiratory system.

Allergy & Immunology specialists can help diagnose and treat allergies that may
contribute to Nasal Congestion/Runny Nose, as well as provide immunological
support for overall health.
```

<br />

The query for the second question (What treatments can help with Nasal Congestion/Runny Nose, Dizziness/Vertigo, Sore Throat), looks like this:

```surql
SELECT <-relation_Treats<-graph_Treatment AS treatment
FROM graph_Symptom
WHERE name IN
    ["Nasal Congestion/Runny Nose", "Dizziness/Vertigo", "Sore Throat"];
```

<br />

The LLM will then produce the following output:

```text
Here is a summary of the treatments that can help with Nasal Congestion/Runny
Nose, Dizziness/Vertigo, and Sore Throat:


The following treatments have been found to be effective in
alleviating symptoms:

- Vestibular rehabilitation
- Hydration
- Medications to reduce nausea or dizziness
- Antihistamines (for allergies)
- Decongestants (oral or nasal sprays)
- Saline nasal rinses
- Humidifiers
- Throat lozenges/sprays
- Treating underlying cause (e.g., cold, allergies)
- Pain relievers (e.g., acetaminophen, ibuprofen)
- Warm salt water gargles
```

<br />

**Rust**

A medical chatbot using SurrealDB and LangChain can be made in Rust thanks to a crate called [langchain_rust](https://docs.rs/langchain-rust/latest/langchain_rust/) which as of last year [includes support for SurrealDB as a vector store](https://github.com/Abraxas-365/langchain-rust/pull/32). This implementation doesn't (yet!) include [graph queries](/blog/make-a-genai-chatbot-using-graphrag-with-surrealdb-langchain#stitching-the-graph-together), but we can still use classic vector search to find recommendations for treatment for a patient.

To start off, use a command like `cargo new medical_bot` to create a new Cargo project, go into the project directory and add the following under `[dependencies]`.

```toml
anyhow = "1.0.98"
langchain-rust = { version = "4.6.0", features = ["surrealdb", "mistralai"] }
serde = "1.0.219"
serde_json = "1.0.140"
serde_yaml = "0.9.34"
surrealdb = { version = "3.0.4", features = ["kv-mem"] }
tokio = "1.45.0"
```

The `langchain-rust` crate comes with OpenAI as a default, and includes a [large number of features](https://docs.rs/crate/langchain-rust/latest/features). We will add `mistralai` to show how easy it is to switch from one platform to another with only about two lines of different code.

The original post assumes that we have a big YAML document with a number of symptoms along with their possible treatments, which is what the `serde_yaml` dependency will let us work with.

```yaml
- category: General Symptoms
  symptoms:
    - name: Fever
      description: Elevated body temperature, usually above 100.4°F (38°C).
      medical_practice: General Practice, Internal Medicine, Pediatrics
      possible_treatments:
        - Antipyretics (e.g., ibuprofen, acetaminophen)
        - Rest
        - Hydration
        - Treating the underlying cause
```

To keep the logic simple, we will take only the `description` of each symptom and its `possible_treatments`, giving us two structs that look like this.

```rust
#[derive(Debug, Deserialize)]
pub struct SymptomCategory {
    pub symptoms: Vec<Symptom>
}

#[derive(Debug, Deserialize)]
pub struct Symptom {
    pub description: String,
    pub possible_treatments: Vec<String>,
}
```

Then for each symptom, we will look through the possible treatments to create a document for each with text that looks like the following:

* 'Elevated body temperature, usually above 100.4°F (38°C).' can be treated by 'Antipyretics (e.g., ibuprofen, acetaminophen)'
* 'Elevated body temperature, usually above 100.4°F (38°C).' can be treated by 'Rest'
* 'Elevated body temperature, usually above 100.4°F (38°C).' can be treated by 'Hydration'
* 'Elevated body temperature, usually above 100.4°F (38°C).' can be treated by 'Treating the underlying cause'

This needs to be turned into a [`Document`](https://docs.rs/langchain-rust/latest/langchain_rust/schemas/document/struct.Document.html) struct on the `langchain-rust` side, which looks like this.

```rust
pub struct Document {
    pub page_content: String,
    pub metadata: HashMap<String, Value>,
    pub score: f64,
}
```

The way to create a `Document` is via [`Document::new()`](https://docs.rs/langchain-rust/latest/langchain_rust/schemas/document/struct.Document.html#method.new) which takes a `String` for the `page_content`, followed by an optional `HashMap` for any metadata. The `score` will be 0.0 when inserting and is only used later on when a similarity search is performed to return a `Document`.

For the `metadata`, we will add the other possible treatments so that any user will be able to first see a recommended treatment for a symptom, followed by all possible treatments for reference.

The `Value` part of the `Document` struct is a [`serde_json`](https://docs.rs/serde_json/latest/serde_json/enum.Value.html) `Value`, which is why we have `serde_json` inside our `Cargo.toml` as well.

All in all, the logic to grab the `YAML` file and turn it into a `Vec` of `Document`s looks like this.

```rust
fn get_docs() -> Result<Vec<Document>, Error> {
    let yaml_str = std::fs::read_to_string("symptoms.yaml")?;
    let categories: Vec<SymptomCategory> = serde_yaml::from_str(&yaml_str)?;

    let symptoms = categories
        .into_iter()
        .flat_map(|cat| cat.symptoms)
        .collect::<Vec<Symptom>>();
    Ok(symptoms
        .into_iter()
        .flat_map(|symptom| {
            let metadata = HashMap::from([
                (
                    "possible treatments".to_string(),
                    Value::from(symptom.possible_treatments.clone()),
                )
            ]);
            symptom
                .possible_treatments
                .into_iter()
                .map(|treat| {
                    Document::new(format!("'{}' can be treated by '{treat}'.", symptom.description.clone()))
                        .with_metadata(metadata.clone())
                })
                .collect::<Vec<Document>>()
        })
        .collect::<Vec<Document>>())
}
```

With this taken care of, it's time to do some setup inside `main()`. First we need to start running the database, which can be run in memory or via some other path such as an address to a [Surreal Cloud](/cloud) or a [locally running instance](/docs/reference/cli/db/commands/start.md).

```rust
let database_url = std::env::var("DATABASE_URL").unwrap_or("memory".to_string());

let db = surrealdb::engine::any::connect(database_url).await?;
db.query("DEFINE NAMESPACE test; USE NAMESPACE test; DEFINE DATABASE test;")
    .await?;

//  Uncomment the following lines to authenticate if necessary
//  .user(surrealdb::opt::auth::Root {
//      username: "root".into(),
//      password: "secret".into(),
//  });

db.use_ns("main").await?;
db.use_db("main").await?;
```

The next step is to initialise an embedder from the `langchain-rust` crate. Here we have the choice of an `OpenAiEmbedder` or `MistralAIEmbedder` thanks to the added feature flag.

After that comes a `StoreBuilder` struct used to initiate a SurrealDB `Store`, which takes an embedder, a database, and a number of dimensions - 1536 in this case for OpenAI. If using Mistral, the dimensions would be 1024.

Note that we are wrapping this in an `Arc` so that the store can start adding the documents on startup inside a separate task without making the user wait to see any CLI output.

At the very end is an `.initialize()` method which defines some tables and fields which will be used when doing similarity searches.

```rust
// Initialize Embedder
let embedder = OpenAiEmbedder::default();
// Embedding size is 1024 in this case
// let embedder = MistralAIEmbedder::try_new()?;

// Initialize the SurrealDB Vector Store
let store = Arc::new(
    StoreBuilder::new()
        .embedder(embedder)
        .db(db)
        .vector_dimensions(1536)
        .build()
        .await
        .map_err(|e| anyhow!(e.to_string()))?,
);

store
    .initialize()
    .await
    .map_err(|e| anyhow!(e.to_string()))?;
```

Then we will clone the `Arc` to allow the `store` to be passed into a new blocking task to add the `Vec<Document>` returned by our `get_docs()` function. Inside this is a method called `add_documents()` which is a built-in method from the `rust-langchain` crate.

```rust
let arced = Arc::clone(&store);

tokio::task::spawn_blocking(move || {
    let docs = get_docs()?;
    Handle::current()
        .block_on(arced.add_documents(&docs, &VecStoreOptions::default()))
        .map_err(|e| anyhow::anyhow!("{e}"))?;
    Ok::<(), Error>(())
});
```

While the store adds these documents in its own task, we will start a simple CLI that asks the user for a query, and then passes this into the built-in `.similarity_search()` method. This method allows us to specify the number of documents to return and a minimum similarity score, for which we will use 2 and 0.6.

The rest of the code just involves setting up a simple loop to handle user output, along with the results of the output of the `.similarity_search()` method.

```rust
    loop {
        // Ask for user input
        print!("Query> ");
        stdout().flush()?;
        let mut query = String::new();
        stdin().read_line(&mut query)?;

        let results = store
            .similarity_search(
                &query,
                2,
                &VecStoreOptions::default().with_score_threshold(0.6),
            )
            .await
            .map_err(|e| anyhow!(e.to_string()))?;

        if results.is_empty() {
            println!("No results found.");
        } else {
            println!("Possible symptoms:");
            results.iter().for_each(|r| {
                println!("{}\n All possible treatments: ", r.page_content);
                if let Some(Value::Array(array)) = r.metadata.get("possible treatments") {
                    for val in array {
                        if let Value::String(s) = val {
                            println!("  {s}");
                        }
                    }
                };
                println!();
            });
        };
    }
```

As the output shows, our bot is capable of returning meaningful results despite only having access to data from 236 lines of YAML!

```text
Query> I've been exercising a lot outside and it's really hot.
Possible symptoms:
'Elevated body temperature, usually above 100.4°F (38°C).' can be treated by 'Hydration'.
 All possible treatments: 
  Antipyretics (e.g., ibuprofen, acetaminophen)
  Rest
  Hydration
  Treating the underlying cause

'Elevated body temperature, usually above 100.4°F (38°C).' can be treated by 'Rest'.
 All possible treatments: 
  Antipyretics (e.g., ibuprofen, acetaminophen)
  Rest
  Hydration
  Treating the underlying cause
```

```text
Query> I get dizzy sometimes
Possible symptoms:
'Feeling lightheaded, unsteady, or experiencing a sensation that the room is spinning.' can be treated by 'Medications to reduce nausea or dizziness'.
 All possible treatments: 
  Addressing underlying cause (e.g., inner ear issues, low blood pressure)
  Vestibular rehabilitation
  Medications to reduce nausea or dizziness
  Hydration

'Feeling lightheaded, unsteady, or experiencing a sensation that the room is spinning.' can be treated by 'Addressing underlying cause (e.g., inner ear issues, low blood pressure)'.
 All possible treatments: 
  Addressing underlying cause (e.g., inner ear issues, low blood pressure)
  Vestibular rehabilitation
  Medications to reduce nausea or dizziness
  Hydration
```

```text
Query> What should I do in life?
Possible symptoms:
'Feeling unusually drained, lacking energy, or experiencing persistent exhaustion.' can be treated by 'Lifestyle modifications (diet, exercise)'.
 All possible treatments: 
  Rest and adequate sleep
  Lifestyle modifications (diet, exercise)
  Addressing underlying medical conditions (e.g., anemia, thyroid disorders)
  Stress management

'Feeling unusually drained, lacking energy, or experiencing persistent exhaustion.' can be treated by 'Stress management'.
 All possible treatments: 
  Rest and adequate sleep
  Lifestyle modifications (diet, exercise)
  Addressing underlying medical conditions (e.g., anemia, thyroid disorders)
  Stress management
```

To try it yourself, save [`symptoms.yaml` from the langchain-surrealdb examples](https://github.com/surrealdb/langchain-surrealdb/blob/main/examples/graph/symptoms.yaml) to the filename `symptoms.yaml` and then copy the following code into your cargo project, then set the env var `OPENAI_API_KEY` or `MISTRAL_API_KEY` along with `cargo run`.

You can also give a crate called [archiver](https://crates.io/crates/archiver) a try, which has its own command-line interface to use SurrealDB with Ollama via the same crate we used in this post.

```rust
// To run this example execute: `cargo run` in the folder. Be sure to have an OpenAPI key
// set to the OPENAI_API_KEY env var
// or MISTRAL_API_KEY if using Mistral

use anyhow::{Error, anyhow};
use langchain_rust::{
    embedding::{MistralAIEmbedder, openai::openai_embedder::OpenAiEmbedder},
    schemas::Document,
    vectorstore::{VecStoreOptions, VectorStore, surrealdb::StoreBuilder},
};
use serde::Deserialize;
use serde_json::Value;
use std::{
    collections::HashMap,
    io::{Write, stdin, stdout},
    sync::Arc,
};
use tokio::runtime::Handle;

#[derive(Debug, Deserialize)]
pub struct SymptomCategory {
    pub category: String,
    pub symptoms: Vec<Symptom>,
}

#[derive(Debug, Deserialize)]
pub struct Symptom {
    pub name: String,
    pub description: String,
    pub possible_treatments: Vec<String>,
}

fn get_docs() -> Result<Vec<Document>, Error> {
    let yaml_str = std::fs::read_to_string("symptoms.yaml")?;
    let categories: Vec<SymptomCategory> = serde_yaml::from_str(&yaml_str)?;

    let symptoms = categories
        .into_iter()
        .flat_map(|cat| cat.symptoms)
        .collect::<Vec<Symptom>>();
    Ok(symptoms
        .into_iter()
        .flat_map(|symptom| {
            let metadata = HashMap::from([(
                "possible treatments".to_string(),
                Value::from(symptom.possible_treatments.clone()),
            )]);
            symptom
                .possible_treatments
                .into_iter()
                .map(|treat| {
                    Document::new(format!(
                        "'{}' can be treated by '{treat}'.",
                        symptom.description.clone()
                    ))
                    .with_metadata(metadata.clone())
                })
                .collect::<Vec<Document>>()
        })
        .collect::<Vec<Document>>())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let database_url = std::env::var("DATABASE_URL").unwrap_or("memory".to_string());

    let db = surrealdb::engine::any::connect(database_url).await?;
    db.query("DEFINE NAMESPACE test; USE NAMESPACE test; DEFINE DATABASE test;")
        .await?;

    //  Uncomment the following lines to authenticate if necessary
    //  .user(surrealdb::opt::auth::Root {
    //      username: "root".into(),
    //      password: "secret".into(),
    //  });

    db.use_ns("main").await?;
    db.use_db("main").await?;

    // Initialize Embedder
    let embedder = OpenAiEmbedder::default();
    // Embedding size is 1024 in this case
    // let embedder = MistralAIEmbedder::try_new()?;

    // Initialize the SurrealDB Vector Store
    let store = Arc::new(
        StoreBuilder::new()
            .embedder(embedder)
            .db(db)
            .vector_dimensions(1536)
            .build()
            .await
            .map_err(|e| anyhow!(e.to_string()))?,
    );

    // Intialize the tables in the database. This is required to be done only once.
    store
        .initialize()
        .await
        .map_err(|e| anyhow!(e.to_string()))?;

    let arced = Arc::clone(&store);

    tokio::task::spawn_blocking(move || {
        let docs = get_docs()?;
        Handle::current()
            .block_on(arced.add_documents(&docs, &VecStoreOptions::default()))
            .map_err(|e| anyhow::anyhow!("{e}"))?;
        Ok::<(), Error>(())
    });

    loop {
        // Ask for user input
        print!("Query> ");
        stdout().flush()?;
        let mut query = String::new();
        stdin().read_line(&mut query)?;

        let results = store
            .similarity_search(
                &query,
                2,
                &VecStoreOptions::default().with_score_threshold(0.6),
            )
            .await
            .map_err(|e| anyhow!(e.to_string()))?;

        if results.is_empty() {
            println!("No results found.");
        } else {
            println!("Possible symptoms:");
            results.iter().for_each(|r| {
                println!("{}\n All possible treatments: ", r.page_content);
                if let Some(Value::Array(array)) = r.metadata.get("possible treatments") {
                    for val in array {
                        if let Value::String(s) = val {
                            println!("  {s}");
                        }
                    }
                };
                println!();
            });
        };
    }
    Ok(())
}
```

## Ready to build?

Find all the code in the [repository examples](https://github.com/surrealdb/langchain-surrealdb/tree/main/examples/graph).

Get started for free with [Surreal Cloud](https://studio.surrealdb.com/signin).

Any questions or thoughts about this or Graph RAG using SurrealDB? [Join our Discord](https://discord.gg/surrealdb) - `#all-ai` is a good place to start for this topic, and `#help` or `#general` work for anything else.

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/github-actions

# Use SurrealDB in GitHub Actions

This guide will show you how to set up and use the official GitHub Action for SurrealDB in your CI/CD pipeline.

This guide will show you how to set up and use the official GitHub Action for SurrealDB in your CI/CD pipeline.

## Step 1: Create a new GitHub workflow file

Create a new YAML file in your repository's `.github/workflows` directory. You can name the file `surrealdb-ci.yml`.

```yaml
name: SurrealDB CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - name: Git checkout
      uses: actions/checkout@v4
    - name: Start SurrealDB
      uses: surrealdb/setup-surreal@v3
      with:
        surrealdb_version: latest
        surrealdb_port: 8000
        surrealdb_username: root
        surrealdb_password: secret
        surrealdb_auth: false
        surrealdb_strict: false
        surrealdb_log: info
        surrealdb_additional_args: --allow-all
        surrealdb_retry_count: 30
```

## Step 2: Customise workflow arguments

The official SurrealDB GitHub Action accepts several arguments to configure the SurrealDB setup. The table below lists the available arguments and their defaults:

<table>
    <thead>
        <tr>
            <th scope="col">Argument</th>
            <th scope="col">Description</th>
            <th scope="col">Default</th>
            <th scope="col">Value</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_version
            </td>
            <td scope="row" data-label="Description">
                SurrealDB version to use
            </td>
            <td scope="row" data-label="Default">
                latest
            </td>
            <td scope="row" data-label="Value">
                latest, nightly, beta, alpha, v1.x.x, v2.x.x, v3.x.x
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_datastore
            </td>
            <td scope="row" data-label="Description">
                Datastore to start SurrealDB with
            </td>
            <td scope="row" data-label="Default">
                memory
            </td>
            <td scope="row" data-label="Value">
                Any valid datastore path, for example rocksdb:data
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_port
            </td>
            <td scope="row" data-label="Description">
                Port to run SurrealDB on
            </td>
            <td scope="row" data-label="Default">
                8000
            </td>
            <td scope="row" data-label="Value">
                Valid number from 0 to 65535
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_username
            </td>
            <td scope="row" data-label="Description">
                Username to use for SurrealDB
            </td>
            <td scope="row" data-label="Default">
                root
            </td>
            <td scope="row" data-label="Value">
                Customisable by the user
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_password
            </td>
            <td scope="row" data-label="Description">
                Password to use for SurrealDB
            </td>
            <td scope="row" data-label="Default">
                root
            </td>
            <td scope="row" data-label="Value">
                Customisable by the user
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_auth
            </td>
            <td scope="row" data-label="Description">
                Enable authentication
            </td>
            <td scope="row" data-label="Default">
                false
            </td>
            <td scope="row" data-label="Value">
                true, false
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_strict
            </td>
            <td scope="row" data-label="Description">
                Enable strict mode
            </td>
            <td scope="row" data-label="Default">
                false
            </td>
            <td scope="row" data-label="Value">
                true, false
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_log
            </td>
            <td scope="row" data-label="Description">
                Enable logs
            </td>
            <td scope="row" data-label="Default">
                trace
            </td>
            <td scope="row" data-label="Value">
                none, full, error, warn, info, debug, trace
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_import_file
            </td>
            <td scope="row" data-label="Description">
                SurrealQL file to import on startup
            </td>
            <td scope="row" data-label="Default">

            </td>
            <td scope="row" data-label="Value">
                Path to a .surql file, requires SurrealDB v3.0.0 or later
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_additional_args
            </td>
            <td scope="row" data-label="Description">
                Additional arguments for SurrealDB
            </td>
            <td scope="row" data-label="Default">

            </td>
            <td scope="row" data-label="Value">
                <a href="/docs/reference/cli/db/commands/start.md" target="_blank" rel="noopener noreferrer" title="Any valid SurrealDB CLI arguments">Any valid SurrealDB CLI arguments</a>
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Argument">
                surrealdb_retry_count
            </td>
            <td scope="row" data-label="Description">
                Seconds to wait for SurrealDB to become ready
            </td>
            <td scope="row" data-label="Default">
                30
            </td>
            <td scope="row" data-label="Value">
                Any valid integer
            </td>
        </tr>
    </tbody>
</table>

The file passed to `surrealdb_import_file` selects its own namespace and database, so it should begin with a `USE NS ... DB ...;` statement.

### Workflow outputs

The action exposes the details of the instance it started, so that later steps do not have to reconstruct them:

<table>
    <thead>
        <tr>
            <th scope="col">Output</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Output">
                endpoint
            </td>
            <td scope="row" data-label="Description">
                The HTTP endpoint the SurrealDB instance is listening on
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Output">
                version
            </td>
            <td scope="row" data-label="Description">
                The exact version of SurrealDB that was installed
            </td>
        </tr>
    </tbody>
</table>

### Example configuration

Here is an example configuration that sets specific values for each argument:

```yaml
name: SurrealDB CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - name: Git checkout
      uses: actions/checkout@v4
    - name: Start SurrealDB
      id: surrealdb
      uses: surrealdb/setup-surreal@v3
      with:
        surrealdb_version: latest
        surrealdb_port: 8000
        surrealdb_username: root
        surrealdb_password: secret
        surrealdb_auth: false
        surrealdb_strict: false
        surrealdb_log: info
        surrealdb_additional_args: --allow-all
        surrealdb_retry_count: 30
    - name: Run the tests
      run: ./run-tests.sh
      env:
        SURREALDB_ENDPOINT: ${{ steps.surrealdb.outputs.endpoint }}
```

### Tips for customisation

1. **Version Control**: Use specific versions to avoid unexpected changes. Example: surrealdb_version: v3.2.4.
2. **Security**: Always use strong passwords for surrealdb_password and avoid using default credentials in production.
3. **Logs**: Set an appropriate log level based on your needs. For debugging, use debug or trace.
4. **Additional Arguments**: Use surrealdb_additional_args to pass any additional CLI arguments required by your setup.

## Step 3: Commit and push

After creating and customising your workflow file, commit and push it to your repository:
```sh
git add .github/workflows/surrealdb-ci.yml
git commit -m "Add SurrealDB CI workflow"
git push origin main
```

## Step 4: Verify workflow execution

Go to your repository on GitHub and navigate to the "Actions" tab. You should see your workflow running when you push changes or create a pull request. Check the logs to verify that SurrealDB is starting up correctly and that all steps are executed successfully.

## Conclusion

Using the official GitHub Action for SurrealDB simplifies the process of setting up and running SurrealDB in your CI/CD pipeline. Customise the workflow as per your project requirements, and ensure you follow best practices for security and version control.

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/how-to-build-a-knowledge-graph-for-ai

# Build a knowledge graph for AI

Build a knowledge graph for AI agents and RAG: graphs, embeddings and SurrealQL retrieval patterns in one tutorial.

Welcome to the tutorial on building knowledge graphs for AI. This document is for you if:

- you are an engineer working in gen AI
- you want to understand why knowledge graphs are relevant for AI agents

## Introduction

Let’s start by placing knowledge graphs on the map, by showing a modern multi-agent RAG architecture. In this example, assume each agent has a different role in the process to answer a user's prompt. In order to make this happen, each agent comes equipped with its own tool or tools like web search, MCP servers, and more. A knowledge graph in this case is just another toolset for the agents.

<img src="~/assets/img/tutorials/agentic-rag-light.png" darkSrc="~/assets/img/tutorials/agentic-rag.png" alt="Agentic RAG" />

While the word “tool” is being used very casually here, [tool calling](https://martinfowler.com/articles/function-call-LLM.html) is a very important concept in our context. Most modern LLM models (like Claude Sonnet 4.5, Gemini 3 Flash Preview, DeepSeek V3.2, and [many more listed on OpenRouter](https://openrouter.ai/collections/tool-calling-models)) are capable of using tools as part of their process before generating an answer. These tools can be built in the model (like web search in Claude models) or provided by you (like the example below).

This Python code shows how you can provide your agent with a “retrieval” tool from your knowledge graph. The *docstring* in this function gives to the LLM the information it needs to know when and how to call this function.

```python
embedder = Embedder('openai:text-embedding-3-small')
agent = Agent('openai:gpt-5.2')

@agent.tool
async def retrieve(context: RunContext[Deps], search_query: str) -> str:
    """Retrieve documents from the knowledge graph based on a search query.

    Args:
        search_query: The search query.
    """
    with logfire.span("KG search for {search_query=}", search_query=search_query):
        # -- Build SurrealQL query
        surql = generate_surql(search_query)
        
        # -- Embeddings
        result = await embedder.embed_query(search_query)
        embedding = result.embeddings[0]
        
        # -- Query
        results = query(
            context.deps.db,
            surql,
            {"embedding": list(embedding)},
            SearchResult,
        )

    results = "\n\n".join(
        f"# Document name: {x.doc.filename}\n{'\n\n'.join(str(y.content) for y in x.chunks)}\n"
        for x in results
    )
    
    return results
```

Before diving into how to build a knowledge graph, let’s define what they are.

## What is a knowledge graph?

> In knowledge representation and reasoning, a **knowledge graph** is a knowledge base that uses a graph-structured data model or topology to represent and operate on data. Knowledge graphs are often used to store interlinked descriptions of entities - objects, events, situations or abstract concepts - while also encoding the free-form semantics or relationships underlying these entities.

[Source: Wikipedia](https://en.wikipedia.org/w/index.php?title=Knowledge_graph&oldid=1334019348)

The image below presents two specimens of a knowledge graph. The first one is a very structured and predictable one, whose **nodes and edges are explicit** in the original data. The second one is its opposite: a more free-form graph, with some **entities inferred by an LLM** out of the corpus.

If you take a close look you'll see that's why the first one includes only a single prepared graph edge called `INCLUDES`, while the second has more than one such as `MENTIONED_IN` and `PUBLISHED_IN` - these were generated by the LLM which concluded it made sense to use these to describe the relation between entities.

<img src="~/assets/img/tutorials/knowledge-graph-types-light.png" darkSrc="~/assets/img/tutorials/knowledge-graph-types.png" alt="Types of knowledge graphs" />

But… 🤔

## When do AI agents need a knowledge graph?

Because LLMs by themselves are brilliant storytellers but with a fuzzy memory. In turn, AI agents are designed to perform tasks and make decisions, requiring the accuracy only a structured graph can provide. An LLM with a knowledge graph is like a storyteller with a highly-organised, cross-referenced encyclopedia.

**Example to show some of the benefits:**

*“Summarise the reviews of this month’s most popular product in our store”*

With a prompt like that, and a knowledge graph like in the example before, your agent would be able to deterministically retrieve the list of the reviews for the best selling products. Let's see how easy this is to do with a SurrealQL query.

```surql
-- Get the ID best product based on its count
LET $best = (
    SELECT id, count(<-product_in_order) AS count
    FROM ONLY product
    ORDER BY count DESC
    LIMIT 1
).id;

-- Then return the reviews where this product shows up
SELECT *, $best AS product
FROM review
WHERE $best IN ->review_for_product->product;
```

The output of this last query will look like this.

```surql
[
	{
		id: review:1,
		product: product:detector,
		rating: 5,
		text: 'Excellent!'
	},
	{
		id: review:2,
		product: product:detector,
		rating: 4,
		text: 'Pretty good.'
	}
]
```

Want to give it a try yourself? Head on over to [SurrealDB Studio](https://studio.surrealdb.com/), go into the sandbox and run the following statements to set up the schema and seed data before running the query we just saw. To keep that data, use **Deploy to Cloud** in SurrealDB Studio to create a free SurrealDB Cloud instance, then connect to it and run the same statements there.

```surql
-- Products
DEFINE TABLE product SCHEMAFULL;
DEFINE FIELD name ON product TYPE string;

-- Orders
DEFINE TABLE order SCHEMAFULL;
DEFINE FIELD created_at ON order TYPE datetime;

-- Reviews
DEFINE TABLE review SCHEMAFULL;
DEFINE FIELD rating ON review TYPE int;
DEFINE FIELD text ON review TYPE string;

-- Edge: order -> product
DEFINE TABLE product_in_order SCHEMAFULL TYPE RELATION;

-- Edge: review -> product
DEFINE TABLE review_for_product SCHEMAFULL TYPE RELATION;

CREATE product:detector SET name = "Dragon detector";
CREATE product:repellent SET name = "Repellent";

-- Orders
CREATE order:1 SET created_at = time::now();
CREATE order:2 SET created_at = time::now();
CREATE order:3 SET created_at = time::now();

-- Order edges (Dragon detector sells twice, Repellent once)
RELATE order:1->product_in_order->product:detector;
RELATE order:2->product_in_order->product:detector;
RELATE order:3->product_in_order->product:repellent;

-- Reviews
CREATE review:1 SET rating = 5, text = "Excellent!";
CREATE review:2 SET rating = 4, text = "Pretty good.";

-- Review edges (both for Dragon detector)
RELATE review:1->review_for_product->product:detector;
RELATE review:2->review_for_product->product:detector;
```

And to finish up this example, here is a bonus query that lets you see all incoming graph edges to a table. The ? here is used as a wildcard to match anything, which in this case means all of the `product_in_order` and `review_for_product` edges in between the `order` and `review` tables.

```surql
-- Query full graph
SELECT 
	*,
	<-?<-? AS all_edges
FROM product;
```

We can use this data to explain the main benefits of a knowledge graph:

- **multi-hop reasoning**: it navigates the graph to one or more edges: `review → review_for_product → product → product_in_order → order`
- **deterministic accuracy**: the query output is backed by hard data, a fact that will add great value to the LLM context.
- **explainability**: if required, besides the plain answer, you get the query that was executed and the structured results. In our example, you get the list of reviews, but also which is the top-selling product, and if you wish, you could include how many items were sold.
- **reduced hallucinations:** because we leveraged our graph relations to exactly know which are the reviews of the best-selling products, the LLM just needs to summarise them. That leaves little room for hallucinations. Compare this to asking the same question with RAG on ingested sales reports instead of a knowledge graph: the data about which is the best selling product may or may not be mentioned in one of those documents, which will get retrieved using semantic search (or at least a chunk of the report), and fingers crossed, that chunk gets a good enough score to be picked up and included into the LLM context.
- **dynamic knowledge:** whenever new orders and reviews are created, the query will pick them up. Because of its structured nature, it’s easier to keep up to date, specially if your “transactional” DB is the same as your knowledge graph DB.

## When is a knowledge graph not necessary?

As a counterexample, let's look at another use case in which a knowledge graph might be overkill:

- Dataset: successful troubleshooting conversations with customers (from support ticket system or e-mail), internal support conversations from company chat (e.g. Slack threads), FAQs and documentations from internal wiki (e.g. Notion, PDFs, etc.)
- Agent job: answer questions like: “Robot firmware is v.1.67 and I can’t get access via SSH”

A vector store populated with the available dataset may provide good references for an LLM to help.

You should only consider adding graph relations to the mix if your vector store is too big, or you have very dense neighbourhoods (e.g. a lot of troubleshooting chats about the same issue, causing [context distraction, confusion, and clash](https://www.blog.langchain.com/context-engineering-for-agents/)). You might also want to trim down the vector space by relating chunks to specific domains (support category, product line, firmware version).

This image illustrates dense neighbourhoods, and how graph relations can help to trim down the vector space, by running queries that read like this: “find reviews in the proximity of `$vector` AND are connected with `->review_for_product->product->product_in_category->dragons`”.

<img src="~/assets/img/tutorials/vector-clusters-graph-relations-light.png" darkSrc="~/assets/img/tutorials/vector-clusters-graph-relations.png" alt="Vector clusters with graph relations" />

## Moving from unstructured data to a knowledge graph

These are the main steps that are required to go from unstructured data to having a knowledge graph for your AI agents. The **Extraction, Transformation, and Loading** steps are commonly referred to as **ETL**.

### 1. Extraction

1.1. Parsing

For each document, parse it and transform it into structured data. It could be a CSV file which is already structured, but unstructured data like a PDF with text, images, and tables can be worked with as well.

1.2. Chunking

We now have “plain” data, which is commonly (but not necessarily) kept in Markdown format. It is very likely that the document may be too long, which is less than ideal for LLMs which have a finite context window (references: https://arxiv.org/abs/2502.05167).

1.3. Embedding

Semantic retrieval is possible because of vector embeddings. You decide what you want to embed. You almost always want to embed chunks, but can also embed content on graph nodes (e.g. to run a semantic search on keywords and from there query other connected nodes).

1.4. Entity and relationship extraction

Entities will become nodes (any concept like `people`, `document`, `product`), and relationship edges (any verb or predicate like `works_at`, `explains`).

Depending on your data, and how structured it is, some of the entities and relationships will be easy to extract because they may be explicit in the data (e.g. Martin → works_at → SurrealDB). Some others will require to be inferred based on some context (e.g. extract from a threads that Martin → knows_about → SurrealQL).

### 2. Transformation

This step in the process is meant to clean your data. Here are some ideas for what you might want to do at this point:

- Deduplication and ontology alignment: for example, you might have Arnold → governor → California along with Schwarzenegger → star of → Predator. `Arnold` and `Schwarzenegger` should get merged.
- Inference and enrichment: there are pros and cons about inferring attributes when generating a knowledge graph. It depends on your specific needs, but be careful: you could burn through tokens generating things that were never used, plus using inferred attributes as context could lead to inaccurate results. Inferred attributes work better as ways to navigate the graph, rather than to provide context to the LLM. As an example, take [LightRAG](https://lightrag.github.io), which demonstrates good results using “inference and enrichment” in a clever way to aid with navigation rather than context.

### 3. Loading

Loading is the last step in the ETL process. Here’s where you connect things with each other in the database, both vector embeddings, and graph relations. Look at the practical [Loading example below](#loading).

Next, you’ll find common practices and practical examples for parsing, chunking, and all the steps mentioned above.

## Practical examples

To finish up today's post, let's look at some examples of how to do the following:

- [Parsing unstructured data](#parsing-unstructured-data)
- [Chunking documents](#chunking-documents)
- [Embedding generation](#embedding-generation)
- [Entity and relationships extraction](#entity-and-relationship-extraction)
- [Loading](#loading)

### Parsing unstructured data

The following example shows how to use Kreuzberg to parse PDFs. You may need to configure it differently, depending on your documents and the types of content. It’s not the same to parse a simple PDF, a PDF with images and tables, a spreadsheet, or websites.

The following example uses a `flow` decorator to register functions for different steps in the ETL process. An orchestrator takes care of calling this function for `document`s that lack a “stamp” in their `chunked` field:

```python
@exe.flow("document", stamp="chunked", priority=2)
def chunk(record: flow.Record, hash: str):  # pyright: ignore[reportUnusedFunction]
    doc = OriginalDocumentTA.validate_python(record)

    chunking_handler(db, doc)

    # set output field so it's not reprocessed again
    _ = db.sync_conn.query(
        "UPDATE $rec SET chunked = $hash", {"rec": doc.id, "hash": hash}
    )
```

The `chunking_handler` is in charge of the actual parsing. The code (simplified from [kreuzberg_converter.py](https://github.com/surrealdb/kaig/blob/main/examples/knowledge-graph/src/knowledge_graph/extraction/providers/kreuzberg/kreuzberg_converter.py)) looks like this:

```python
from kreuzberg import (
    ChunkingConfig,
    ExtractionConfig,
    KeywordAlgorithm,
    KeywordConfig,
    TokenReductionConfig,
    extract_file_sync,
)
from pydantic import TypeAdapter

@dataclass
class ChunkWithMetadata:
    content: str
    metadata: dict[str, Any]

ChunksTA = TypeAdapter(list[ChunkWithMetadata])
	
config = ExtractionConfig(
    use_cache=True,
    # optional keyword extraction
    keywords=KeywordConfig(
        algorithm=KeywordAlgorithm.Yake, max_keywords=10, min_score=0.1
    ),
    chunking=ChunkingConfig(max_chars=1000, max_overlap=100),
    token_reduction=TokenReductionConfig(mode="light"),
    enable_quality_processing=True,
)

result = extract_file_sync(path_or_bytes, config=config)

print(f"Chunks: {result.chunks}")
print(f"Metadata: {result.metadata}")
print(f"Chunks: {len(result.chunks)}")

chunks = ChunksTA.validate_python(result.chunks)
```

A simple trick: hash the chunk and use that as the ID to avoid generating embeddings for chunks that already exist. This applies to mostly every record that gets processed in any way, not only for chunks.

```python
hash = hashlib.md5(chunk_text.encode("utf-8")).hexdigest()
chunk_id = RecordID(Tables.chunk.value, hash)

# skip if it already exists
if db.exists(chunk_id):
    continue
```

Find the complete code in [ingestion.py](https://github.com/surrealdb/kaig/tree/main/examples/knowledge-graph/src/knowledge_graph/ingestion).

Resources:

- Open source libraries: [Kreuzberg](https://kreuzberg.dev), [Docling](https://docling-project.github.io/docling/), [Marker](https://github.com/datalab-to/marker)
- Commercial solutions: [Document AI from Tensorlake](https://tensorlake.ai), https://www.datalab.to/

### Chunking documents

Let’s use an example document to explain different chunking strategies, but be mindful that other use cases may favour different strategies. Imagine your “raw” documents as backups of group chats. They are plain text files, in which each line looks like “\{user\} \{timestamp\} \{message\}”.

**Different strategies:**

- **Token limit:** chunks documents of equal size to guarantee that they fit into your embedding model window (commonly between 512 to 8k tokens)
- **Recursive:** Instead of a hard cut at *N* characters, it uses a hierarchy of separators (typically ["\n\n", "\n", " ", ""]) to find the best place to split.
- **Semantic:** chunks document based on their semantic meaning. With our group chat example, chunks are divided when the conversation topic changes.
- **Structure:** Rather than treating a document as raw text, these strategies use the inherent formatting (Markdown, HTML, or Code) to define boundaries
- **Custom:** be creative! Bringing our group chat example again, you can decide on periods of silence as a good point to split chunks. Doing this would be faster and cheaper than using the semantic strategy, and probably as accurate.

Simple and cheap strategies are worth trying first to have a good baseline. You then evaluate the results, and decide if a better (and more expensive) solution is required. This often produces better results than starting by choosing a complex strategy that may be overkill.

Another tip: adding overlaps to the chunks is a common practice, specially for the strategies that are simpler best-effort ones.

### Embedding generation

Directly using provider SDKs:

```python
  def embed_with_ollama(text: str) -> list[float]:
      """Generate embedding using Ollama."""
      res = ollama.embed(model=MODEL_NAME, input=text, truncate=True)
      return list(res.embeddings[0])

  def embed_with_openai(text: str) -> list[float]:
      """Generate embedding using OpenAI."""
      response = openai_client.embeddings.create(
          model=MODEL_NAME, input=text
      )
      return response.data[0].embedding
```

With an AI framework, like pydantic-ai:

```python
embedder = Embedder('openai:text-embedding-3-small')

with logfire.span(
    'create embedding for {search_query=}', search_query=search_query
):
    result = await embedder.embed_query(text)
    embedding_vector = result.embeddings[0]
```

### Entity and relationship extraction

This function shows how to extract concepts from a chunk, and relate them with graph edges:

```python
def extract_concepts(db: DB, chunk: Chunk) -> list[str]:
    if not db.llm:
        logger.warning("No LLM configured, skipping inference")
        return []
    with logfire.span("Extract concepts {chunk=}", chunk=chunk.id):
        instructions = dedent("""
            - Only return concepts that are: names, places, people, organisations, events, products, services, etc.
            - Do not include symbols or numbers
        """)

        concepts = db.llm.infer_concepts(chunk.content, instructions)
        logger.info(f"Concepts: {concepts}")

        for concept in concepts:
            concept_id = RecordID(Tables.concept.value, concept)
            _ = db.embed_and_insert(
                Concept(content=concept, id=concept_id),
                table=Tables.concept.value,
                id=concept,
            )
            db.relate(
                chunk.id,
                EdgeTypes.MENTIONS_CONCEPT.value.name,
                concept_id,
            )

        logger.info("Finished inference!")
        return concepts
```

The [implementation of `infer_concepts`](https://github.com/surrealdb/kaig/blob/main/src/kaig/llm.py) is a bit complex because it’s abstracting multiple providers. So, here is a simplification:

```python
PROMPT_INFER_CONCEPTS = """
Given the "Text" below, can you generate a list of concepts that can be used
to describe it?. Don't provide explanations.

{additional_instructions}

## Text:

{text}
"""

class LLM:
    ...
    
    def infer_concepts(
        self, text: str, additional_instructions: str = ""
    ) -> list[str]:
				additional_instructions = (
            "Return a JSON array of strings. " + additional_instructions
	      )
        prompt = PROMPT_INFER_CONCEPTS.format(
           text=text, additional_instructions=additional_instructions
        )
        response = self._generate_openai(
            prompt, response_format={"type": "json_object"}
        )
        # parses the response into a list of str
        return validate_list(response)
```

Using `additional_instructions` allows us to reuse then function `infer_concepts` for different domains.

### Loading

For the following example, assume the following schema:

Vector indexes on: `chunk` and `keyword` tables

Graph relations: `PART_OF`, `MENTIONED_IN`

Tables: `chunk`, `document`, `keyword`

We have extracted the entities and relationships from the chunks, so we are ready to insert the nodes and edges into the graph, using semantic triplets like these:

`chunk → PART_OF → document`

`keyword → MENTIONED_IN → chunk`

In Python, it looks like this:

```python
def insert(db: DB, triplets: list[(RecordID, str, RecordID)]):
	for (a, relation, b) in triplets:
	  # - Store the nodes
	  for x in [a, b]:
	    node = Node(id=x, content=x.id)
	    
	    # - Embed the node if it has a vector index
		  if x.table in vector_tables:
				db.embed_and_insert(node)
			else:
			  db.insert(node)
		
		# - Store the relation
		db.relate(a, relation, b)
```

The code above is an simplification of [inference.py](https://github.com/surrealdb/kaig/blob/main/examples/knowledge-graph/src/knowledge_graph/handlers/inference.py) from this [knowledge-graph example](https://github.com/surrealdb/kaig/tree/main/examples/knowledge-graph), which uses utils functions (e.g. `embed_and_insert`) from our [Kai G examples repository](https://github.com/surrealdb/kaig/tree/main).

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/http-via-postman

# Query SurrealDB from Postman

In this tutorial, you will learn how to query the SurrealDB RESTful HTTP API endpoints using Postman.

SurrealDB provides a RESTful HTTP API for interacting with the database.

In this tutorial, you will learn how to query SurrealDB endpoints via the [Postman collection](https://postman.com/surrealdb/workspace/surrealdb/collection/19100500-3da237f3-588b-4252-8882-6d487c11116a). SurrealDB supports requests over HTTP and REST, which can handle queries ranging from creating simple tables to graph relationships between complex tables.

Check out the [HTTP & REST integration documentation](/docs/reference/rest-api/http-protocol.md) for a detailed list of all the endpoints supported.

## Prerequisites

This tutorial assumes that you have the following:

- A Postman account to fork the [SurrealDB collection](https://postman.com/surrealdb/workspace/surrealdb/collection/19100500-3da237f3-588b-4252-8882-6d487c11116a)
- SurrealDB installed - If you do not, see the [installation guide](/docs/running/installation.md) specific to your machine.

## Getting started

Before forking the Collection from Postman, ensure that you have your SurrealDB instance running locally because by default the collection endpoint is set to [`localhost:8000`](http://localhost:8000) or [`http://127.0.0.1:8000`](http://127.0.0.1:8000/)

To do so run the [Start command](/docs/reference/cli/db/commands/start.md) in your terminal:

```bash
surreal db start --user root --pass secret
```

The above command starts SurrealDB with [authentication](/docs/reference/cli/db/commands/start.md#authentication) and specifies that the user and password are `root`. Note that this is just for demonstration purposes. You can replace `root` in both instances with any other value.

After running locally, head over to Postman and create a fork for your workspace. You should see all the endpoints listed. You can also [check the endpoints in the documentation](/docs/reference/rest-api/http-protocol.md).

## Using the `INFO` statement

You can get information about your workspace by using the INFO statement. However, you need the right permissions in order to see the output. See the documentation for the [INFO statement](/docs/reference/query-language/statements/info.md).

Note that the `namespace` and `database` values for the Postman collection are set to `test` by default.

You can see the JSON output using the [INFO statement](/docs/reference/query-language/statements/info.md) in the body of the request.

```surql
INFO FOR ROOT / DB / NS
```

Below is the output of querying for the info of the current namespace.

```surql
INFO FOR NS
```

```json
[
    {
        "result": {
            "accesses": {},
            "databases": {
                "test": "DEFINE DATABASE test"
            },
            "users": {}
        },
        "status": "OK",
        "time": "190.166µs"
    }
]
```

## Defining a record user

To define a [record user](/docs/learn/security/authentication/users.md#record-users), first navigate to the `POST /sql`  endpoint and in the header of the request add the following fields:

```json
Accept:application/json
NS:{{namespace}}
DB:{{database}}
```
You can also [define](/docs/reference/query-language/statements/define/user.md) a [system user](/docs/learn/security/authentication/users.md#system-users) with other credentials such as Root or Database user. For this tutorial, we will be using only [record users](/docs/learn/security/authentication/users.md#record-users).

> [!NOTE]
> The Namespace and Database fields are set to the value `test` by default. You can change this in the collection settings.

In the request body, [define the record access method](/docs/reference/query-language/statements/define/access/record.md) `human` with a session of 24 hours:

```surql
DEFINE ACCESS human ON DATABASE TYPE RECORD
    SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
    SIGNIN ( SELECT * FROM user WHERE email = $email
      AND crypto::argon2::compare(pass, $pass) )
    DURATION FOR SESSION 24h
;
```

The code above allows a user to sign up as a record user with their email and password, and hashes the password with the [`crypto::argon2::generate`](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2generate) function.

The sign in logic gets all the users where the emails match and uses the [`crypto::argon2::compare`](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) function to compare the stored hash with the password provided.

Now when you get the info of the Database using `INFO for DB` you can see the access method `human` in the following output:

```json
[
    {
        "result": null,
        "status": "OK",
        "time": "102.458µs"
    },
    {
        "result": {
            "accesses": {
                "human": "DEFINE ACCESS human ON DATABASE TYPE RECORD SIGNUP (CREATE user SET email = $email, pass = crypto::argon2::generate($pass)) SIGNIN (SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass)) DURATION FOR TOKEN 1h, FOR SESSION 1d"
            },
            "analyzers": {},
            "functions": {},
            "models": {},
            "params": {},
            "tables": {},
            "users": {}
        },
        "status": "OK",
        "time": "78.084µs"
    }
]
```

## Signing up a new scope user

After defining the `signup` and `signin` logic head over to the `POST /signup` endpoint to signup and in the header of the request add the following fields:

```json
Accept:application/json
namespace:test
database:test
access:human  
```

In the body of the request add the following information:

```json
{
    "ns": "test",
    "db": "test",
    "ac": "human",
    "email": "test@surreal.com",
    "pass":"1234567886"
}
```

The result will be a  JSON object:

```json
{
    "code": 200,
    "details": "Authentication succeeded",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MDcxNDY2NTgsIm5iZiI6MTcwNzE0NjY1OCwiZXhwIjoxNzA3MjMzMDU4LCJpc3MiOiJTdXJyZWFsREIiLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJBQyI6Imh1bWFuIiwiSUQiOiJ1c2VyOnc2ZzBsNmh5eHpjZzlubTY2dGVjIn0.8Gud51cocThB8DMKD1zovtGiVgf5L1dAS6-pjWb6Lm6a7-4Spp7xXjD7JrHHdtJVNX1O0d8GdjZwRGTsP_NM9A"
}
```

## Signing in a record user

Now that a user is defined, you can sign in. To do so, go to the `POST /signin` endpoint and use the same credentials. In the body of the request, add the same information you used to sign up:

```json
{
    "ns": "test",
    "db": "test",
    "ac": "human",
    "email": "test3@surreal.com",
    "pass":"1234567886"
} 
```

This should return a similar JSON object indicating successful authentication and providing a new token.

```json
{
    "code": 200,
    "details": "Authentication succeeded",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MDcxNDY2NTgsIm5iZiI6MTcwNzE0NjY1OCwiZXhwIjoxNzA3MjMzMDU4LCJpc3MiOiJTdXJyZWFsREIiLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJBQyI6Imh1bWFuIiwiSUQiOiJ1c2VyOnc2ZzBsNmh5eHpjZzlubTY2dGVjIn0.8Gud51cocThB8DMKD1zovtGiVgf5L1dAS6-pjWb6Lm6a7-4Spp7xXjD7JrHHdtJVNX1O0d8GdjZwRGTsP_NM9A"
}
```

## Using table endpoints

The Postman collection has a couple of endpoints for `POST` `GET` `PUT` `PATCH` `DEL` operations. For example, if you want to add a new entry to the table `Person` (which is the default table in the collection), go to the [`POST /key/:table`](/docs/reference/rest-api/http-protocol.md#post-table) endpoint and add the table content in the body of the request. For example:

```json
{
    age: 32,
    name: 'John'
}
```

You should get a response as seen below, notice that there is a [record ID](/docs/reference/query-language/language-primitives/data-types/record-ids.md), with this record ID you can now use any of the [`/key/:table/:id`](/docs/reference/rest-api/http-protocol.md#get-record) endpoints for  `POST` `GET` `PUT` `PATCH` `DEL` operations.

```json
[
    {
        "result": [
            {
                "age": 32,
                "id": "person:p1cdf6cx89gnfboq5wye",
                "name": "John"
            }
        ],
        "status": "OK",
        "time": "105.667µs"
    }
]
```

## Conclusion

In this tutorial, we've walked through how to set up and use SurrealDB over HTTP using Postman. We've covered how to define a new record access method, sign up a new user, and sign in with a user. These are the steps for managing user authentication in your applications using SurrealDB.

The Postman collection is still in active development. For more information on using SurrealDB via the HTTP endpoints, [check out our documentation on HTTP and REST endpoints](/docs/reference/rest-api/http-protocol.md).

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/minimal-langchain

# Build a minimal LangChain chatbot

This tutorial shows how to build a minimal LangChain chatbot with both vector and graph.

This tutorial walks through a simple example that does the following:

1. creates the vector store ([SurrealDBVectorStore](https://python.langchain.com/docs/integrations/vectorstores/surrealdb/)) and graph ([SurrealDBGraph](https://github.com/surrealdb/langchain-surrealdb/blob/main/langchain_surrealdb/experimental/surrealdb_graph.py)) instances
2. adds documents to the vector store, including the embeddings ([what are embeddings?](/blog/find-your-celebrity-soulmate-with-the-magic-of-vector-search))
3. builds a graph
4. based on a provided topic, does a vector search and a graph query to generate an answer in natural language

For this example, the data that we are going to store and then retrieve is:

- concept definitions: stored in the Vector Store
- people who know about those concepts: stored in the Graph (e.g. Martin -> knows about -> SurrealDB)

## 1. Create the vector store and graph instances

```python
import time

from langchain_community.graphs.graph_document import GraphDocument, Node, Relationship
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import OllamaEmbeddings
from langchain_ollama.llms import OllamaLLM
from surrealdb import Surreal

from langchain_surrealdb.experimental.surrealdb_graph import SurrealDBGraph
from langchain_surrealdb.vectorstores import SurrealDBVectorStore

conn = Surreal("ws://localhost:8000/rpc")
conn.signin({"username": "root", "password": "secret"})
conn.use("langchain", "demo")
vector_store = SurrealDBVectorStore(OllamaEmbeddings(model="all-minilm:22m"), conn)
graph_store = SurrealDBGraph(conn)

vector_store.delete()
graph_store.delete_nodes()
```

<br />

## 2. Add documents to the vector store

```python
doc1 = Document(
    page_content="SurrealDB is the ultimate multi-model database for AI applications",
    metadata={"key": "sdb"},
)
doc2 = Document(
    page_content="Surrealism is an artistic and cultural movement that emerged in the early 20th century",
    metadata={"key": "surrealism"},
)
vector_store.add_documents(documents=[doc1, doc2], ids=["1", "2"])
```

<br />

## 3. Build the graph

```python
# Document nodes
node_sdb = Node(id="sdb", type="Document")
node_surrealism = Node(id="surrealism", type="Document")

# People nodes
node_martin = Node(id="martin", type="People", properties={"name": "Martin"})
node_tobie = Node(id="tobie", type="People", properties={"name": "Tobie"})
node_max = Node(id="max", type="People", properties={"name":"Max Ernst"})

# Edges
graph_documents = [
    GraphDocument(
        nodes=[node_martin, node_tobie, node_sdb],
        relationships=[
            Relationship(source=node_martin, target=node_sdb, type="KnowsAbout"),
            Relationship(source=node_tobie, target=node_sdb, type="KnowsAbout")
        ],
        source=doc1,
    ),
    GraphDocument(
        nodes=[node_max, node_surrealism],
        relationships=[
            Relationship(source=node_max, target=node_surrealism, type="KnowsAbout")
        ],
        source=doc2,
    ),
]

graph_store.add_graph_documents(graph_documents)
```

<br />

## 4. Let’s get an LLM involved

For this example we are using [OllamaLLM](https://python.langchain.com/docs/integrations/llms/ollama/) from the LangChain components. You can use any other of the LLM components.

For Ollama, here are [all the parameters](https://docs.ollama.com/modelfile). In the example we turned the `temperature` up to the max to get the craziest outcomes possible. You may want to leave it at around 0.7, but it depends on your use case.

```python
model = OllamaLLM(model="llama3.2", temperature=1, verbose=True)

# Let's retrieve information about these 2 topics
queries = ["database", "surrealism"]
for q in queries:
    print(f'\n----------------------------------\nTopic: "{q}"\nVector search:')
    results = vector_store.similarity_search_with_score(query=q, k=2)
    for doc, score in results:
        print(f"• [{score:.0%}]: {doc.page_content}")
    top_match = results[0][0]

    # Graph query
    res = graph_store.query(
        """
        SELECT <-relation_KnowsAbout<-graph_People as people
        FROM type::record("graph_Document", $doc_key)
        FETCH people
        """,
        {"doc_key": top_match.metadata.get("key")},
    )
    people = [x.get("name") for x in res[0].get("people", [])]

    print(f"\nGraph result: {people}")

    # Template for the LLM
    template = """
    You are a young, energetic database developer in your last 20s, who loves to
    talk tech, and who's also very geeky.
    Use the following pieces of retrieved context to answer the question.
    Use four sentences maximum and keep the answer concise.
    Try to be funny with a play on words.

    Context: {context}. People who know about this: {people}.

    Question: Explain "{topic}", summarize the context provided, and tell me
    who I can ask for more information.

    Answer:
    """

    prompt = ChatPromptTemplate.from_template(template)
    chain = prompt | model

    answer = chain.invoke(
        {"context": top_match.page_content, "people": people, "topic": q}
    )
    print(f"\nLLM answer:\n===========\n{answer}")
    time.sleep(4)

print("\nBye!")
```

<br />

## Let’s try it out

```text
----------------------------------
Topic: "database"
Vector search:
• [34%]: SurrealDB is the ultimate multi-model database for AI applications
• [22%]: Surrealism is an artistic and cultural movement that emerged in the early 20th century

Graph result: ['Martin', 'Tobie']

LLM answer:
===========
"Databases are like my brain, but less cluttered (mostly). Seriously though, a
database is a storage system that organises data in a way that allows efficient
retrieval. Given our conversation about SurrealDB being the ultimate multi-model
database for AI applications, it's no wonder Martin and Tobie are experts on the
subject - they're the 'key' people to ask!

----------------------------------
Topic: "surrealism"
Vector search:
• [52%]: Surrealism is an artistic and cultural movement that emerged in the early 20th century
• [46%]: SurrealDB is the ultimate multi-model database for AI applications

Graph result: ['Max Ernst']

LLM answer:
===========
It looks like we've stumbled into a "dream" world of art - Surrealism is all
about exploring the subconscious mind through bizarre and fantastical creations.
Think weird creatures, melting objects, and illogical scenarios - it's like
someone poured a SQL query into my brain! According to the context, Max Ernst
was one of the pioneers of this wild movement. If you want to learn more, I'd
recommend checking out some online tutorials or asking a fellow art history geek
(just don't ask me to explain it in code)

Bye!
```

<br />

## Bonus stage

Did you notice the prompt in the code? Be creative and try different personalities:

```python
template = """
    You are rap singer, who always rhymes when talking.
    ...
"""
```

<br />

🥁 Drum roll...

```text
LLM answer:
===========
Yo, listen up, let me share a tale,
About databases, it's time to prevail.
A database's a collection, of data so fine,
Storing info, making it all mine.

Context's tight, about SurrealDB's the claim,
Tobie and Martin know its AI fame.
For more deets, ask them with glee,
They'll hook you up with knowledge, just wait and see!
```

<br />

It never disappoints.

```text
LLM answer:
===========
Yo, listen up, let me share a tale,
Surrealism's the movement that set sail.
It emerged in the 20th century's early prime,
Influenced by Max Ernst, the pioneer of the time.
```

<br />

## Ready to build?

Find all the code in the [langchain-surrealdb repository examples](https://github.com/surrealdb/langchain-surrealdb/tree/main/examples/basic/graph.py).

Get started for free with [Surreal Cloud](https://studio.surrealdb.com/signin).

Any questions or thoughts about this or graph queries using SurrealDB? [Join our Discord](https://discord.gg/surrealdb) - `#all-ai` and `#surrealql` are good places to start for this topic, and `#help` or `#general` work for anything else.

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/overview

# Tutorials

Step-by-step tutorials and walkthroughs for specific tasks with SurrealDB: integrations, real-time apps, AI patterns, and more.

This section holds **tutorials**: practical **walkthroughs** that take you through a task or feature in order, with commands, configuration, and code you can follow along with. They are aimed at learning and production-style setup rather than only skimming a finished app.

If you prefer a **ready-made project** you can clone and run with minimal narrative, see the [demos](/docs/explore/tutorials/demos/overview.md) section instead.

If you are new to SurrealDB, you may also want [Getting started](/docs) and the [SurrealQL](/docs/reference/query-language.md) reference.

## Getting started

- [Define a schema in SurrealDB](/docs/explore/tutorials/tutorials/define-a-schema.md) - build up a schemafull table with fields, assertions and indexes
- [Query SurrealDB from Postman](/docs/explore/tutorials/tutorials/http-via-postman.md) - drive the HTTP endpoints from a REST client
- [Connect to SurrealDB via ngrok](/docs/explore/tutorials/tutorials/connect-via-ngrok.md) - expose a local instance to a remote client
- [Use SurrealDB in GitHub Actions](/docs/explore/tutorials/tutorials/github-actions.md) - run a database alongside your CI jobs

## Authentication

- [Integrate Auth0 with SurrealDB](/docs/explore/tutorials/tutorials/auth0-integration.md) - verify Auth0 tokens with a record access method
- [Integrate AWS Cognito with SurrealDB](/docs/explore/tutorials/tutorials/aws-cognito-integration.md) - the same pattern with a Cognito user pool

## AI and search

- [Build an AI agent with Python](/docs/explore/tutorials/tutorials/build-an-ai-agent.md) - an agent that queries the database as a tool
- [Build a GenAI chatbot with Graph RAG](/docs/explore/tutorials/tutorials/gen-ai-chatbot.md) - retrieval over a graph rather than a flat vector store
- [Build a knowledge graph for AI](/docs/explore/tutorials/tutorials/how-to-build-a-knowledge-graph-for-ai.md) - model entities and relations for retrieval
- [Build a minimal LangChain chatbot](/docs/explore/tutorials/tutorials/minimal-langchain.md) - the smallest working LangChain setup
- [Implement semantic search in Rust](/docs/explore/tutorials/tutorials/semantic-search-in-rust.md) - embeddings, a vector index, and search from the Rust SDK

## Real-time

- [Build a real-time presence app](/docs/explore/tutorials/tutorials/build-a-real-time-presence-app.md) - live queries driving who is online

---

Source: https://surrealdb.com/docs/explore/tutorials/tutorials/semantic-search-in-rust

# Implement semantic search in Rust

In this guide, you'll learn how to implement semantic search in Rust using either Mistral AI or OpenAI via their Rust crates.

This guide demonstrates how to use the Rust SDK to store AI embeddings from Mistral AI or OpenAI which can then be queried natively in SurrealQL.

**Mistral AI**

## Using Mistral

<img src="~/assets/img/tutorials/semantic-search-mistral-light.png" darkSrc="~/assets/img/tutorials/semantic-search-mistral.png" alt="Semantic search in Rust with SurrealDB and Mistral AI" />

The purpose of this post is to demonstrate how to use the Rust SDK to store Mistral AI embeddings as [SurrealDB vectors](/docs/learn/data-models/vector-search/overview.md), which can then be queried natively in SurrealQL to perform semantic search.

This guide uses Rust's [mistralai-client](https://crates.io/crates/mistralai-client) crate to generate embeddings, but the code below can be modified to suit [other languages](https://docs.mistral.ai/getting-started/clients/#rust) that have clients for Mistral's AI platform. If you are a Python user, check out [this page](/docs/build/integrations/embeddings-providers/mistral.md) in the documentation for another ready-made example.

### Setup

Setting up an embedded SurrealDB database requires no installation and can be done in just a few lines of code. After creating a new Cargo project with `cargo new project_name` and going into the project folder, add the following dependencies inside `Cargo.toml`:

```toml
anyhow = "1.0.102"
mistralai-client = "0.14.0"
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros"] }
surrealdb = { version = "3.0.4", default-features = false, features = ["kv-mem"] }
```
<br />

You can add the same dependencies on the command line through a single command:

```bash
cargo add anyhow mistralai-client tokio surrealdb --features surrealdb/kv-mem
```
<br />

Setting up a SurrealDB database in Rust is as easy as calling the `connect` function with `"memory"` to instantiate an embedded database in memory. This code uses `anyhow` to allow the question mark operator to be used, but you can also just begin with `.unwrap()` everywhere and eventually move on to your own preferred error handling.

```rust
use anyhow::Error;
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() -> Result<(), Error> {
    let db = connect("memory").await?;
    Ok(())
}
```
<br />

If you have a running Cloud or local instance, you can pass that path into the `connect()` function instead.

```rust
// Cloud address
let db = connect("wss://cloud-docs-068rp16e0hsnl62vgooa7omjks.aws-euw1.staging.surrealdb.cloud").await?;

// Local address
let db = connect("ws://localhost:8000").await?;
```
<br />

After connecting, we will select a namespace and database name, such as `ns` and `db`.

```rust
db.use_ns("main").use_db("main").await?;
```
<br />

### Create a vector table and index

Next we'll create a table called `document` to store documents and embeddings, along with an index for the embeddings. The statements look like this:

```surql
DEFINE TABLE document;
DEFINE FIELD text ON document TYPE string;
DEFINE FIELD embedding ON document TYPE array<float>;
DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1024 DIST COSINE;
```
<br />

The important piece to understand is the relationship between the `embedding` field, a simple array of floats, and the index that we have given the name `hnsw_embed`. The size of the vector (1024 here) represents the number of dimensions in the embedding. This is to match Mistral AI's `mistral-embed` model, which uses [1024 as its length](https://docs.mistral.ai/getting-started/models/models_overview/#premier-models).

The [HNSW index](/docs/learn/data-models/vector-search/vector-indexes.md) is not strictly necessary to use the KNN operator (`<||>`) to find an embedding's closest neighbours, and for our small sample code we will use the simple [brute force method](/docs/reference/query-language/language-primitives/operators.md#brute-force-method) which chooses [an algorithm](/docs/learn/data-models/vector-search/similarity-search.md#computation-on-vectors-vector-package-of-functions) such as Euclidean, Hamming, and so on. The following is the code that we will use, which uses the cosine of an embedding to find the four closest neighbours.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|4,COSINE|> $embeds
    ORDER BY distance;
```

As the dataset grows, however, the syntax can be changed to use [the HNSW index](/docs/reference/query-language/language-primitives/operators.md#hnsw-method), by replacing an algorithm with a number that represents the size of the dynamic candidate list. This index is recommended when a small loss of accuracy is acceptable in order to preserve performance.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|4,40|> $embeds
    ORDER BY distance;
```

Inside the Rust SDK we can put all four of these inside a single `.query()` call and then add a line to see if there are errors inside any of them.

```rust
let mut res = db
    .query(
        "DEFINE TABLE document;
DEFINE FIELD text ON document TYPE string;
DEFINE FIELD embedding ON document TYPE array<float>;
DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1024 DIST COSINE;",
    )
    .await?;
for (index, error) in res.take_errors() {
    println!("Error in query {index}: {error}");
}
```
<br />

### Generate Mistral AI embeddings

At this point, you will need a [key](https://console.mistral.ai/api-keys) to interact with Mistral AI's platform. They offer a free tier for experimentation, after which you will be able to create a key to interact with it via the code below.

The code in this page will still work without a proper key, but the request to the Mistral AI API will end up returning the following error message.

```text
Error: ApiError: 401 Unauthorized: {"detail":"Unauthorized"}
```
<br />

The best way to set the key is as an environment variable, which we will set to be a static called `KEY`. The client will look for one called `MISTRAL_API_KEY`, though you can change this when setting up the Mistral AI Rust client if you like.

```rust
// Looks for MISTRAL_API_KEY
let client = Client::new(Some(KEY.to_string()), None, None, None)?;
// Looks for OTHER_ENV_VAR
let client = Client::new(Some(KEY.to_string()), Some("OTHER_ENV_VAR".to_string()), None, None)?;
```

Using a `LazyLock` will let us call it via `std::env::var()` function the first time it is accessed. You can of course simply put it into a `const` for simplicity when first testing, but always remember to never hard-code API keys in your code in production.

```rust
static KEY: LazyLock<String> = LazyLock::new(|| {
    std::env::var("MISTRAL_API_KEY").unwrap()
});
```
<br />

And then run the code like this:

```bash
MISTRAL_API_KEY=whateverthekeyis cargo run
```
<br />

Or like this if you are using PowerShell on Windows.

```powershell
$env:MISTRAL_API_KEY = "whateverthekeyis"
cargo run
```
<br />

We can also create a `const MODEL` to hold the Mistral AI model used, which in this case is an `EmbedModel::MistralEmbed`.

```rust
const MODEL: EmbedModel = EmbedModel::MistralEmbed;
```

Inside `main()`, we will then [create a client](https://docs.rs/mistralai-client/0.14.0/mistralai_client/v1/client/struct.Client.html#method.new) from the `mistralai-client` crate.

```rust
let client = Client::new(Some(KEY.to_string()), None, None, None)?;
```
<br />

We'll use that to generate a Mistral AI embedding using the [`mistral-embed`](https://docs.mistral.ai/getting-started/models/models_overview/#premier-models) model. The `mistralai-client` has both sync and async functions that take a `Vec<String>`, and since SurrealDB uses the tokio runtime, we'll call the async `.embeddings_async()` method.

```rust
let input = vec!["Joram is the main character in the Darksword Trilogy.".to_string()];

let result = client.embeddings_async(MODEL, input, None).await?;
println!("{:?}", result);
```
<br />

The output in your console should show a massive number of floats, 1024 of them to be precise. That's the embedding for this input!

### Store embeddings in database

Now that we have the embedding returned from the Mistral AI client, we can store it in the database. The [response](https://docs.rs/mistralai-client/0.14.0/mistralai_client/v1/embedding/struct.EmbeddingResponse.html) returned from the mistralai-client crate looks like this, with a `Vec` of `EmbeddingResponseDataItem` structs that hold a `Vec<f32>`.

```rust
pub struct EmbeddingResponse {
    pub id: String,
    pub object: String,
    pub model: EmbedModel,
    pub data: Vec<EmbeddingResponseDataItem>,
    pub usage: ResponseUsage,
}

pub struct EmbeddingResponseDataItem {
    pub index: u32,
    pub embedding: Vec<f32>,
    pub object: String,
}
```
<br />

We know that our simple request only returned a single embedding, so `.remove(0)` will do the job. In a more complex codebase you would probably opt for a match on `.get(0)` to handle any possible errors.

```rust
let embeds = result.data.remove(0).embedding;
```
<br />

There are a [number of ways](/docs/reference/rust/concepts/flexible-typing.md) to work with or avoid structs when using the Rust SDK, but we'll just go with two basic structs: one to represent the input into a `.create()` statement, and one to display the results.

```rust
#[derive(SurrealValue)]
struct DocumentInput {
    text: String,
    embedding: Vec<f32>,
}

#[derive(Debug, SurrealValue)]
struct Document {
    id: RecordId,
    embedding: Vec<f32>,
    text: String,
}
```
<br />

Once that is done, we can print out the created documents as a `Document` struct. We'll fiddle with the code a bit to have the `input` start as a `&str` which will be turned into a `String` in order to get the embedding, as well as to create a `Document` struct.

```rust
let input = "Octopuses solve puzzles and escape enclosures, showing advanced intelligence.";

let mut result = client
    .embeddings_async(MODEL, vec![input.to_string()], None)
    .await?;
let embeds = result.data.remove(0).embedding;
let in_db = db
    .create::<Option<Document>>("document")
    .content(DocumentInput {
        text: input.into(),
        embedding: embeds.to_vec(),
    })
    .await?;
println!("{in_db:?}");
```
<br />

We should now add some more `document` records. To do this, we'll move the logic to create them inside a function of its own. Since the `embeddings_async()` method takes a single `Vec<String>`, we'll first clone it to keep the original `Vec<String>` around, then zip it together with the embeddings returned so that they can be put into the database along with the original input.

```rust
async fn create_embeds(
    input: Vec<String>,
    db: &Surreal<Any>,
    client: &Client,
) -> Result<(), Error> {
    let cloned = input.clone();
    let embeds = client.embeddings_async(MODEL, input, None).await?;
    let zipped = cloned
        .into_iter()
        .zip(embeds.data.into_iter().map(|item| item.embedding));

    for (text, embeds) in zipped {
        let _in_db = db
            .create::<Option<Document>>("document")
            .content(DocumentInput {
                text,
                embedding: embeds,
            })
            .await?;
    }
    Ok(())
}
```
<br />

Then we'll create four facts for each of four topics: sea creatures, Korean and Japanese cities, historical figures, and planets of the Solar System (including the dwarf planet Ceres).

```rust
let embeds = [
    "Octopuses solve puzzles and escape enclosures, showing advanced intelligence.",
    "Sharks exhibit learning behavior, but their intelligence is instinct-driven.",
    "Sea cucumbers lack a brain and show minimal cognitive response.",
    "Clams have simple nervous systems with no known intelligent behavior.",
    //
    "Seoul is South Korea’s capital and a global tech hub.",
    "Sejong is South Korea’s planned administrative capital.",
    "Busan a major South Korean port located in the far southeast.",
    "Tokyo is Japan’s capital, known for innovation and dense population.",
    //
    "Wilhelm II was Germany’s last Kaiser before World War I.",
    "Cyrus the Great founded the Persian Empire with tolerant rule.",
    "Napoleon Bonaparte was a French emperor and brilliant military strategist.",
    "Aristotle was a Greek philosopher who shaped Western intellectual thought.",
    //
    "Venus’s atmosphere ranges from scorching surface to Earth-like upper clouds.",
    "Mars has a thin, cold atmosphere with seasonal dust storms.",
    "Ceres has a tenuous exosphere with sporadic water vapor traces.",
    "Saturn’s atmosphere spans cold outer layers to a deep metallic hydrogen interior",
]
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>();

create_embeds(embeds, &db, &client).await?;
```
<br />

### Semantic search

Finally let's perform semantic search over the embeddings in our database. We'll go with this query that uses the KNN operator to return the closest four matches to an embedding.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|4,COSINE|> $embeds
    ORDER BY distance;
```
<br />

You can customise this [with other algorithms](/docs/learn/data-models/vector-search/similarity-search.md#computation-on-vectors-vector-package-of-functions) such as Euclidean, Hamming, and so on.

We will then put this into a separate function called `ask_question()` which looks similar to `create_embed()`, except that it first prints out its input, and then uses its embedding retrieved from Mistral to query the database against existing documents instead of creating a new document.

```rust
async fn ask_question(input: &str, db: &Surreal<Any>, client: &Client) -> Result<(), Error> {
    println!("{input}");
    let embeds = client
        .embeddings_async(MODEL, vec![input.to_string()], None)
        .await?
        .data
        .remove(0)
        .embedding;

    let mut response = db.query("SELECT text, vector::distance::knn() AS distance FROM document WHERE embedding <|4,COSINE|> $embeds ORDER BY distance;").bind(("embeds", embeds)).await?;
    let as_val: Value = response.take(0)?;
    println!("{as_val}\n");
    Ok(())
}
```
<br />

Finally, we will call this function a few times inside `main()` to confirm that the results are what we expect them to be, printing out the results of each so that we can check them.

```rust
ask_question("Which Korean city is just across the sea from Japan?", &db, &client).await?;
ask_question("Who was Germany's last Kaiser?", &db, &client).await?;
ask_question("Which sea animal is most intelligent?", &db, &client).await?;
ask_question("Which planet's atmosphere has a part with the same temperature as Earth?", &db, &client).await?;
```
<br />

The output shows that the facts that fit most to our questions end up displayed first, with differing distance depending on how close the other facts were. Octopuses end up smarter than sharks (which is true), but the "learning behaviour" part of our input does end up making sharks score pretty close. On the other extreme, Wilhelm II is clearly the only input that comes anywhere close to "Germany's last Kaiser", with Napoleon Bonaparte way behind. Poor Aristotle doesn't make it into any results, with "Sejong is South Korea's planned administrative capital" slightly closer semantically in terms of "Who was Germany's last Kaiser".

```text
Which Korean city is just across the sea from Japan?
[{ distance: 0.19170371029549582f, text: 'Busan is a major South Korean port located in the far southeast.' }, { distance: 0.2399314515762122f, text: 'Tokyo is Japan’s capital, known for innovation and dense population.' }, { distance: 0.2443623703771407f, text: 'Sejong is South Korea’s planned administrative capital.' }, { distance: 0.24488082839731895f, text: 'Seoul is South Korea’s capital and a global tech hub.' }]

Who was Germany's last Kaiser?
[{ distance: 0.11228576780228805f, text: 'Wilhelm II was Germany’s last Kaiser before World War I.' }, { distance: 0.2957177300085634f, text: 'Napoleon Bonaparte was a French emperor and brilliant military strategist.' }, { distance: 0.34394473621670896f, text: 'Cyrus the Great founded the Persian Empire with tolerant rule.' }, { distance: 0.34911517400935843f, text: 'Sejong is South Korea’s planned administrative capital.' }]

Which sea animal is most intelligent?
[{ distance: 0.2342596053829904f, text: 'Octopuses solve puzzles and escape enclosures, showing advanced intelligence.' }, { distance: 0.24131327939924785f, text: 'Sharks exhibit learning behavior, but their intelligence is instinct-driven.' }, { distance: 0.2426242772516931f, text: 'Clams have simple nervous systems with no known intelligent behavior.' }, { distance: 0.24474598154128135f, text: 'Sea cucumbers lack a brain and show minimal cognitive response.' }]

Which planet's atmosphere has a part with the same temperature as Earth?
[{ distance: 0.20653440713083582f, text: 'Venus’s atmosphere ranges from scorching surface to Earth-like upper clouds.' }, { distance: 0.23354208810464594f, text: 'Mars has a thin, cold atmosphere with seasonal dust storms.' }, { distance: 0.24560810032473468f, text: 'Saturn’s atmosphere spans cold outer layers to a deep metallic hydrogen interior' }, { distance: 0.2761595357544341f, text: 'Ceres has a tenuous exosphere with sporadic water vapor traces.' }]
```
<br />

As the database grows, you could also change the `<|4,COSINE|>` part of the query to something like `<|4,40|>` to see the results using the HNSW index instead of the brute force method.

Finally, here is all of the code for you to run and modify as you wish.

```rust
use std::sync::LazyLock;

use anyhow::Error;
use mistralai_client::v1::{client::Client, constants::EmbedModel};
use surrealdb::{
    Surreal,
    engine::any::{Any, connect},
    types::{RecordId, SurrealValue, ToSql, Value},
};

static KEY: LazyLock<String> = LazyLock::new(|| std::env::var("MISTRAL_API_KEY").unwrap());

// Experiment plan
const MODEL: EmbedModel = EmbedModel::MistralEmbed;

#[derive(SurrealValue)]
struct DocumentInput {
    text: String,
    embedding: Vec<f32>,
}

#[derive(Debug, SurrealValue)]
struct Document {
    id: RecordId,
    embedding: Vec<f32>,
    text: String,
}

async fn create_embeds(
    input: Vec<String>,
    db: &Surreal<Any>,
    client: &Client,
) -> Result<(), Error> {
    let cloned = input.clone();
    let embeds = client.embeddings_async(MODEL, input, None).await?;
    let zipped = cloned
        .into_iter()
        .zip(embeds.data.into_iter().map(|item| item.embedding));

    for (text, embeds) in zipped {
        let _in_db = db
            .create::<Option<Document>>("document")
            .content(DocumentInput {
                text,
                embedding: embeds,
            })
            .await?;
    }
    Ok(())
}

async fn ask_question(input: &str, db: &Surreal<Any>, client: &Client) -> Result<(), Error> {
    println!("{input}");
    let embeds = client
        .embeddings_async(MODEL, vec![input.to_string()], None)
        .await?
        .data
        .remove(0)
        .embedding;

    let mut response = db.query("SELECT text, vector::distance::knn() AS distance FROM document WHERE embedding <|4,COSINE|> $embeds ORDER BY distance;").bind(("embeds", embeds)).await?;
    let as_val: Value = response.take(0)?;
    println!("{as_val}\n");
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let db = connect("memory").await.unwrap();

    db.use_ns("ns").use_db("db").await.unwrap();

    let mut res = db
        .query(
            "DEFINE TABLE document;
             DEFINE FIELD text ON document TYPE string;
             DEFINE FIELD embedding ON document TYPE array<float>;
             DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1024 DIST COSINE;",
        )
        .await
        .unwrap();
    for (index, error) in res.take_errors() {
        println!("Error in query {index}: {error}");
    }

    let client = Client::new(Some(KEY.to_string()), None, None, None)?;

    let embeds = [
        "Octopuses solve puzzles and escape enclosures, showing advanced intelligence.",
        "Sharks exhibit learning behavior, but their intelligence is instinct-driven.",
        "Sea cucumbers lack a brain and show minimal cognitive response.",
        "Clams have simple nervous systems with no known intelligent behavior.",
        //
        "Seoul is South Korea’s capital and a global tech hub.",
        "Sejong is South Korea’s planned administrative capital.",
        "Busan is a major South Korean port located in the far southeast.",
        "Tokyo is Japan’s capital, known for innovation and dense population.",
        //
        "Wilhelm II was Germany’s last Kaiser before World War I.",
        "Cyrus the Great founded the Persian Empire with tolerant rule.",
        "Napoleon Bonaparte was a French emperor and brilliant military strategist.",
        "Aristotle was a Greek philosopher who shaped Western intellectual thought.",
        //
        "Venus’s atmosphere ranges from scorching surface to Earth-like upper clouds.",
        "Mars has a thin, cold atmosphere with seasonal dust storms.",
        "Ceres has a tenuous exosphere with sporadic water vapor traces.",
        "Saturn’s atmosphere spans cold outer layers to a deep metallic hydrogen interior",
    ]
    .into_iter()
    .map(|s| s.to_string())
    .collect::<Vec<String>>();

    create_embeds(embeds, &db, &client).await?;

    ask_question("Which Korean city is just across the sea from Japan?", &db, &client).await?;
    ask_question("Who was Germany's last Kaiser?", &db, &client).await?;
    ask_question("Which sea animal is most intelligent?", &db, &client).await?;
    ask_question("Which planet's atmosphere has a part with the same temperature as Earth?", &db, &client).await?;

    Ok(())
}
```

<br />

**OpenAI**

## Using OpenAI

<img src="~/assets/img/tutorials/semantic-search-openai-light.png" darkSrc="~/assets/img/tutorials/semantic-search-openai.png" alt="Semantic search in Rust with SurrealDB and OpenAI" />

This guide demonstrates how to store OpenAI embeddings as [SurrealDB vectors](/docs/learn/data-models/vector-search/overview.md) via the Rust SDK for the purposes of semantic search. It uses Rust's [async-openai](https://crates.io/crates/async-openai/0.28.3) crate to generate embeddings.

### Setup

Setting up an embedded SurrealDB database only takes a few lines of code. After creating a new Cargo project with `cargo new project_name` and going into the project folder, we will then add the following dependencies inside `Cargo.toml`:

```toml
anyhow = "1.0.98"
async-openai = "0.34.0"
surrealdb = { version = "3.0.4", features = ["kv-mem"] }
tokio = "1.45.0"
```
<br />

They can also be added on the command line using this command:

```bash
cargo add anyhow async-openai tokio surrealdb --features surrealdb/kv-mem
```
<br />

Inside `main()`, we can call the `connect` function with `"memory"` to instantiate an embedded database in memory. With the possibility of error types from various sources, using `anyhow` is the easiest way to get started.

```rust
use anyhow::Error;
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() -> Result<(), Error> {
    let db = connect("memory").await?;
    Ok(())
}
```
<br />

If you have a Cloud or local instance to connect to, you can pass that path into the connect function instead.

```rust
// Cloud address
let db = connect("wss://cloud-docs-068rp16e0hsnl62vgooa7omjks.aws-euw1.staging.surrealdb.cloud").await?;

// Local address
let db = connect("ws://localhost:8000").await?;
```
<br />

After connecting, we will select a namespace and database name, such as `ns` and `db`.

```rust
db.use_ns("ns").use_db("db").await?;
```
<br />

### Create a vector table

Next we'll create a table to store documents and embeddings, along with an index for the embeddings. The statements look like this:

```surql
DEFINE TABLE document;
DEFINE FIELD text ON document TYPE string;
DEFINE FIELD embedding ON document TYPE array<float>;
DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1536;
```
<br />

Inside the SDK we can put all four of these inside a single `.query()` call and then add a line to see if there are errors inside any of them.

```rust
let mut res = db
    .query(
        "DEFINE TABLE document;
DEFINE FIELD text ON document TYPE string;
DEFINE FIELD embedding ON document TYPE array<float>;
DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1536;",
    )
    .await?;
for (index, error) in res.take_errors() {
    println!("Error in query {index}: {error}");
}
```
<br />

The important piece to understand is the relationship between the `embedding` field, a simple array of floats, and the `hnsw_embed` index. The size of the vector (1536 here) represents the number of dimensions in the embedding. Since OpenAI's `text-embedding-3-small` model in this example uses [1536 as its default length](https://platform.openai.com/docs/guides/embeddings), we set the vector size to 1536.

The [HNSW index](/docs/learn/data-models/vector-search/vector-indexes.md) is not strictly necessary to use the KNN operator (`<||>`) to find an embedding's closest neighbours, and for our small sample code we will use the simple [brute force method](/docs/reference/query-language/language-primitives/operators.md#brute-force-method) which chooses [an algorithm](/docs/learn/data-models/vector-search/similarity-search.md#computation-on-vectors-vector-package-of-functions) such as Euclidean, Hamming, and so on. The following is the code that we will use, which uses the cosine of an embedding to find the four closest neighbours.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|2,COSINE|> $embeds
    ORDER BY distance;
```

As the dataset grows, if some loss of accuracy is acceptable then the syntax can be changed to use [the HNSW index](/docs/reference/query-language/language-primitives/operators.md#hnsw-method), by replacing an algorithm with a number that represents the size of the dynamic candidate list.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|2,40|> $embeds
    ORDER BY distance;
```

### Generate OpenAI embeddings

At this point, you will need an [OpenAI API key](https://platform.openai.com/api-keys) to interact with the OpenAI API. You can still check the code to see if it works if you don't have a key, and you will get as far as this error message.

```text
Error: invalid_request_error: Incorrect API key provided: blah. You can find your API key at https://platform.openai.com/account/api-keys. (code: invalid_api_key)
```
<br />

The best way to set the key is as an environment variable, `OPENAI_API_KEY` in this case. Using a `LazyLock` will let us call it via `std::env::var()` function the first time it is accessed. You can of course simply put it into a `const` for simplicity when first testing, but always remember to never hard-code API keys in your code in production.

```rust
static KEY: LazyLock<String> = LazyLock::new(|| {
    std::env::var("OPENAI_API_KEY").unwrap()
});
```
<br />

And then run the code like this:

```bash
OPENAI_API_KEY=whateverthekeyis cargo run
```
<br />

Or like this if you are using PowerShell on Windows.

```powershell
$env:OPENAI_API_KEY = "whateverthekeyis"
cargo run
```
<br />

Inside `main()`, we will then [create a client](https://docs.rs/async-openai/0.28.3/async_openai/struct.Client.html) from the async-openai crate holding this config.

```rust
let config = OpenAIConfig::new().with_api_key(KEY);
let client = Client::with_config(config);
```
<br />

We'll use that to generate an OpenAI embedding using [`text-embedding-3-small`](https://platform.openai.com/docs/guides/embeddings/embedding-models), as follows.

```rust
let input = "What does the cat chase?";

let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
let result = client.embeddings().create(request).await?;
println!("{result:?}");
```
<br />

The output in your console should show a massive number of floats, 1536 of them to be precise. That's the embedding for this input!

### Store embeddings in database

Now that we have the embedding returned from the OpenAI client, we can store it in the database. The [response](https://docs.rs/async-openai/0.28.3/async_openai/types/struct.CreateEmbeddingResponse.html) returned from the async-openai crate looks like this, with a `Vec` of `Embedding` structs that hold a `Vec<f32>`.

```rust
pub struct CreateEmbeddingResponse {
    pub object: String,
    pub model: String,
    pub data: Vec<Embedding>,
    pub usage: EmbeddingUsage,
}

pub struct Embedding {
    pub index: u32,
    pub object: String,
    pub embedding: Vec<f32>,
}
```
<br />

We know that our simple request only returned a single embedding, so `.remove(0)` will do the job. In a more complex codebase you would probably opt for a match on `.get(0)` to handle any possible errors.

```rust
let embeds = result.data.remove(0).embedding;
```
<br />

There are a [number of ways](/docs/reference/rust/concepts/flexible-typing.md) to work with or avoid structs when using the Rust SDK, but we'll just go with two basic structs: one to represent the input into a `.create()` statement, and another to show the results.

```rust
#[derive(SurrealValue)]
struct DocumentInput {
    text: String,
    embedding: Vec<f32>,
}

#[derive(Debug, SurrealValue)]
struct Document {
    id: RecordId,
    embedding: Vec<f32>,
    text: String,
}
```
<br />

Once that is done, we can print out the created documents as a `Document` struct.

```rust
let in_db = db
    .create::<Option<Document>>("document")
    .content(DocumentInput {
        text: input.into(),
        embedding: embeds.to_vec()
    })
    .await?;
println!("{in_db:?}");
```
<br />

We should now add some more `document` records. To do this, we'll move the logic to create them inside a function of its own:

```rust
async fn create_embed(
    input: &str,
    db: &Surreal<Any>,
    client: &Client<OpenAIConfig>,
) -> Result<(), Error> {
    let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
    let result = client.embeddings().create(request).await?;

    let embeds = &result.data.get(0).unwrap().embedding;

    let _in_db = db
        .create::<Option<Document>>("document")
        .content(DocumentInput {
            text: input.into(),
            embedding: embeds.to_vec(),
        })
        .await?;
    Ok(())
}
```
<br />

And then call it a few times inside `main()`. See if you can guess the answers yourself!

```rust
for input in [
    "What does the cat chase?", 
    "What do Fraggles love to eat?", 
    "Which planet rotates slowly on its axis?", 
    "Which Greek general helped Cyrus the Younger?", 
    "What is the largest inland sea?"] {
    create_embed(input, &db, &client).await?
}
```
<br />

### Semantic search

Finally let's perform semantic search over the embeddings in our database.

With that done, it's time to test the database out. We'll go with this query that uses the KNN operator to return the closest two matches to an embedding.

```surql
SELECT 
    text,
    vector::distance::knn() AS distance FROM document
    WHERE embedding <|2,COSINE|> $embeds
    ORDER BY distance;
```
<br />

You can customise this [with other algorithms](/docs/learn/data-models/vector-search/similarity-search.md#computation-on-vectors-vector-package-of-functions) such as Euclidean, Hamming, and so on.

We will then put this into a separate function called `test_embed()` which looks similar to `create_embed()`, except that it uses its embedding retrieved from OpenAI to query the database against existing documents instead of creating a new document.

```rust
async fn test_embed(
    input: &str,
    db: &Surreal<Any>,
    client: &Client<OpenAIConfig>,
) -> Result<(), Error> {
    let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
    let mut result = client.embeddings().create(request).await?;

    let embeds = result.data.remove(0).embedding;

    let mut response = db.query("SELECT text, vector::distance::knn() AS distance FROM document WHERE embedding <|2,COSINE|> $embeds ORDER BY distance;").bind(("embeds", embeds)).await?;
    let as_val: Value = response.take(0)?;
    println!("{as_val}\n");
    Ok(())
}
```
<br />

Finally, we will call this function a few times inside `main()` to confirm that the results are what we expect them to be, printing out the results of each so that we can eyeball them and make sure that they are what we expect them to be.

```rust
println!("Venus is closest to:");
test_embed("Venus", &db, &client).await?;

println!("Xenophon is closest to:");
test_embed("Xenophon", &db, &client).await?;

println!("Mice are closest to:");
test_embed("mouse", &db, &client).await?;

println!("Radishes are closest to:");
test_embed("radish", &db, &client).await?;

println!("The Caspian Sea is closest to:");
test_embed("Caspian Sea", &db, &client).await?;
```
<br />

The output shows that in each case the closest document is returned first:

* "Venus" to "Which planet rotates slowly on its axis?"
* "Xenophon" to "Which Greek general helped Cyrus the Younger?"
* "mouse" to "What does the cat chase?"
* "radish" to "What do Fraggles love to eat?", and
* "Caspian Sea" to "What is the largest inland sea?"

Success!

```text
Venus is closest to:
[{ distance: 0.6495068000978139f, text: 'Which planet rotates slowly on its axis?' }, { distance: 0.8388033444017572f, text: 'Which Greek general helped Cyrus the Younger?' }]

Xenophon is closest to:
[{ distance: 0.4421917772479055f, text: 'Which Greek general helped Cyrus the Younger?' }, { distance: 0.873354690471173f, text: 'What does the cat chase?' }]

Mice are closest to:
[{ distance: 0.6945913095506092f, text: 'What does the cat chase?' }, { distance: 0.8249335430462937f, text: 'Which planet rotates slowly on its axis?' }]

Radishes are closest to:
[{ distance: 0.7256996315669555f, text: 'What do Fraggles love to eat?' }, { distance: 0.8812784798259233f, text: 'What does the cat chase?' }]

The Caspian Sea is closest to:
[{ distance: 0.49966454922547254f, text: 'What is the largest inland sea?' }, { distance: 0.8096568276647603f, text: 'Which Greek general helped Cyrus the Younger?' }]
```
<br />

At this point, you could give the HNSW index a try by changing the `<|2,COSINE|>` in the query to something like `<|2,40|>`. The distance numbers will end up looking quite different, but the ordering of the closest neighbours will probably be the same in this small example.

<br />

Finally, here is all of the code for you to run and modify as you wish. Any questions or thoughts about this or semantic search using SurrealDB? [Join our Discord](https://discord.gg/surrealdb) - `#rust` and `#all-ai` are good places to start for this topic, and `#help` or `#general` work for anything else.

```rust
use std::sync::LazyLock;

use anyhow::Error;
use async_openai::{Client, config::OpenAIConfig, types::CreateEmbeddingRequestArgs};
use surrealdb::{
    Surreal,
    engine::any::{Any, connect}, types::{RecordId, SurrealValue, ToSql, Value},
};

static KEY: LazyLock<String> = LazyLock::new(|| std::env::var("OPENAI_API_KEY").unwrap());

#[derive(SurrealValue)]
struct DocumentInput {
    text: String,
    embedding: Vec<f32>,
}

#[derive(Debug, SurrealValue)]
struct Document {
    id: RecordId,
    embedding: Vec<f32>,
    text: String,
}

async fn create_embed(
    input: &str,
    db: &Surreal<Any>,
    client: &Client<OpenAIConfig>,
) -> Result<(), Error> {
    let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
    let mut result = client.embeddings().create(request).await?;

    let embeds = result.data.remove(0).embedding;

    let _in_db = db
        .create::<Option<Document>>("document")
        .content(DocumentInput {
            text: input.into(),
            embedding: embeds.to_vec(),
        })
        .await?;
    Ok(())
}

async fn test_embed(
    input: &str,
    db: &Surreal<Any>,
    client: &Client<OpenAIConfig>,
) -> Result<(), Error> {
    let request = CreateEmbeddingRequestArgs::default()
        .model("text-embedding-3-small")
        .input(input)
        .dimensions(1536u32)
        .build()?;
    let mut result = client.embeddings().create(request).await?;

    let embeds = result.data.remove(0).embedding;

    let mut response = db.query("SELECT text, vector::distance::knn() AS distance FROM document WHERE embedding <|2,COSINE|> $embeds ORDER BY distance;").bind(("embeds", embeds)).await?;
    let as_val: Value = response.take(0)?;
    println!("{}\n", as_val.to_sql());
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let db = connect("memory").await?;

    db.use_ns("ns").use_db("db").await?;

    let mut res = db
        .query(
            "DEFINE TABLE document;
             DEFINE FIELD text ON document TYPE string;
             DEFINE FIELD embedding ON document TYPE array<float>;
             DEFINE INDEX hnsw_embed ON document FIELDS embedding HNSW DIMENSION 1536 DIST COSINE;",
        )
        .await?;
    for (index, error) in res.take_errors() {
        println!("Error in query {index}: {error}");
    }

    let config = OpenAIConfig::new().with_api_key(&*KEY);

    let client = Client::with_config(config);

    for input in [
        "What does the cat chase?",
        "What do Fraggles love to eat?",
        "Which planet rotates slowly on its axis?",
        "Which Greek general helped Cyrus the Younger?",
        "What is the largest inland sea?",
    ] {
        create_embed(input, &db, &client).await?
    }

    println!("Venus is closest to:");
    test_embed("Venus", &db, &client).await?;

    println!("Xenophon is closest to:");
    test_embed("Xenophon", &db, &client).await?;

    println!("Mice are closest to:");
    test_embed("mouse", &db, &client).await?;

    println!("Radishes are closest to:");
    test_embed("radish", &db, &client).await?;

    println!("The Caspian Sea is closest to:");
    test_embed("Caspian Sea", &db, &client).await?;

    Ok(())
}
```

---

Source: https://surrealdb.com/docs/learn/data-models

# Data Models

Store and query document, graph, vector, time-series, geospatial. All the data models SurrealDB supports, in one database.

When you think of a database, you probably think of a table. A table is a collection of rows and columns, where each row represents a record and each column represents a field. In a relational database, these tables are connected by foreign keys, which are columns that reference the primary key of another table.

SurrealDB is a multi-model database, which means that it can store data in different formats. This flexibility allows you to choose the most appropriate data model for your use case, whether you are storing [graph](/docs/learn/data-models/graph/overview.md), [document](/docs/learn/data-models/document/overview.md), [time-series](/docs/learn/data-models/time-series/overview.md), [vector](/docs/learn/data-models/vector-search/overview.md), [full-text search](/docs/learn/data-models/full-text-search/overview.md), or [geospatial](/docs/learn/data-models/geospatial/overview.md) data.

Throughout this section, you will explore the different data models that SurrealDB supports and how they can be achieved using SurrealQL. You will also learn about how to think in SurrealDB whether you are a SQL, NoSQL, or graph developer.

## Resources

- [Document](/docs/learn/data-models/document/overview.md)
- [Graph and record links](/docs/learn/data-models/graph/overview.md)
- [Vector](/docs/learn/data-models/vector-search/overview.md)
- [Text and full-text search](/docs/learn/data-models/full-text-search/overview.md)
- [Time series](/docs/learn/data-models/time-series/overview.md)
- [Geospatial](/docs/learn/data-models/geospatial/overview.md)

---

Source: https://surrealdb.com/docs/learn/data-models/architecture

# Architecture

How SurrealDB separates compute from storage. Every data model maps onto one storage engine, and namespaces, databases and tables give the structure.

SurrealDB separates the query engine (compute) from the storage layer. The engine speaks one query language and exposes one API, while the storage layer decides how data is persisted, replicated and scaled. Because the two are decoupled, the same database, queries and SDK calls work from an embedded edge application through to a distributed cloud cluster.

<img src="~/assets/img/image/cloud/light/architecture-light.png" darkSrc="~/assets/img/image/cloud/architecture-dark.png" alt="Diagram of SurrealDB's layered architecture: clients connect through [client].surrealdb.com to multiple compute nodes, all backed by centralised storage on AWS S3." />

This page describes the two layers, how the [data models](/docs/learn/data-models.md) share a single store, and how a SurrealDB deployment is organised.

## Query layer

The query layer handles client requests and coordinates work against storage:

- Parses and executes [SurrealQL](/docs/reference/query-language.md)
- Authenticates connections and sessions, for example through `SIGNIN` and access methods
- Enforces table- and field-level `PERMISSIONS` as records are read and written
- Plans index-backed queries, updates index entries on writes, and coordinates [transactions](/docs/learn/querying/concepts-and-guides/transactions.md) against storage

Incoming SurrealQL passes through a parser, an executor that groups statements into transactions, an iterator that plans data access and fetches keys from storage, and a document processor that applies permissions and persists changes through the storage API.

Every transaction runs under snapshot isolation with write conflict detection on commit, whichever storage backend sits underneath. A [`SELECT ... FOR UPDATE`](/docs/reference/query-language/statements/select.md#the-for-update-clause) read extends that detection to records the transaction reads without writing.

## Storage layer

The storage layer handles persistence and durability. It determines deployment characteristics such as scalability, temporal versioning, replication and fault tolerance. SurrealDB integrates with several engines depending on how you run the database:

| Concern | Typical engine |
| --- | --- |
| Single-node production | [RocksDB](https://rocksdb.org/) (recommended for single node) |
| Single-node or embedded | [SurrealKV](https://github.com/surrealdb/surrealkv) (beta) |
| In-memory server or embedded | [SurrealMX](https://github.com/surrealdb/surrealmx) |
| Browser persistence | [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) |
| Distributed multi-node | Shared distributed storage on [SurrealDB Cloud Scale](https://surrealdb.com/pricing/scale) and self-hosted Enterprise |

Each engine must support transactional read and write of individual keys and key ranges. That is the whole contract, which is why the query layer can offer identical semantics across every deployment model.

## One store for every data model

SurrealDB is a document database at its core. Each record is a document held on a key-value engine, and it can carry arbitrary nested objects and arrays.

The other models come from how those keys are laid out rather than from separate subsystems:

- **[Record IDs](/docs/reference/query-language/language-primitives/data-types/record-ids.md)** sort in a defined order, so a range read over a table returns records in that order. This is what makes [time-series](/docs/learn/data-models/time-series/overview.md) access a range scan rather than a scan-and-filter.
- **Graph edges** are records of their own, created with [`RELATE`](/docs/reference/query-language/statements/relate.md). Traversal reads the edge table by key, so a [graph query](/docs/learn/data-models/graph/overview.md) stays a series of key lookups.
- **Indexes** for [vector](/docs/learn/data-models/vector-search/overview.md) and [full-text](/docs/learn/data-models/full-text-search/overview.md) search are index entries in the same store, maintained by the query layer as records change.

One consequence matters for application design: a write that touches a document, its edges and its index entries is one transaction against one store, so those parts cannot drift apart.

## System structure

SurrealDB is a multi-tenant platform. Resources nest in four levels, and each level has its own [`DEFINE`](/docs/reference/query-language/statements/define/overview.md) statement:

| Level | Statement | Purpose |
| --- | --- | --- |
| [Namespace](/docs/reference/query-language/statements/define/namespace.md) | `DEFINE NAMESPACE` | Isolation for an organisation, department or team. No limit on the number of namespaces. |
| [Database](/docs/reference/query-language/statements/define/database.md) | `DEFINE DATABASE` | The unit that holds data. Each database has its own tables, indexes, schema, settings and permissions. No limit per namespace. |
| [Table](/docs/reference/query-language/statements/define/table.md) | `DEFINE TABLE` | A collection of records. Called a collection in some other systems. |
| [Field](/docs/reference/query-language/statements/define/field.md) | `DEFINE FIELD` | A typed field on a table, with optional assertions and permissions. |

A row or document in SurrealDB is called a **record**, and a column is called a **field**. Records are [created](/docs/reference/query-language/statements/create.md), [read](/docs/reference/query-language/statements/select.md), [updated](/docs/reference/query-language/statements/update.md) and [deleted](/docs/reference/query-language/statements/delete.md), with further statements for common patterns: [`UPSERT`](/docs/reference/query-language/statements/upsert.md) writes a record whether or not it already exists, and [`RELATE`](/docs/reference/query-language/statements/relate.md) links two records through an edge table.

### Namespaces and databases

A namespace is the outer container. It holds databases and nothing else, which makes it the natural boundary in multi-tenant deployments where separate applications or teams share one instance or cluster. Permissions and access methods can be granted at namespace level rather than per database.

A database sits inside a namespace and is where data lives: tables, records, indexes, events, functions and access methods. Most work happens here.

Both are defined with a unique name and an optional comment. The [`USE`](/docs/reference/query-language/statements/use.md) statement switches the session from one namespace or database to another.

```surql
DEFINE NAMESPACE dev_namespace COMMENT "Internal use only: do not use in prod";
USE NAMESPACE dev_namespace;
-- Now inside 'dev_namespace', define a database within it
DEFINE DATABASE dev_db_1 COMMENT "First of many dev databases";
```

Multiple [access](/docs/reference/query-language/statements/define/access.md) methods can be defined on a namespace or a database. The [record](/docs/reference/query-language/statements/define/access/record.md) access method authenticates your end users against your own tables, down to field level.

### Inspecting a deployment

The [`INFO`](/docs/reference/query-language/statements/info.md) statement reports what exists at each level:

- `INFO FOR ROOT`: namespaces, root users, and system information such as allocated memory and physical cores.
- `INFO FOR NAMESPACE`: the namespace's databases, users and access methods.
- `INFO FOR DATABASE`: the database's tables, users, accesses, functions, analysers and more.

Each resource is reported as the `DEFINE` statement that would recreate it, which makes the output a readable snapshot of the schema:

```surql title="Abridged output of INFO FOR DATABASE"
{
	accesses: {  },
	analyzers: {
		blank_snowball: 'DEFINE ANALYZER blank_snowball TOKENIZERS BLANK FILTERS LOWERCASE, SNOWBALL(ENGLISH)'
	},
	functions: {
		pound_to_usd: 'DEFINE FUNCTION fn::pound_to_usd($price: number) -> float { $price * 1.26f } PERMISSIONS FULL'
	},
	tables: {
		order: 'DEFINE TABLE order TYPE RELATION IN person OUT product SCHEMAFULL PERMISSIONS NONE',
		user: 'DEFINE TABLE user TYPE ANY SCHEMALESS PERMISSIONS NONE'
	},
	users: {
		Boris: "DEFINE USER Boris ON DATABASE PASSHASH '[REDACTED]' ROLES VIEWER DURATION FOR TOKEN 1h, FOR SESSION NONE"
	}
}
```

Further `INFO` statements report the state of individual tables, users and indexes.

## Deployment models

The separation of compute from storage gives four ways to run the same database:

- **Embedded**, in memory through SurrealMX, on disk through RocksDB or SurrealKV, or in the browser through IndexedDB.
- **Single-node self-hosted**, on RocksDB, or on SurrealKV while it is in beta.
- **Multi-node self-hosted**, on [managed Kubernetes](/docs/manage/self-hosted/managed-kubernetes.md) such as EKS, GKE or AKS.
- **Managed**, through [managed instances](/docs/manage/instances.md), from single-node **Start** instances to multi-node **Scale** clusters on distributed storage.

Development can begin on an embedded or single-node deployment and move to a cluster later without changing application code or queries.

For storage trade-offs and how to choose a model, see [Deployment models](/docs/manage/self-hosted/deployment-models.md).

## Where SurrealDB sits in your stack

SurrealDB works as a conventional database behind a backend service, using the SDKs for Go, Python, Rust, C, Java, .NET, Node.js or PHP.

It can also serve a frontend directly. Table-, record- and field-level permissions, combined with [record access](/docs/reference/query-language/statements/define/access/record.md) authentication, let a browser or mobile client connect to the database and still only see the data it is entitled to. The JavaScript SDK, WebAssembly and the framework integrations for React, Next.js, Vue, Svelte and others support this arrangement.

Both approaches use the same query language and the same permission model, so a project can start with one and add the other later.

---

Source: https://surrealdb.com/docs/learn/data-models/document/common-patterns

# Common patterns

Map document-database concepts to SurrealDB, compare SurrealQL with MongoDB-style operations, and find resources for CRUD and migration.

When thinking in a document model database, you will often find that the concepts align closely with SurrealDB. The table below maps common terms.

## Concept mapping

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Document model</th>
            <th colspan="2" scope="col">SurrealDB</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Document model">
              database
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                database
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Document model">
                collection
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                table
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Document model">
                document
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                record
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Document model">
                field
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                field
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Document model">
                index
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                index
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Document model">
                Objectid
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                record id
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Document model">
                transactions
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                transactions
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Document model">
                reference and embedding
            </td>
            <td colspan="2" scope="row" data-label="SurrealDB">
                record links, embedding and graph relations
            </td>
        </tr>
    </tbody>
</table>

## Benefits of using a document model database

1. **Flexibility**: You don’t need to define rigid schemas in advance. Changes to data structure are often just changes in the JSON object itself.

2. **Natural data representation**: Since you’re working with JSON-like objects, document databases align well with modern programming languages that manipulate data as objects or dictionaries.

3. **Simplicity of application code**: Because you can embed everything related to an entity in a single document, you often have fewer JOINs (or complex queries) and simpler code for retrieving complete objects.

4. **Easier horizontal scaling**: Many document databases are built for horizontal partitioning (sharding), making them easier to scale for large workloads.

## Using MongoDB syntax as a reference

As a multi-model database, SurrealDB offers a lot of flexibility. SurrealQL often provides more than one way to achieve the same result, depending on developer preference. The mapping below focuses on syntax that most closely resembles the MongoDB query language (MQL).

For SurrealQL equivalents to MongoDB data types and how to import MongoDB data into SurrealDB, see the Surreal Sync migration tool:

* [Surreal Sync for MongoDB](https://github.com/surrealdb/surreal-sync/blob/main/docs/mongodb.md)
* [MongoDB data types support in Surreal Sync](https://github.com/surrealdb/surreal-sync/blob/main/docs/mongodb-data-types.md)

## Syntax mapping

### Create

As MongoDB is schemaless, only the SurrealQL schemaless approach is shown below. For a schemafull option, see the [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) page.

For more SurrealQL examples, see the [`CREATE`](/docs/reference/query-language/statements/create.md) and [`INSERT`](/docs/reference/query-language/statements/insert.md) pages.

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">MQL</th>
            <th colspan="2" scope="col">SurrealQL</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.createCollection("person")
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                CREATE person
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.insertMany([{ name: "John" }, { name: "Jane" }])
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                INSERT INTO person [{name: "John"}, {name: "Jane"}]
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.createIndex({ name: 1 })
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                DEFINE INDEX idx_name ON TABLE person FIELDS name
            </td>
        </tr>
    </tbody>
</table>

### Read

For more SurrealQL examples, see the [`SELECT`](/docs/reference/query-language/statements/select.md), [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md) and [`RETURN`](/docs/reference/query-language/statements/return.md) pages.

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">MQL</th>
            <th colspan="2" scope="col">SurrealQL</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.find()
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                SELECT * FROM person
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.find({}, { _id: 0, name: 1 })
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                SELECT name FROM person
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.find({ name: “Jane” }, { _id: 0, name: 1 })
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                SELECT name FROM person WHERE name = "Jane"
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.find({ name: “Jane” }, { _id: 0, name: 1 }).explain()
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                SELECT name FROM person WHERE name = "Jane" EXPLAIN
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.aggregate([{ $count: “personCount” }])
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                SELECT count() AS person_count FROM person GROUP ALL
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.aggregate([{ $group: { _id: “$name” } }])
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                SELECT array::distinct(name) FROM person GROUP ALL
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.find().limit(10)
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                SELECT * FROM person LIMIT 10
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.review.aggregate([{ “$lookup”: { “from”: “person”, “localField”: “person”, “foreignField”: “_id”, “as”: “person_detail” } }])
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                SELECT *, person.name as reviewer FROM review
            </td>
        </tr>
    </tbody>
</table>

### Update

For more SurrealQL examples, see the [`UPDATE`](/docs/reference/query-language/statements/update.md) page.

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">MQL</th>
            <th colspan="2" scope="col">SurrealQL</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.updateMany({ name: “Jane” }, { $set: { last_name: “Doe” } })
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                UPDATE person SET last_name = "Doe" WHERE name = "Jane"
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.updateMany({ name: “Jane” }, { $unset: { last_name: 1 } })
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                UPDATE person UNSET last_name WHERE name = "Jane"
            </td>
        </tr>
    </tbody>
</table>

### Delete

For more SurrealQL examples, see the [`DELETE`](/docs/reference/query-language/statements/delete.md) and [`REMOVE`](/docs/reference/query-language/statements/remove.md) pages.

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">MQL</th>
            <th colspan="2" scope="col">SurrealQL</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.deleteMany({ name: “Jane” })
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                DELETE person WHERE name = "Jane"
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.deleteMany({})
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                DELETE person
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="MQL">
                db.person.drop()
            </td>
            <td colspan="2" scope="row" data-label="SurrealQL">
                REMOVE TABLE person
            </td>
        </tr>
    </tbody>
</table>

## Resources

- [`CREATE` statement](/docs/reference/query-language/statements/create.md)
- [`SELECT` statement](/docs/reference/query-language/statements/select.md)
- [`UPDATE` statement](/docs/reference/query-language/statements/update.md)
- [`DELETE` statement](/docs/reference/query-language/statements/delete.md)
- [`RELATE` statement](/docs/reference/query-language/statements/relate.md)
- [SurrealQL documentation](/docs/reference/query-language.md)
- [SurrealDB University](/learn)

---

Source: https://surrealdb.com/docs/learn/data-models/document/nested-objects-and-arrays

# Nested objects and arrays

Store nested JSON-like structures in SurrealDB records, use SurrealQL examples with addresses and record links, and relate documents via fields such as article authors.

Documents in SurrealDB behave like structured objects you might already use in application code: fields can hold primitives, nested objects, and arrays of values.

## Example: a user with nested addresses

The example below creates a `users` record with an `addresses` array of objects.

```surql
CREATE users CONTENT {
    name: 'Alice Smith',
    email: 'alice@example.com',
    age: 29,
    addresses: [
        {
            type: 'home',
            address_line: '123 Maple St',
            city: 'Springfield',
            country: 'USA'
        },
        {
            type: 'work',
            address_line: '456 Oak Ave',
            city: 'Metropolis',
            country: 'USA'
        }
    ]
};
```

By clicking the **Run query** button, you will see a result similar to:

```surql
[
	{
		addresses: [
			{
				address_line: '123 Maple St',
				city: 'Springfield',
				country: 'USA',
				type: 'home'
			},
			{
				address_line: '456 Oak Ave',
				city: 'Metropolis',
				country: 'USA',
				type: 'work'
			}
		],
		age: 29,
		email: 'alice@example.com',
		id: 'users:a2ndbh1hsquvkvthws09',
		name: 'Alice Smith'
	}
]
```

You may notice that the `id` field has a `users:` prefix. This is because SurrealDB uses an [id](/docs/reference/query-language/language-primitives/data-types/record-ids.md) to uniquely identify each record, and the combination of the table name and the record id is used as the [record link](/docs/reference/query-language/language-primitives/record-links.md).

## Embedding and linking

Document model databases are designed to store data in a flexible, nested structure. **Data organisation** often means self-describing documents in JSON or a similar format. Relationships can be represented **inside** the document (embedding) or via **references** using record links to other documents.

For example, if you wanted to associate a `person` with an `article` they wrote, you could assign the person's ID to the `author` field of the article document. This binds the `person` and `article` together, allowing you to query the `article` by the `person`'s ID.

```surql
CREATE article SET
	created_at = time::now(),
	author = person:john,
	title = 'Lorem ipsum dolor',
	text = 'Donec eleifend, nunc vitae commodo accumsan, mauris est fringilla.';
CREATE person:john SET
	name.first = 'John',
	name.last = 'Adams',
	name.full = string::join(' ', name.first, name.last),
	age = 29,
	admin = true,
	signup_at = time::now()
;
```

## Retrieving documents

To read documents back, use a normal `SELECT`. For example, to return every field from the `users` table:

```surql
SELECT * FROM users;
```

SurrealDB automatically generates a unique [`id`](/docs/reference/query-language/language-primitives/data-types/record-ids.md) for each document unless you supply your own identifiers.

For more on schema options and CRUD patterns, see [Schema modes](/docs/learn/data-models/document/schema-modes.md) and [Common patterns](/docs/learn/data-models/document/common-patterns.md).

---

Source: https://surrealdb.com/docs/learn/data-models/document/overview

# Document model

Learn how to think in a document model, how SurrealDB maps tables and records to documents, and where to find guides on nested data, schema modes, and common patterns including SurrealQL and MongoDB-style mappings.

One of the most popular database models is that of a document database. It provides a flexible way to store data, allowing for nested structures and relationships to be stored within a single document.

In a document database, data is stored in the form of documents (which usually resemble JSON objects) rather than in rows and columns.

This model offers a level of simplicity and flexibility that can be especially appealing when your data does not naturally fit into a strict tabular format or when the structure of your data frequently changes.

Over the last decade, we have seen a surge of NoSQL databases such as [MongoDB](https://www.mongodb.com/), [CouchDB](https://couchdb.apache.org/), and [DynamoDB](https://aws.amazon.com/dynamodb/), all of which are in the broad category of document stores (although with varying specific features).

Thinking in a document model means orienting your data design around the entities as you naturally represent them in your applications, rather than forcing your data into normalised or heavily structured relational schemas.

## Core concepts of document-oriented modelling

In a document-oriented system you typically work with:

- **Self-describing records**: Data is grouped into records that carry their own structure (fields, nested objects, arrays), often similar to JSON in application code.
- **Embedding vs linking**: Related data can live inside the same document (embedding) or be referenced with [record links](/docs/reference/query-language/language-primitives/record-links.md) or graph-style relations when you need shared entities or strict edges.
- **Flexible schema**: Tables can mix different shapes when you choose a schemaless workflow, or you can tighten definitions with [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) and field types when you need validation and tooling support.

## Where to go next

The guides in this section break these ideas down in more detail:

- [Nested objects and arrays](/docs/learn/data-models/document/nested-objects-and-arrays.md): worked examples with nested structures, record IDs, and linking documents.
- [Schema modes](/docs/learn/data-models/document/schema-modes.md): creating and retrieving documents, and how schemaless usage compares to defining a schema.
- [Common patterns](/docs/learn/data-models/document/common-patterns.md): concept mapping to other document stores, benefits, MongoDB-style SurrealQL examples, and further resources.

For statement-level reference and CRUD details, see the [`CREATE`](/docs/reference/query-language/statements/create.md), [`SELECT`](/docs/reference/query-language/statements/select.md), [`UPDATE`](/docs/reference/query-language/statements/update.md), and [`DELETE`](/docs/reference/query-language/statements/delete.md) statements, and the [SurrealQL documentation](/docs/reference/query-language.md).

---

Source: https://surrealdb.com/docs/learn/data-models/document/schema-modes

# Schema modes

Create flexible documents without upfront field definitions, or tighten schemas with DEFINE TABLE; add and retrieve JSON-like records in SurrealQL.

SurrealDB supports both **schemaless** workflows (insert records of varying shape into the same table) and **schemafull** definitions when you want validation and clearer contracts. This page focuses on the schemaless style that feels closest to typical document databases.

## Schema flexibility

Document model databases are often chosen because:

1. **Data organisation**: Data is contained in self-describing documents, typically in JSON or a similar format. Relationships can be done within the document itself (embedding) or via [record links](/docs/reference/query-language/language-primitives/record-links.md) to other documents.

2. **Schema flexibility**: Schemas are often flexible, allowing for documents of varying shapes in the same collection (or table-like structure). When you need stricter rules, you can use [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) and [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md).

## Creating and managing documents

In SurrealDB, you can create a database and then store collections of documents (often referred to as “tables”) without strict schema definitions. This is conceptually similar to creating a table in a relational database, but you do not need to define all columns upfront. Instead, you can insert JSON-like objects directly.

### Adding a document

```surql
CREATE users CONTENT {
    name: "Alice Smith",
    email: "alice@example.com",
    age: 29,
    addresses: [
        {
            type: "home",
            address_line: "123 Maple St",
            city: "Springfield",
            country: "USA"
        },
        {
            type: "work",
            address_line: "456 Oak Ave",
            city: "Metropolis",
            country: "USA"
        }
    ]
};
```

Here, a user document includes nested objects (`addresses`) in the same record. You can add nested objects or properties without modifying a central schema first.

### Retrieving documents

```surql
SELECT * FROM users;
```

The query returns all documents in the `users` table, similar to a traditional SQL `SELECT`:

```surql
[
	{
		addresses: [
			{
				address_line: '123 Maple St',
				city: 'Springfield',
				country: 'USA',
				type: 'home'
			},
			{
				address_line: '456 Oak Ave',
				city: 'Metropolis',
				country: 'USA',
				type: 'work'
			}
		],
		age: 29,
		email: 'alice@example.com',
		id: 'users:xyz123',
		name: 'Alice Smith'
	}
]
```

SurrealDB automatically generates a unique [`id`](/docs/reference/query-language/language-primitives/data-types/record-ids.md) for the document. You can also specify your own custom IDs if you prefer more human-readable or domain-specific identifiers.

## Next steps

For concept mapping to other document stores and MongoDB-style SurrealQL examples, see [Common patterns](/docs/learn/data-models/document/common-patterns.md).

---

Source: https://surrealdb.com/docs/learn/data-models/full-text-search/analyzers-and-tokenizers

# Analyzers and tokenizers

Learn how analyzers turn raw text into searchable tokens: tokenizers, filters, and testing with search::analyze before you build an index.

Full-text search does not compare your query string to the document byte for byte. Instead, SurrealDB tokenises text into terms, optionally filters those terms (case folding, stemming, and more), and indexes what comes out.

If you are new to FTS in SurrealDB, read the [overview](/docs/learn/data-models/full-text-search/overview.md) first. This guide walks through analyzers from the ground up; exact grammar, every clause, and diagrams live under [`DEFINE ANALYZER`](/docs/reference/query-language/statements/define/analyzer.md).

## What an analyzer does

Roughly, processing flows like this:

1. Optional `FUNCTION`: transforms the raw input string once (for example normalising punctuation or stripping markup) via a [user-defined function](/docs/reference/query-language/statements/define/function.md) that accepts and returns a `string`.
2. Tokenizers - split the string into tokens (words, symbols, or other chunks) using one or more built-in tokenizers.
3. Filters - transform each token (lowercase, strip accents, stem, n-grams, and so on).

The same analyzer is used when indexing and matching queries, so spending time here pays off for relevance and performance.

## See the tokens before you index

Use [`search::analyze()`](/docs/reference/query-language/functions/database-functions/search.md#searchanalyze) to print the token array an analyzer would produce, which is ideal for experimentation.

Start with the simplest split, whitespace-only tokenization:

```surql
DEFINE ANALYZER words TOKENIZERS blank;

RETURN search::analyze("words", "hello   world");
```

```surql title="Output"
[
	'hello',
	'world'
]
```

Once you are happy with the tokens, you attach the analyzer name to a full-text index and query with `@@` (covered on [Search indexes](/docs/learn/data-models/full-text-search/search-indexes.md) and [Scoring and ranking](/docs/learn/data-models/full-text-search/scoring-and-ranking.md)).

## Step 1 - Choose how to split text (tokenizers)

Tokenizers answer: *where are the boundaries between tokens?* Some examples of tokenizers are `blank`, `camel`, and `class`.

```surql
DEFINE ANALYZER example_blank TOKENIZERS blank;
search::analyze("example_blank", "hello world");

DEFINE ANALYZER example_camel TOKENIZERS camel;
search::analyze("example_camel", "helloWorld");

DEFINE ANALYZER example_class TOKENIZERS class;
search::analyze("example_class", "123abc!XYZ");
```

These all decide a boundary from the characters around it, which is enough for languages that separate words with spaces or case changes. Chinese, Japanese and Korean need a dictionary instead: `segment(chinese | japanese | korean)`, documented under [DEFINE ANALYZER](/docs/reference/query-language/statements/define/analyzer.md#segmentlanguage). Korean attaches grammatical particles to the word they follow, so `한국어를` only becomes searchable as `한국어` once it is segmented:

```surql
DEFINE ANALYZER example_korean TOKENIZERS blank,segment(korean);
search::analyze("example_korean", "한국어를 배우고 있습니다");
```

```surql title="Output"
[
	'한국어',
	'를',
	'배우',
	'고',
	'있',
	'습니다'
]
```

## Step 2 - Normalise and enrich tokens (filters)

Filters answer: *what should each token look like before indexing?*

Some examples of filters are `ascii`, `snowball`, and `ngram`.

```surql
DEFINE ANALYZER example_ascii TOKENIZERS class FILTERS ascii;
search::analyze("example_ascii", "résumé café");

DEFINE ANALYZER english_snowball TOKENIZERS class FILTERS
  snowball(english);
DEFINE ANALYZER german_snowball TOKENIZERS class FILTERS
  snowball(german);

RETURN [
    search::analyze("english_snowball",
      "Looking at some running cats"),
    search::analyze("german_snowball",
      "Sollen wir was trinken gehen?")
];

DEFINE ANALYZER example_ngram TOKENIZERS class FILTERS ngram(1, 3);
search::analyze("example_ngram", "apple banana");
```

## Custom dictionaries with `mapper(path)`

The `mapper(path)` filter rewrites tokens using a tab-separated file: canonical form first, variant second, one pair per line. That supports lemmatisation beyond what stemming alone catches, or normalising arbitrary phrasing (for example mapping multilingual error strings to a single code).

The server reads the dictionary from the **host filesystem** when you define the analyzer. Configure [`SURREAL_FILE_ALLOWLIST`](/docs/reference/cli/db/environment-variables.md#file-config) so the path lies under an allowed directory, without which no filesystem paths are permitted. See [DEFINE ANALYZER - `mapper(path)`](/docs/reference/query-language/statements/define/analyzer.md#mapperpath) for startup examples.

Point `path` at a dictionary file under your allowlist. Here is a very short example dictionary:

```text
drive	driven
drive	drives
swim	swam
```

An analyzer making use of this dictionary can be defined as follows:

```surql
DEFINE ANALYZER lemme_english TOKENIZERS blank,
  class FILTERS lowercase,
  mapper('/path/to/lemmatization-en.txt');

RETURN [
    search::analyze("lemme_english", "He drove and swam"),
];
```

## Next steps

- [Search indexes](/docs/learn/data-models/full-text-search/search-indexes.md) - attach `FULLTEXT ANALYZER` to a field.
- [Scoring and ranking](/docs/learn/data-models/full-text-search/scoring-and-ranking.md) - `@@`, BM25, `search::score`, and highlights.
- Reference: [`DEFINE ANALYZER`](/docs/reference/query-language/statements/define/analyzer.md), [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md), [Search functions](/docs/reference/query-language/functions/database-functions/search.md).

### Updating or creating analyzers safely

To add an analyzer only if it is missing, or to replace an existing definition, use `IF NOT EXISTS` or `OVERWRITE` on `DEFINE ANALYZER`. Examples and caveats are in the [statement reference](/docs/reference/query-language/statements/define/analyzer.md#using-if-not-exists-clause).

---

Source: https://surrealdb.com/docs/learn/data-models/full-text-search/other-ways-to-work-with-text

# Other ways to work with text

Compare and sort text with COLLATE and NUMERIC, use contains and fuzzy string metrics, regex matching, and how these complement full-text search and hybrid retrieval.

Besides [full-text search](/docs/learn/data-models/full-text-search/overview.md), SurrealDB offers many tools for comparing, ordering, and matching strings: sorting with `COLLATE` / `NUMERIC`, substring checks, distance and similarity scores, regex, and ad-hoc string helpers. This page summarises those options so you can pick the right tool next to your FTS indexes and analyzers.

## Comparing and sorting text

### In SELECT queries

The following example shows a few records created from an array of strings in an order that is sorted to the human eye: lowest to highest numbers, then A to Z.

```surql
FOR $word IN ['1', '2', '11', 'Ábaco', 'kitty', 'Zoo'] {
	CREATE data SET val = $word;
};
```

Inside a `SELECT` query, an `ORDER BY` clause can be used to order the output by one or more field names. For the above data, an ordered `SELECT` query looks like this.

```surql
SELECT VALUE val FROM data ORDER BY val;
```

However, in the case of strings, sorting is done by Unicode ordering which often leads to output that seems out of order to the human eye. The output of the above query shows the following:

```surql title="Output"
[ '1', '11', '2', 'Zoo', 'kitty', 'Ábaco' ]
```

This is because:

* '11' is ordered before '2', because the first character in the string '2' is greater than the first character in the string '1'.
* 'Zoo' is ordered before 'kitty', because the first character in the string 'Zoo' is 'Z', number 0059 in the [list of Unicode characters](https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin). A lowercase 'k' is 0076 on the list and thus "greater", while the 'Á', registered as the "Latin Capital letter A with acute", is 0129 on the list.

To sort strings in a more natural manner to the human eye, the keywords [`NUMERIC` and `COLLATE` (or both) can be used](/docs/reference/query-language/statements/select.md#sort-records-using-the-order-by-clause). `NUMERIC` will instruct strings that parse into numbers to be treated as such.

```surql
SELECT VALUE val FROM data ORDER BY val NUMERIC;
-- Output:
['1', '2', '11', 'Zoo', 'kitty', 'Ábaco']
```

`COLLATE` instructs unicode strings to sort by alphabetic order, rather than Unicode order.

```surql
SELECT VALUE val FROM data ORDER BY val COLLATE;
-- Output:
['1', '11', '2', 'Ábaco', 'kitty', 'Zoo']
```

And for the data in this example, `COLLATE NUMERIC` is likely what will be desired.

```surql
SELECT VALUE val FROM data ORDER BY val COLLATE NUMERIC;
-- Output:
['1', '2', '11', 'Ábaco', 'kitty', 'Zoo']
```

### Sorting functions

The functions [`array::sort_natural()`, `array::sort_lexical()`, and `array::sort_natural_lexical()`](/docs/reference/query-language/functions/database-functions/array.md) can be used on ad-hoc data to return the same output as the `COLLATE` and `NUMERIC` clauses in a [`SELECT` statement](/docs/reference/query-language/statements/select.md).

## Contains functions and operators

The most basic way to see if one string is contained inside another is to use the `IN` operator, or the [`string::contains()` function](/docs/reference/query-language/functions/database-functions/string.md#stringcontains).

```surql
"Umple" IN "Rumplestiltskin";
//- false
string::contains("Rumplestiltskin", "Umple");
//- false
-- Same function using method syntax
"Rumplestiltskin".contains("Umple");
//- false

"umple" IN "Rumplestiltskin";
//- true
string::contains("Rumplestiltskin", "umple");
//- true
"Rumplestiltskin".contains("umple");
//- true
```

SurrealDB has a number of [operators](/docs/reference/query-language/language-primitives/operators.md) to determine if all or some of the values of one array are contained in another, such as `CONTAINSALL` and `CONTAINSANY`, or `ALLINSIDE` and `ANYINSIDE`. The queries with `CONTAINS` and `INSIDE` perform the same operation, just in the opposite order.

```surql
-- If 1,2,3 contains each item in 1,2...
[1,2,3] CONTAINSALL [1,2];
-- then each item in 1,2 is inside 1,2,3
[1,2] ALLINSIDE [1,2,3];
```

Because strings are essentially arrays of characters, these operators work with strings as well. Both of these queries will return `true`:

```surql
"Rumplestiltskin" CONTAINSALL ["umple", "kin"];
"kin" ALLINSIDE "Rumplestiltskin";
["kin", "someotherstring"] ANYINSIDE "Rumplestiltskin";
```

## Equality and fuzzy equality

SurrealDB offers quite a few algorithms inside the [string functions module](/docs/reference/query-language/functions/database-functions/string.md) for distance or similarity comparison. They are:

* `string::distance::damerau_levenshtein()`
* `string::distance::normalized_damerau_levenshtein()`
* `string::distance::hamming()`
* `string::distance::levenshtein()`
* `string::distance::normalized_levenshtein()`
* `string::distance::osa()`

* `string::similarity::jaro()`
* `string::similarity::jaro_winkler()`

Which of these functions to choose depends on your personal use case.

For example, fuzzy similarity and distance scores are not a measure of absolute equality and ordered similarity scores should only be used in comparisons against the same string. Take the following queries for example which return the score for the string "United" and "Unite":

```surql
-- return 131 and 111
string::similarity::fuzzy("United Kingdom", "United");
string::similarity::fuzzy("United Kingdom", "Unite");

-- also return 131 and 111
string::similarity::fuzzy("United", "United");
string::similarity::fuzzy("United", "Unite");
```

While the word "Unite" is clearly closer to the word "United" than it is to "United Kingdom", the algorithm used for this function only considers how much of the second string is found in the first string.

However, the `string::similarity::jaro()` function returns an output that approaches 1 if two strings are equal, making it a more apt solution when the first and second string may be entirely different. Using the same input strings as above shows that "Unite" is clearly the most similar of the strings that are not outright equal to "United".

```surql
string::similarity::jaro("United Kingdom", "United");
//- 0.8095238095238096f
string::similarity::jaro("United Kingdom", "Unite");
//- 0.7857142857142857f
string::similarity::jaro("United", "United");
//- 1
string::similarity::jaro("United", "Unite");
//- 0.9444444444444445f
```

Another example of the large difference between algorithms is the Hamming distance algorithm, which only compares strings of equal length.

```surql
string::distance::hamming("United Kingdom", "United");
//- Error: strings have different length
string::distance::hamming("United", "United");
//- 0
string::distance::hamming("United", "Unitéd");
//- 1
string::distance::hamming("United", "uNITED");
//- 6
```

## Regex matching

The `string::matches()` function can be used to perform regex matching on a string.

```surql
string::matches("Cat", "[HC]at");
//- true
string::matches("Hat", "[HC]at");
//- true
```

## Other string functions

SurrealDB has a large number of [string functions](/docs/reference/query-language/functions/database-functions/string.md) that can be used manually to refine string searching, such as `string::lowercase()`, `string::starts_with()`, and `string::ends_with()`.

```surql
SELECT 
    $this AS word, 
    $this.lowercase() = "sleek" AS is_sleek
FROM ["sleek", "SLEEK", "Sleek", "sleeek"];
```

```surql title="Output"
[
	{
		is_sleek: true,
		word: 'sleek'
	},
	{
		is_sleek: true,
		word: 'SLEEK'
	},
	{
		is_sleek: true,
		word: 'Sleek'
	},
	{
		is_sleek: false,
		word: 'sleeek'
	}
]
```

## Combining full-text and vector search

SurrealDB has functions that combine full-text and vector search results. For more on that pattern, see [Hybrid search](/docs/learn/data-models/vector-search/hybrid-search.md) in the vector search section.

## Resources

- [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md)
- [`DEFINE ANALYZER`](/docs/reference/query-language/statements/define/analyzer.md)
- [Search functions](/docs/reference/query-language/functions/database-functions/search.md)
- [SurrealDB search functions](/docs/reference/query-language/functions/database-functions/search.md)
- [SurrealDB operators](/docs/reference/query-language/language-primitives/operators.md)
- Blog post: [Create a Search Engine with SurrealDB full-text Search](/blog/create-a-search-engine-with-surrealdb-full-text-search)

---

Source: https://surrealdb.com/docs/learn/data-models/full-text-search/overview

# Full-text search model

Learn how full-text search differs from literal matching, why SurrealDB fits FTS workloads, and where to find guides on analyzers, indexes, scoring, and non-FTS text operations.

A full-text search database is designed to index and retrieve text-based data (like articles, messages, or comments) based on tokenized and modified parts of the text itself, rather than exact, literal matches. This allows you to:

* Find documents containing certain keywords.
* Search for phrases or words with variants (e.g., “run,” “runs,” “running”).
* Rank results by relevance, not just by literal string matches.

As a multi-model database, SurrealDB has integrated full-text search capabilities so that you can store your data and query it with advanced text search features.

Note: SurrealDB has many other built-in ways of working with text besides full-text search. For more details, see [Other ways to work with text](/docs/learn/data-models/full-text-search/other-ways-to-work-with-text.md).

## How full-text search differs from keyword matching

In traditional databases, you might do something like:

```surql
SELECT * FROM articles WHERE 'fox' IN title;
```

This approach:

- Doesn’t rank results by relevance; it just returns every article containing “fox.”
- Ignores language variations, e.g., “Foxes,” “FoX,” or synonyms like “vixen.”
- May scan an entire table, making it slower for large datasets.

Full-text search, by contrast, uses an inverted index or other specialised structures for fast lookups and can handle a variety of linguistic transformations. It can highlight results and rank them by how relevant or frequent the terms are.

## Advantages of SurrealDB for FTS

- **Unified model**: You can keep your data, relationships, and search logic in a single engine.
- **Flexible schema**: SurrealDB can be schemaless, so adding new fields or text columns doesn’t require schema migrations.
- **Query language**: SurrealQL blends SQL-like syntax with searching syntax (the [`@@` matching operator](/docs/reference/query-language/language-primitives/operators.md#matches-a-idmatchesa) for FTS queries, advanced indexing features, and so on).
- **Real-time updates**: SurrealDB can handle real-time changes, so newly inserted or updated text becomes searchable quickly.

## Implementing full-text search

There are three steps involved in full-text search:

- Defining an analyzer
- Defining an index that uses the analyzer
- Querying using syntax that specifically uses full-text search

The guides in this section walk through each part.

## Where to go next

- [Analyzers and tokenizers](/docs/learn/data-models/full-text-search/analyzers-and-tokenizers.md): `DEFINE ANALYZER`, tokenizers, filters, stemming, and `search::analyze`.
- [Search indexes](/docs/learn/data-models/full-text-search/search-indexes.md): `FULLTEXT ANALYZER` on a single field per index.
- [Scoring and ranking](/docs/learn/data-models/full-text-search/scoring-and-ranking.md): the `@@` operator, BM25, highlights, and `search::score` / `search::highlight`.
- [Other ways to work with text](/docs/learn/data-models/full-text-search/other-ways-to-work-with-text.md): sorting and collating text, contains, fuzzy matching, regex, and related string tools.

For reference, see [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md), [`DEFINE ANALYZER`](/docs/reference/query-language/statements/define/analyzer.md), and [Search functions](/docs/reference/query-language/functions/database-functions/search.md).

---

Source: https://surrealdb.com/docs/learn/data-models/full-text-search/scoring-and-ranking

# Scoring and ranking

Query with the MATCHES operator, combine BM25 and HIGHLIGHTS on indexes, and use search::score and search::highlight with numbered match clauses.

After you have [defined analyzers](/docs/learn/data-models/full-text-search/analyzers-and-tokenizers.md) and attached [search indexes](/docs/learn/data-models/full-text-search/search-indexes.md), you can run full-text queries.

## Querying

Once an index that uses a full-text analyzer is in place, use the `@@` operator (the `MATCHES` operator) to query it.

- **Ranking / Scoring**: Once matches are found, an FTS engine ranks them to show the most relevant results first. Algorithms such as BM25 or TF-IDF look at how often terms appear in a document, or whether those terms appear in the title vs. the body, etc.

- **Highlighting** : A good search experience shows where in the text the matches occur, often by wrapping matched terms in HTML tags or otherwise emphasising them.

```surql
DEFINE ANALYZER my_analyzer
  TOKENIZERS class
  FILTERS lowercase, ascii;

-- Two statements as full-text indexes must be defined on only one field
DEFINE INDEX body_index
  ON TABLE article
  FIELDS body
  FULLTEXT ANALYZER my_analyzer;

DEFINE INDEX title_index
  ON TABLE article
  FIELDS title
  FULLTEXT ANALYZER my_analyzer;

CREATE article SET
  title = "Machine Learning!",
  body = "Machine learning, or ML, is all the rage these days. Developers are...";

CREATE article SET
  title = "History of machines",
  body = "The earliest 'machine' used by our ancestors was a simple sharpened stone tool. It was...";

SELECT body, title
FROM article
WHERE body @@ "machine" OR title @@ "machine";
```

```surql title="Output"
[
	{
		body: 'Machine learning, or ML, is all the rage these days. Developers are...',
		title: 'Machine Learning!'
	},
	{
		body: "The earliest 'machine' used by our ancestors was a simple sharpened stone tool. It was...",
		title: 'History of machines'
	}
]
```

To use highlighting and best match scoring on searches, the `BM25` and `HIGHLIGHTS` clauses can be added to the `DEFINE INDEX` statement. These enable you use the [`search::highlight`](/docs/reference/query-language/functions/database-functions/search.md#searchhighlight) and [`search::score`](/docs/reference/query-language/functions/database-functions/search.md#searchscore) functions.

Inside a query, the `@@` operator takes a number that is matched with the same number passed into one of these functions. In the example below, the `WHERE text @0@ "night"` part of the query will match with `search::highlight("->", "<-", 0)` and `search::score(0) AS text_score`, while `title @1@ "hound"` will match with `search::score(1) AS title_score`.

```surql
DEFINE ANALYZER my_analyzer
  TOKENIZERS class, blank
  FILTERS lowercase, ascii;

DEFINE INDEX text_index
  ON TABLE article
  FIELDS text
  FULLTEXT ANALYZER my_analyzer BM25 HIGHLIGHTS;

DEFINE INDEX title_index
  ON TABLE article
  FIELDS title
  FULLTEXT ANALYZER my_analyzer BM25 HIGHLIGHTS;

INSERT INTO article (title, text) VALUES
    ("A Study in Scarlet",
      "IN the year 1878 I took my degree of Doctor of Medicine of the University of London,
      and proceeded to Netley to go through the course prescribed for surgeons in the army.")
    ("A Study in Scarlet",
      "Having completed my studies there,
      I was duly attached to the Fifth Northumberland Fusiliers as Assistant Surgeon.")
    ("The Sign of the Four",
      "SHERLOCK HOLMES took his bottle from the corner of the mantel-piece and his hypodermic syringe from its neat morocco case.")
    ("The Hound of the Baskervilles",
      "MR. SHERLOCK HOLMES,
      who was usually very late in the mornings,
      save upon those not infrequent occasions when he was up all night,
      was seated at the breakfast table.")
    ("The Hound of the Baskervilles",
      "I stood upon the hearth-rug and picked up the stick which our visitor had left behind him the night before.");

SELECT
  text,
  title,
  search::highlight("->", "<-", 0) AS title,
  search::score(0) AS text_score,
  search::score(1) AS title_score
FROM article
WHERE
  text @0@ "night"
  OR title @1@ "hound";
```

```surql title="Output"
[
	{
		text: 'MR. SHERLOCK HOLMES,
		  who was usually very late in the mornings,
		  save upon those not infrequent occasions when he was up all night,
		  was seated at the breakfast table.'
		text_score: 0.30209195613861084f,
		title: 'MR. SHERLOCK HOLMES,
		  who was usually very late in the mornings,
		  save upon those not infrequent occasions when he was up all ->night<-,
		  was seated at the breakfast table.'
		title_score: 0.32491400837898254f
	},
	{
		text:
		  'I stood upon the hearth-rug and picked up the stick which our visitor had left behind him the night before.',
		text_score: 0.35619309544563293f,
		title:
		  'I stood upon the hearth-rug and picked up the stick which our visitor had left behind him the ->night<- before.',
		title_score: 0.32491400837898254f
	}
]
```

## Why a score can be 0

BM25 weights every term by its inverse document frequency, a measure of how rare that term is across the indexed documents. SurrealDB computes it as `ln((N - n + 0.5) / (n + 0.5))`, where `N` is the number of indexed documents and `n` is how many of them contain the term, and then clamps the result at zero so that very common terms never carry a negative weight.

That clamp is reached as soon as `n` is at least half of `N`. A term held by half or more of the indexed documents weighs exactly `0`, and because that weight multiplies the rest of the formula, `search::score()` returns `0` for it.

Small datasets reach the threshold easily. Three documents, two of which contain the term being searched for, are already enough:

```surql
DEFINE ANALYZER simple TOKENIZERS class FILTERS lowercase;
DEFINE INDEX doc_text ON document FIELDS text FULLTEXT ANALYZER simple BM25;

CREATE document:1 SET text = "a database for graphs";
CREATE document:2 SET text = "a database for documents";
CREATE document:3 SET text = "an engine for vectors";

SELECT id, search::score(0) AS score FROM document WHERE text @0@ 'database';
```

```surql title="Output"
[
	{
		id: document:1,
		score: 0f
	},
	{
		id: document:2,
		score: 0f
	}
]
```

Four more documents that never mention the term leave `n` at 2 while `N` rises to 7, putting the term under the threshold and giving it a positive weight. The index, the query and the two matching records are all unchanged:

```surql
CREATE document:4 SET text = "an engine for time series";
CREATE document:5 SET text = "an engine for documents";
CREATE document:6 SET text = "a store for vectors";
CREATE document:7 SET text = "a store for graphs";

SELECT id, search::score(0) AS score FROM document WHERE text @0@ 'database';
```

```surql title="Output"
[
	{
		id: document:1,
		score: 0.7997389435768127f
	},
	{
		id: document:2,
		score: 0.7997389435768127f
	}
]
```

Matching is unaffected either way, so the same records come back and only their ranking weight is zero. A score of `0` across a handful of records is the clamp behaving as designed, which makes a small dataset a poor place to measure relevance. It is preferable to benchmark on a larger corpus (the size of the real one if possible), and read `ORDER BY score DESC` over a toy dataset as unordered.

The `AND` and `OR` clauses can be used inside the `@@` as well. This allows a single string to be compared against instead of needing to specify individual parts of the string.

```surql
CREATE document:1 SET
  text = "It is rare that I find myself penning a personal note in my chronicles.";

DEFINE ANALYZER simple
  TOKENIZERS blank,class
  FILTERS lowercase;

DEFINE INDEX some_index
  ON document
  FIELDS text
  FULLTEXT ANALYZER simple;

-- @AND@ and @OR@: can use the entire string
SELECT *
FROM document
WHERE text @AND@ "personal rare";

SELECT *
FROM document
WHERE text @OR@ "personal nice weather today";

-- Separate AND and OR outside of matches operator:
-- Must specify parts of string to check for match
SELECT *
FROM document
WHERE text @@ "personal" AND text @@ "rare";

SELECT *
FROM document
WHERE text @@ "personal note";

SELECT *
FROM document
WHERE text @@ "personal" OR text @@ "nice weather today";
```

In addition to full-text search capabilities, SurrealDB has a number of other ways of working with text such as string similarity, regex matching, and functions that order text in different ways. See [Other ways to work with text](/docs/learn/data-models/full-text-search/other-ways-to-work-with-text.md).

---

Source: https://surrealdb.com/docs/learn/data-models/full-text-search/search-indexes

# Search indexes

Apply FULLTEXT ANALYZER indexes to single fields per index, and understand common parse errors when listing multiple fields.

A search index makes a field searchable through an analyzer that has already been defined. This page covers defining one, why each index takes a single field, and the parse error that follows from listing several.

## Defining an index that uses an analyzer

Once a search analyzer is defined, it can be applied to the fields of a table to make them searchable by [defining an index](/docs/reference/query-language/statements/define/indexes.md#full-text-search-fulltext-index) that uses the `FULLTEXT ANALYZER` clause.

```surql
DEFINE ANALYZER my_analyzer
  TOKENIZERS class
  FILTERS lowercase, ascii;

DEFINE INDEX body_index 
  ON TABLE article
  FIELDS body
  FULLTEXT ANALYZER my_analyzer;

DEFINE INDEX title_index
  ON TABLE article
  FIELDS title 
  FULLTEXT ANALYZER my_analyzer;
```

An index can only be defined on a single field.

```surql
DEFINE ANALYZER my_analyzer
  TOKENIZERS class
  FILTERS lowercase, ascii;

DEFINE INDEX body_index 
  ON TABLE article 
  FIELDS body, title 
  FULLTEXT ANALYZER my_analyzer;
```

```surql title="Output"
'Parse error: Expected one column, found 2
 --> [5:55]
  |
5 | ...LDS body, title FULLTEXT ANALYZER my_analyzer;
  |              ^^^^^
'
```

## Several indexes on one field

A field can carry more than one full-text index, each with its own analyzer. This suits a field that needs both exact word matching and stemmed matching, for example. The query planner picks one of the indexes on its own, so name the one to use with a [`WITH INDEX`](/docs/reference/query-language/clauses/with.md) clause.

```surql
DEFINE ANALYZER simple TOKENIZERS class FILTERS lowercase;
DEFINE ANALYZER english TOKENIZERS class FILTERS lowercase, snowball(english);

DEFINE INDEX body_simple ON article FIELDS body FULLTEXT ANALYZER simple;
DEFINE INDEX body_english ON article FIELDS body FULLTEXT ANALYZER english;

CREATE article:one SET body = 'Running dogs';

SELECT id FROM article WITH INDEX body_simple WHERE body @@ 'run';
//- []

SELECT id FROM article WITH INDEX body_english WHERE body @@ 'run';
//- [{ id: article:one }]
```

The `english` analyzer stems `Running` to `run`, so only the index built with it matches the query term.

For querying with `@@`, BM25, and highlights, continue to [Scoring and ranking](/docs/learn/data-models/full-text-search/scoring-and-ranking.md).

---

Source: https://surrealdb.com/docs/learn/data-models/geospatial/distance-and-proximity

# Distance and proximity

Measure distance between points, reason about buffers and proximity, and use geohash helpers for hierarchical location keys alongside SurrealDB geo functions.

Many applications need more than boolean inside/outside tests: they need to know how far apart two geometries are, whether entities fall within a buffer around a route, or a compact geohash key for bucketing nearby points.

## Distance

The shortest path between two points on the Earth is often approximated in databases using great-circle or similar metrics, depending on your CRS and functions available. SurrealDB’s [`geo::distance()`](/docs/reference/query-language/functions/database-functions/geo.md#geodistance) calculates the distance between two geolocation points, and can be useful when ranking “nearest” results or filtering by a maximum radius.

When you only need ordering, you may still use distance in an `ORDER BY` clause. When you need a hard cutoff, combine it with a `WHERE` threshold.

## Geohashes

Geohashes approximate location with a short string that prefixes share when points are nearby, and are useful for rough bucketing and cache keys. SurrealDB provides [`geo::hash::encode()` and `geo::hash::decode()`](/docs/reference/query-language/functions/database-functions/geo.md) for moving between points and geohash strings.

## Next steps

For end-to-end examples that tie these ideas to product features, see [Location-based patterns](/docs/learn/data-models/geospatial/location-based-patterns.md).

---

Source: https://surrealdb.com/docs/learn/data-models/geospatial/geometry-types

# Geometry types

Use GeoJSON-style geometry values in SurrealDB, including Point, LineString, Polygon, and collections, with longitude-before-latitude ordering.

SurrealDB’s `geometry` type follows the [GeoJSON](https://en.wikipedia.org/wiki/GeoJSON) model in which both (and only) a `type` and `coordinates` field must be specified. A tuple shorthand (e.g. `(50.0, 75.7)`) is also accepted when creating a `point`.

## Coordinate order

Points are defined with **longitude before latitude**, as required by the GeoJSON spec. Many map services display latitude first, so be sure to confirm the order when ingesting external data.

```surql
-- Full GeoJSON object form
CREATE city:london SET centre = {
    type: "Point",
    coordinates: [-0.118092, 51.509865],
};

-- Tuple shorthand for a point (lon, lat)
CREATE city:london SET centre = (-0.118092, 51.509865);
```

See the [Geometries](/docs/reference/query-language/language-primitives/data-types/geometries.md) reference for `LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, `MultiPolygon`, and `Collection`, including examples for each type.

## Why geometry types matter

- **Points** capture a single location (venues, vehicles, users).
- **Line strings** represent paths (routes, boundaries along a corridor).
- **Polygons** represent regions (service areas, countries, floor plans projected to the globe).

Choosing the right type makes spatial predicates (`INTERSECTS`, `CONTAINS`, and so on) accurate.

---

Source: https://surrealdb.com/docs/learn/data-models/geospatial/location-based-patterns

# Location-based patterns

Apply geospatial modelling in SurrealDB to store-of-interest search, delivery zones, asset tracking, and other location-first product features.

Once you can store [geometry types](/docs/learn/data-models/geospatial/geometry-types.md) and run [spatial queries](/docs/learn/data-models/geospatial/spatial-queries.md) with [distance](/docs/learn/data-models/geospatial/distance-and-proximity.md) in mind, most product features follow a small set of patterns.

## Store finder (“near me”)

Store each site's coordinates as a `Point`, optionally with a category and opening hours. At query time, supply the user's location, compute distance or bounding-box filters, and sort by proximity. SurrealDB has no spatial index type, so for dense datasets store a geohash (from [`geo::hash::encode()`](/docs/reference/query-language/functions/database-functions/geo.md#geohashencode)) in a field with a standard index, or at the start of the record ID, to avoid scanning the whole table.

For example, consider records used to store millions of events by location. These can use array-based record IDs that start with a geohash, so a query can select every record with exactly this hash without any further filtering.

```surql
DEFINE FUNCTION fn::create_geo_record($point: point) -> object {
    LET $hash = geo::hash::encode($point, 4);
    CREATE ONLY event:[$hash, $point, rand::ulid()];
};

fn::create_geo_record((50.0, 50.1));
fn::create_geo_record((50.1, 50.1));
fn::create_geo_record((50.5, 50.1));

SELECT * FROM event:['v0gt', NONE, NONE]..['v0gt', .., ..];
```

```surql title="Output: returns two of three above events"
[
	{
		id: event:[
			'v0gt',
			(50f, 50.1f),
			'01KNTYS85HQZ7T4D066MAVA3G9'
		]
	},
	{
		id: event:[
			'v0gt',
			(50.1f, 50.1f),
			'01KNTYS85H8JHM3T9YKGMT2BV8'
		]
	}
]
```

## Service areas and delivery zones

Represent coverage as polygons (or multipolygons for islands). New customers or orders are classified with point-in-polygon tests.

For a single zone record, store the boundary as a [polygon geometry](/docs/reference/query-language/language-primitives/data-types/geometries.md) and test whether a customer’s location falls inside it using [`CONTAINS`](/docs/reference/query-language/language-primitives/operators.md#contains) (polygon on the left, point on the right):

```surql
DEFINE TABLE service_area SCHEMAFULL;
DEFINE FIELD name ON service_area TYPE string;
DEFINE FIELD boundary ON service_area TYPE geometry<polygon>;

CREATE service_area:greater_london SET
	name = "Greater London",
	boundary = {
		type: "Polygon",
		coordinates: [[
			[-0.38314819, 51.37692386],
			[0.1785278, 51.37692386],
			[0.1785278, 51.61460570],
			[-0.38314819, 51.61460570],
			[-0.38314819, 51.37692386]
		]]
	};

LET $customer = (-0.118092, 51.509865);
SELECT name FROM service_area WHERE boundary CONTAINS $customer;
```

```surql title="Output"
[
	{
		name: 'Greater London'
	}
]
```

A point outside the ring (for example further north) returns no records. For overlapping territories, you may return several matches and disambiguate with priority, postal rules, or extra fields on `service_area`.

## Tracking and corridors

For vehicles or assets reporting on a schedule, store recent positions as points with timestamps and use line strings or buffers when you care about adherence to a route corridor rather than a single snapshot.

## Resources

- [Geospatial data types](/docs/reference/query-language/language-primitives/data-types/geometries.md)
- [Geo functions](/docs/reference/query-language/functions/database-functions/geo.md)
- [SurrealDB University](/learn/book/chapter-10)

---

Source: https://surrealdb.com/docs/learn/data-models/geospatial/overview

# Geospatial model

Learn how geospatial data differs from plain numeric fields, core ideas such as CRS and geometry types, and where to find guides on types, queries, distance, and location-based patterns.

A geospatial database is specifically designed (or extended) to store and query data related to the Earth’s surface. Rather than just focusing on numeric or textual data, these systems handle coordinates, polygons, linestrings, and other spatial objects that define locations and shapes. Queries typically revolve around spatial relationships, like finding points within a boundary, measuring distances, or detecting intersections between shapes.

Historically, geospatial capabilities were mostly the domain of specialised systems such as PostGIS (an extension of PostgreSQL) or dedicated GIS software. SurrealDB offers geospatial features to unify location-related data with other data models (relational, document, graph, vector, and so on) in one database with no extensions required.

In this section, you’ll learn how to think like a geospatial database user: represent location with geometry or geography types, query with spatial predicates, and combine SurrealDB’s [geo functions](/docs/reference/query-language/functions/database-functions/geo.md) with the rest of your data.

## Core concepts of geospatial data

- Coordinate reference systems (CRS): Coordinates can be stored in different projections. A common choice is WGS84 for latitude and longitude (EPSG:4326), used by GPS. Other systems exist for local or projected coordinates.

- Geometry types: Including points, linestrings, polygons, and multi-variants - see [Geometry types](/docs/learn/data-models/geospatial/geometry-types.md).

- Spatial relationships: Operations such as contains, intersects, within, touches, or disjoint - see [Spatial queries](/docs/learn/data-models/geospatial/spatial-queries.md).

- Distance and proximity: Measuring distance between geometries, buffers, and geohash-style encodings - see [Distance and proximity](/docs/learn/data-models/geospatial/distance-and-proximity.md).

For reference types and function signatures, see [Geometries](/docs/reference/query-language/language-primitives/data-types/geometries.md) and [Geo functions](/docs/reference/query-language/functions/database-functions/geo.md).

---

Source: https://surrealdb.com/docs/learn/data-models/geospatial/spatial-queries

# Spatial queries

Combine geometry fields with SurrealQL SELECTs and geo functions to answer contains, intersects, and distance-style questions over location data.

Geospatial workloads usually start with a question in plain language, such as "which stores are inside this polygon?", "which trips cross this corridor?", or "which assets are within 10 km of this point?". They are then translated into **spatial relationships** between stored geometries.

## Relationships you model most often

- **Contains / within**: Is a point inside a polygon, or is one region inside another?
- **Intersects**: Do two geometries share any area or touch along an edge?
- **Touches / disjoint**: Boundary contact only, or no overlap at all?

SurrealDB exposes these ideas through geometry values and the [`geo::`](/docs/reference/query-language/functions/database-functions/geo.md) function family.

```surql title="Example of geo:: function"
let $london = (-0.04592553673505285, 51.555282574465764);
let $harare = (30.463880214538577, -17.865161568822085);
RETURN geo::distance($london, $harare);
```

Exact operators available to your version are documented alongside geometry types and index definitions. You can also use [`EXPLAIN`](/docs/reference/query-language/clauses/explain.md) to confirm your query plan when tuning performance.

## Tip: combine with non-spatial filters

As location queries are rarely done in isolation, you can combine spatial predicates with ordinary fields to form queries for tasks like "coffee shops in this polygon that are open now", instead of returning every record in the table.

---

Source: https://surrealdb.com/docs/learn/data-models/graph/creating-relations

# Creating relations

Create graph edges with RELATE, store data on edge tables, define TYPE RELATION for safety and tooling, and handle symmetric relations with unique indexes.

In SurrealDB, nodes are typically records in ordinary tables (users, posts, companies, and so on). Graph edges are also **real tables**, created and queried with [`RELATE`](/docs/reference/query-language/statements/relate.md).

## Creating nodes and edge records

SurrealDB uses [`RELATE`](/docs/reference/query-language/statements/relate.md) to form the usual graph triple: subject → predicate → object.

Using `RELATE`, you can model primary relationships from something like an e-commerce flow: wish list, cart, order, and review. These become your edge tables.

```surql
RELATE person:billy->wishlist->product:01HGAR7A0R9BETTCMATM6SSXPT;
RELATE person:vanessa->cart->product:01GXRS3FZG8Y8SDBNHMC14N25X;
RELATE person:loki->order->product:tesseract;
RELATE person:u1cczojntb5kvos2ugue->review->product:8g8ftj1mblza2vikm680;
```

Here, existing record IDs from the `person` and `product` tables are used. The edge tables sit between them and are created by these `RELATE` statements.

After you run `RELATE`, edge records gain two standard fields: `in` and `out`. The `in` field holds the ID of the record on the left side of the statement, and `out` the ID on the right. The triple can also be read as:

- `in -> id -> out`

where the first node is `in`, the edge is the record’s `id`, and the second node is `out`.

## Adding data to edge tables with `SET` and `CONTENT`

Unlike in graph-only databases, **edges in SurrealDB are tables**, so you can store fields on them like any other record.

```surql
RELATE person:j151lkm3k1dytd53y0i5->wishlist->product:r5pgmgjmr57kjvm2j1g1
  SET time.created_at = time::now();

CREATE product:crystal_cave SET name = "The Crystal Cave", price = 5;
CREATE person:brian SET address = "555 Brian Street";
RELATE person:brian->order-> product:crystal_cave
  SET quantity = 2;
```

You can both create an `order` relationship and query through it to pull data from `product` and `person`:

```surql
SELECT 
    quantity, 
    out.name AS product_name,
    out.price * quantity AS price,
    in.address AS shipping_address
FROM order;
```

```surql title="Output"
[
  { 
    price: 10, 
    product_name: 'The Crystal Cave', 
    quantity: 2, 
    shipping_address: '555 Brian Street' 
  }
]
```

That shows how to read fields on the edge table directly. For traversing with arrow syntax from nodes, see [Graph traversal](/docs/learn/data-models/graph/graph-traversal.md).

## Creating a graph relation with metadata on the edge

This pattern is like the examples above, except the `user` record does not keep a `comments` field. A `wrote` edge connects user and comment, and **the edge record** stores context (location, device, mood):

```surql
LET $new_user = CREATE ONLY user SET name = "User McUserson";
LET $new_comment = CREATE ONLY comment SET 
    text = "I learned something new!", 
    created_at = time::now();

RELATE $new_user->wrote->$new_comment SET
	location = "Arizona",
	os = "Windows 11",
	mood = "happy";
```

Once the edge exists, you can traverse it with the arrow operator (forward, backward, recursively and more). See [Graph traversal](/docs/learn/data-models/graph/graph-traversal.md) for patterns such as:

```surql
SELECT ->wrote->comment FROM user;
SELECT <-wrote<-user FROM comment;
SELECT <-wrote<-user->wrote->comment FROM comment;
```

## Define a table as a relation for type safety and SurrealDB Studio

Defining a table as `TYPE RELATION` restricts it to valid graph edges between records.

Adding `TYPE RELATION` to [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) is enough to enforce that behaviour.

```surql
DEFINE TABLE likes TYPE RELATION;
```

Constraining `in` and `out` record types ensures only intended record types can be linked:

```surql
DEFINE TABLE likes TYPE RELATION IN person OUT blog_post | book;
```

Strict relation definitions also feed [SurrealDB Studio](/docs/explore/studio.md)’s Designer view.

Create some data and relate it:

```surql
CREATE person:one, book:one, blog_post:one;
RELATE person:one->likes->book:one;
RELATE person:one->likes->blog_post:one;
```

Before the table is defined as a relation, SurrealDB Studio only sees schemaless tables:

![SurrealDB Studio designer view showing four schemaless tables without a specified connection between them.](~/assets/img/surrealdb/models/schema1.png)

Defining the table as a `TYPE RELATION` clarifies that `likes` is a graph table:

```surql
DEFINE TABLE likes TYPE RELATION;
CREATE person:one, book:one, blog_post:one;
RELATE person:one->likes->book:one;
RELATE person:one->likes->blog_post:one;
```

![SurrealDB Studio designer view showing three schemaless tables together with a likes table that has been defined as a relation.](~/assets/img/surrealdb/models/schema2.png)

With `IN` and `OUT` specified, Designer can show the full relation:

```surql
DEFINE TABLE likes 
	TYPE RELATION
	IN person 
	OUT blog_post | book;
CREATE person:one, book:one, blog_post:one;
RELATE person:one->likes->book:one;
RELATE person:one->likes->blog_post:one;
```

![SurrealDB Studio designer view showing four schemafull tables: three regular tables and one relation table linking them.](~/assets/img/surrealdb/models/schema3.png)

## Unique index for relations “between equals”

When an edge represents friendship, partnership, sister cities, and similar **symmetric** roles, it may not matter which side is `in` or `out`, but you still want at most one edge for the pair.

```surql
CREATE person:one, person:two;

-- Relate them like this?
RELATE person:one->friends_with->person:two;
-- Or like this?
RELATE person:two->friends_with->person:one;
```

Define a field from the sorted `in` and `out` values and put a **unique** index on it:

```surql
DEFINE FIELD key 
    ON TABLE friends_with
    VALUE <string>array::sort([in, out]);
DEFINE INDEX only_one_friendship 
    ON TABLE friends_with
    FIELDS key
    UNIQUE;
```

Then a second `RELATE` from the other direction hits the index:

```surql
CREATE person:one, person:two;
RELATE person:one->friends_with->person:two;
RELATE person:two->friends_with->person:one;
```

```surql title="Output of RELATE statements"
-------- Query --------

[
	{
		id: friends_with:dblidwpc44qqz5bvioiu,
		in: person:one,
		key: '[person:one, person:two]',
		out: person:two
	}
]

-------- Query --------

"Database index `only_one_friendship` already contains
'[person:one, person:two]',
with record `friends_with:dblidwpc44qqz5bvioiu`"
```

## Duplicate record IDs

Enforced in `INSERT RELATION` statements since SurrealDB 3.1.5, another way to ensure at most one edge between two records is to choose a specific ID. Because an `INSERT RELATION` statement only requires the `in` and `out` paths to be present, a statement like the one below will create a random ID for the edge such as `likes:3lmr630sb72awbzhh4cl`.

```surql
-- 
INSERT RELATION INTO likes {
    in: person:one,
    out:person:two
};
```

If a specific ID is declared, then an attempt to insert a second edge will return an error.

```surql
INSERT RELATION INTO likes {
    in: person:one,
    out:person:two,
    id: 1
};

INSERT RELATION INTO likes {
    in: person:one,
    out:person:two,
    id: 1
};
```

This can be used as an ad-hoc unique index, especially if the ID itself contains the IDs of the records being linked. The [`array::sort()`](/docs/reference/query-language/functions/database-functions/array.md#arraysort) function can also be used here for symmetric relations.

```surql
LET $in = person:three;
LET $out = person:four;

INSERT RELATION INTO married_to {
    in: $in,
    out: $out,
    id: [$in, $out].sort()
};

INSERT RELATION INTO married_to {
    in: $out,
    out: $in,
    id: [$in, $out].sort()
};
```

The final query returns the following error, thanks to the duplicate ID, despite the attempt to double the relation by putting `in` at the `out` field and vice versa.

```surql title="Output"
'Database record `likes:[person:four, person:three]` already exists'
```

Querying symmetric edges with `<->` is covered in [Graph traversal](/docs/learn/data-models/graph/graph-traversal.md). For friendship-oriented examples, see [Social network patterns](/docs/learn/data-models/graph/social-network-patterns.md).

## `RELATE` before both endpoints exist

Graph edge records can exist **before** the two endpoint records are created:

```surql
-- Works fine
RELATE person:one->likes->person:two;
person:one->likes->person;
//- []
-- Finally create the 'person' records
CREATE person:one, person:two;
person:one->likes->person;
//- [person:two]
```

To forbid that, add [`ENFORCED`](/docs/reference/query-language/statements/define/table.md) on the relation table:

```surql
DEFINE TABLE likes TYPE RELATION IN person OUT person ENFORCED;
```

```surql title="Output"
"The record 'person:one' does not exist"
```

Some workflows rely on creating the edge first (for example, a street with predictable house IDs before the houses exist). A [`DEFINE FIELD ... VALUE`](/docs/reference/query-language/statements/define/field.md) can describe the path from `house` to `street` and resolve once the house exists:

```surql
DEFINE FIELD street ON house VALUE $this<-contains<-street;
CREATE street:frankfurt_road;
RELATE street:frankfurt_road->contains->[
    house:["Frankfurt Road", 200], 
    house:["Frankfurt Road", 205],
    house:["Frankfurt Road", 210],
];

-- Twelve months later once the house is built and size is known...
CREATE house:["Frankfurt Road", 200] SET sq_m = 110.5;
```

```surql title="Output"
[
	{
		id: house:[
			'Frankfurt Road',
			200
		],
		sq_m: 110.5f,
		street: [
			street:frankfurt_road
		]
	}
]
```

---

Source: https://surrealdb.com/docs/learn/data-models/graph/graph-traversal

# Graph traversal

Use SurrealQL arrow syntax, filter a traversal by edge table and by the fields on an edge, bidirectional edges, traversals from record IDs, automatic flattening, graph paths in schema fields, and SurrealDB Studio’s Explorer to debug paths step by step.

Graph queries in SurrealDB use SurrealQL’s `->` arrow syntax to walk relationships between records.

## Basic forward and reverse traversals

```surql
SELECT ->wrote->post.* AS userPosts
  FROM users:alice;
```

- `FROM users:alice` starts at that node.
- `->wrote->post.*` follows the `wrote` edge to posts and returns full post records as `userPosts`.

Traverse in the reverse direction (for example, from a post to its authors):

```surql
SELECT <-wrote<-author AS authors
  FROM post:helloworld;
```

`<-wrote<-author` means “follow any `wrote` edge **into** this post from an `author`”.

### A single related record as a value

A traversal always returns an array, even when a record has only one edge of that kind. When there is only ever one, take the first element, either of the traversal or of the field read through it:

```surql
CREATE user:1;
CREATE card:1 SET name = 'visa';
RELATE user:1->has_card->card:1;

SELECT
	->has_card->card.name AS all_names,
	(->has_card->card.name)[0] AS name,
	->has_card->card[0].name AS also_name
FROM user:1;
```

```surql title="Output"
[
	{
		all_names: ['visa'],
		also_name: 'visa',
		name: 'visa'
	}
]
```

Taking `[0]` assumes there is only one. If a user has a second card, `[0]` returns one of the two and the other is ignored without an error. To make the query fail instead, select from the traversal with [`ONLY`](/docs/reference/query-language/statements/select.md#the-only-clause), which returns a single value and returns an error if the traversal holds more than one:

```surql
SELECT (SELECT VALUE name FROM ONLY ->has_card->card) AS name FROM user:1;
```

```surql title="Output"
[
	{
		name: 'visa'
	}
]
```

After a second card is related to the same user, the same query returns an error:

```surql
CREATE card:2 SET name = 'amex';
RELATE user:1->has_card->card:2;

SELECT (SELECT VALUE name FROM ONLY ->has_card->card) AS name FROM user:1;
```

```surql title="Error output"
'Expected a single result output when using the ONLY keyword'
```

A user with no card gets `NONE` rather than an error. [`SPLIT`](/docs/reference/query-language/statements/select.md#the-split-clause) does not help here: it does not split the array that a traversal returns, and on a stored array it returns one result per element rather than an error.

To prevent a second card instead of detecting it, add a [`UNIQUE` index](/docs/reference/query-language/statements/define/indexes.md#unique-index) on the edge's `in` field, so that a user cannot have a second `has_card` edge.

## Chaining paths

From comments, find authors and then all of their comments:

```surql
SELECT <-wrote<-user->wrote->comment FROM comment;
```

## Traverse directly from a record id

Traversal can start at one or more record IDs **without** wrapping everything in `SELECT`:

```surql
CREATE ONLY user:mcuserson SET name = "User McUserson";
CREATE ONLY comment:one SET 
    text = "I learned something new!", 
    created_at = time::now();
CREATE ONLY cat:pumpkin SET name = "Pumpkin";

RELATE user:mcuserson->wrote->comment:one SET
	location = "Arizona",
	os = "Windows 11",
	mood = "happy";

RELATE user:mcuserson->likes->cat:pumpkin;
```

```surql
-- Equivalent to:
-- SELECT VALUE <-wrote<-user FROM ONLY comment:one;
comment:one<-wrote<-user;

-- Equivalent to:
-- SELECT VALUE ->likes->cat FROM ONLY user:mcuserson;
user:mcuserson->likes->cat;
```

```surql title="Output"
-------- Query --------

[user:mcuserson]

-------- Query --------

[cat:pumpkin]
```

To include fields when starting from an id, use destructuring:

```surql
-- Equivalent to:
-- SELECT name, ->likes->cat AS cats FROM ONLY user:mcuserson;
user:mcuserson.{ name, cats: ->likes->cat };
```

## Filtering a traversal

An arrow itself is already a filter on the edge table. For example, `->wrote->comment` follows only `wrote` edges and leaves every other kind alone. Two further filters sit alongside it - one on the edge's own fields, and one on how many edge tables to follow at once.

These examples use a user with two outgoing edges, one of which carries fields:

```surql
CREATE user:mcuserson SET name = "User McUserson";
CREATE comment:one SET text = "I learned something new!";
CREATE cat:pumpkin SET name = "Pumpkin";

RELATE user:mcuserson->wrote->comment:one SET
	location = "Arizona",
	os = "Windows 11",
	mood = "happy";

RELATE user:mcuserson->likes->cat:pumpkin;
```

A condition in parentheses applies to the edge, and only the edges that pass it are followed:

```surql
SELECT VALUE ->(wrote WHERE location = "Arizona")->comment FROM ONLY user:mcuserson;
```

```surql title="Output"
[comment:one]
```

The fields named in the condition are the edge's own, so a condition nothing matches ends the walk there:

```surql
SELECT VALUE ->(wrote WHERE location = "Oregon")->comment FROM ONLY user:mcuserson;
```

```surql title="Output"
[]
```

Square brackets look and act similar but are a different mechanism. `[WHERE ...]` is the generic [array filter](/docs/reference/query-language/language-primitives/data-types/arrays.md#filtering-and-mapping-with-array-functions), so `->wrote[WHERE location = "Arizona"]->comment` walks every `wrote` edge and then filters the array that comes back. Inside the parentheses the condition is part of the path instead, and becomes the edge scan's own predicate, so edges that fail it are never followed. Both return the same records, and [`EXPLAIN`](/docs/reference/query-language/statements/explain.md) shows the difference: the parenthesised form carries a `predicate` on the scan, the bracketed one carries none. On a record with a few edges the difference does not matter, while on one with thousands, only the first stays fast.

To follow edges to one specific record, name its ID in the condition on the target table. A record ID straight after the arrow, as in `->follows->user:janet`, is a parse error:

```surql
CREATE user:ann, user:janet, user:bob;
RELATE user:ann->follows->[user:janet, user:bob];

SELECT VALUE ->follows->(user WHERE id = user:janet) FROM ONLY user:ann;
```

```surql title="Output"
[user:janet]
```

A [range](/docs/reference/query-language/statements/relate.md#ranges-inside-graph-queries) with the same start and end, `->follows->user:janet..=janet`, gives the same result.

To ask only whether the edge exists, compare the traversal with the ID instead:

```surql
SELECT VALUE ->follows->user CONTAINS user:janet FROM ONLY user:ann;
```

```surql title="Output"
true
```

Naming several edge tables walks all of them at once, and `?` stands for any table:

```surql
SELECT VALUE ->(wrote, likes)->? FROM ONLY user:mcuserson;
```

```surql title="Output"
[comment:one, cat:pumpkin]
```

`->?->?` therefore follows every edge leaving a record, whatever its type. Where the edges on a record are not known in advance, reading the table name off each one gives the kinds of relationship it actually has:

```surql
SELECT VALUE array::distinct(->?.map(|$e| $e.id.tb())) FROM ONLY user:mcuserson;
```

```surql title="Output"
['likes', 'wrote']
```

An edge is a record like any other, so the same question can be asked from the edge table instead, with `in` and `out` naming the two ends:

```surql
SELECT out, mood FROM wrote WHERE location = "Arizona";
```

```surql title="Output"
[
	{
		mood: 'happy',
		out: comment:one
	}
]
```

Both forms reach the same edges. The arrow suits a walk that carries on to the records at the far end, and the table form suits a question about the edges themselves.

To return the edges in pages, put a full `SELECT` inside the parentheses. It takes `ORDER BY`, `LIMIT` and `START`: `LIMIT` sets the size of a page, `START` skips the pages before it, and `ORDER BY` keeps the order the same from one page to the next. The path then continues to the records at the far end. Here `user:ann` follows four users, returned two to a page in the order she followed them:

```surql
CREATE user:ann, user:bob, user:cleo, user:dev, user:eve;
RELATE user:ann->follows->user:bob SET since = d"2026-01-04";
RELATE user:ann->follows->user:cleo SET since = d"2026-02-11";
RELATE user:ann->follows->user:dev SET since = d"2026-03-20";
RELATE user:ann->follows->user:eve SET since = d"2026-05-02";

-- First page
SELECT VALUE ->(SELECT * FROM follows ORDER BY since LIMIT 2 START 0)->user FROM ONLY user:ann;
//- [user:bob, user:cleo]

-- Second page
SELECT VALUE ->(SELECT * FROM follows ORDER BY since LIMIT 2 START 2)->user FROM ONLY user:ann;
//- [user:dev, user:eve]
```

Another way to get the next page is to continue from the last value of the page before, with a `WHERE` condition in place of `START`. Pages built this way stay correct when edges are added between requests, because a new edge does not move the edges after it into a different page:

```surql
-- The page after the one that ended with since = 2026-02-11
SELECT VALUE ->(SELECT * FROM follows WHERE since > d"2026-02-11" ORDER BY since LIMIT 2)->user FROM ONLY user:ann;
```

```surql title="Output"
[user:dev, user:eve]
```

To page the records at the far end instead of the edges, put the whole path in `FROM`: `SELECT * FROM user:ann->follows->user ORDER BY id LIMIT 2 START 2`. The [graph clauses](/docs/reference/query-language/statements/relate.md#graph-clauses) section lists the other clauses a path can take.
--END--

### Records with no edges

A traversal that finds no edges returns an empty array, and an empty array is falsy, so `!` in front of a path selects the records that have no edge in that direction. In a tree where each record points to its parent with a `child_of` edge, the roots are the records with no outgoing `child_of`, and the leaves are the records with no incoming one:

```surql
CREATE thing:a, thing:b, thing:c, thing:d;
RELATE thing:b->child_of->thing:a;
RELATE thing:c->child_of->thing:a;
RELATE thing:d->child_of->thing:b;
RELATE thing:d->child_of->thing:c;

-- Roots: no parent
SELECT VALUE id FROM thing WHERE !->child_of;
//- [thing:a]

-- Leaves: no children
SELECT VALUE id FROM thing WHERE !<-child_of;
//- [thing:d]
```

Each record checks only its own edges, so the query does not need to collect the ids of every child first.

## Automatic flattening in graph queries

When two lookups follow each other, or a filter follows a lookup, results can **flatten** in a way that mirrors the graph shape. This example builds a small org chart and walks up and down `works_for`:

```surql
CREATE 
	-- One president
	person:president, 
	-- Two managers
	person:manager1, person:manager2,
	-- Four employees
	person:employee1, person:employee2, person:employee3, person:employee4;

-- Employees work two to a manager, managers work two to a president
RELATE [person:manager1, person:manager2]->works_for->person:president;
RELATE [person:employee1, person:employee2]->works_for->person:manager1;
RELATE [person:employee3, person:employee4]->works_for->person:manager2;

[person:employee1, person:employee2, person:employee3, person:employee4]
    ->works_for->person
    ->works_for->person
    <-works_for<-person
    <-works_for<-person;
```

The result is four arrays, each reflecting the up-and-down path through the president.

You can approximate the same with nested `SELECT` / `map`, but nesting grows quickly:

```surql
[person:employee1, person:employee2, person:employee3, person:employee4]
	-- Each $p is a single record: an array<record>
    .map(|$p| SELECT VALUE out FROM works_for WHERE in = $p.id)
	-- Each $p is an array, so now you have to map each item inside that
    .map(|$p| $p.map(|$p| SELECT VALUE out FROM works_for
      WHERE in = $p.id))
    -- Now an array<array<array<record>>>
    .map(|$p| $p.map(|$p| $p.map(|$p| SELECT VALUE in FROM works_for
      WHERE out = $p.id)))
    -- Now an array<array<array<array<record>>>>
    .map(|$p| $p.map(|$p| $p.map(|$p| $p.map(|$p| SELECT VALUE in
      FROM works_for WHERE out = $p.id))));
```

Graph syntax keeps structure aligned with the traversal. Calling `flatten()` at each step produces a **flat** list of every record seen along the way instead:

```surql
[person:employee1, person:employee2, person:employee3, person:employee4]
    .map(|$p| SELECT VALUE out FROM works_for WHERE in = $p.id).flatten()
    .map(|$p| SELECT VALUE out FROM works_for WHERE in = $p.id).flatten()
    .map(|$p| SELECT VALUE in FROM works_for WHERE out = $p.id).flatten()
    .map(|$p| SELECT VALUE in FROM works_for WHERE out = $p.id).flatten();
```

Treat graph flattening as preserving the **shape** of the walk, not necessarily a single post-hoc array.

### Grouping children under each parent

A path that continues past a level gives the records at the end of the path, and the level in between is not part of the output. In the store below, `->has->category->has->item` gives every item in the store in one array, without the categories they belong to:

```surql
CREATE store:local SET name = 'Local store';
CREATE category:food SET name = 'Food';
CREATE category:drinks SET name = 'Drinks';
CREATE item:burger SET name = 'Burger';
CREATE item:roll SET name = 'Roll';
CREATE item:water SET name = 'Water';

RELATE store:local->has->[category:food, category:drinks];
RELATE category:food->has->[item:burger, item:roll];
RELATE category:drinks->has->item:water;

SELECT name, ->has->category->has->item.name AS items FROM store:local;
```

```surql title="Output"
[
	{
		items: ['Roll', 'Burger', 'Water'],
		name: 'Local store'
	}
]
```

To keep the items under their category, stop the path at `category` and continue from each category with a [destructuring](/docs/reference/query-language/language-primitives/idioms.md#destructuring) `.{ }`. The paths inside the braces start from the category:

```surql
SELECT name, ->has->category.{ name, items: ->has->item.name } AS categories FROM store:local;
```

```surql title="Output"
[
	{
		categories: [
			{ items: ['Roll', 'Burger'], name: 'Food' },
			{ items: ['Water'], name: 'Drinks' }
		],
		name: 'Local store'
	}
]
```

A nested `SELECT` over the path gives the same result, and also allows a `WHERE`, `ORDER BY` or `LIMIT` for each level:

```surql
SELECT name, (SELECT name, ->has->item.name AS items FROM ->has->category) AS categories FROM store:local;
```

The same approach works for record links, for example `categories.{ name, items: items.name }` for a store whose `categories` field holds record links to categories that each hold an `items` field.

### Ranking records by shared connections

A path through a shared record returns a record once for every route that reaches it, so the repeats count the connections. Going from a post to its tags and back to the posts with those tags, a post that shares two tags appears twice. Grouping the result and counting gives the related posts in order of how much they share:

```surql
CREATE post:a SET title = 'A';
CREATE post:b SET title = 'B';
CREATE post:c SET title = 'C';
CREATE post:d SET title = 'D';
CREATE tag:rust, tag:db, tag:web;
RELATE post:a->tagged->[tag:rust, tag:db];
RELATE post:b->tagged->[tag:rust, tag:db, tag:web];
RELATE post:c->tagged->tag:web;
RELATE post:d->tagged->tag:db;

LET $post = post:a;
SELECT title, count() AS shared
FROM ($post->tagged->tag<-tagged<-post).filter(|$p| $p != $post)
GROUP BY title
ORDER BY shared DESC;
```

```surql title="Output"
[
	{ shared: 2, title: 'B' },
	{ shared: 1, title: 'D' }
]
```

The `.filter()` removes the starting post, which the path also reaches through each of its own tags. A `SELECT` over an array of record IDs reads the records, so `title` is available to group by.

## Querying symmetric (“between equals”) relations

For edges like `friends_with`, it may be unclear whether a `person` is on the `in` or `out` side. The `<->` operator traverses **both** directions on that edge table:

```surql
SELECT *, <->friends_with<->person AS friends FROM person;
```

Each record lists the other people in the relation, regardless of which side they were on:

```surql
[
	{
		friends: [
			person:one,
			person:two
		],
		id: person:one
	},
	{
		friends: [
			person:one,
			person:two
		],
		id: person:two
	}
]
```

To drop the current record’s own id from the list, use [`array::complement()`](/docs/reference/query-language/functions/database-functions/array.md#arraycomplement):

```surql
SELECT 
	*, 
	array::complement(<->friends_with<->person, [id]) AS friends
FROM person;
```

```surql title="Output"
[
	{
		friends: [
			person:two
		],
		id: person:one
	},
	{
		friends: [
			person:one
		],
		id: person:two
	}
]
```

For more on this pattern, see the [`RELATE` statement](/docs/reference/query-language/statements/relate.md#bidirectional-relation-querying) and [Chapter 7 of Aeon's Surreal Renaissance](/learn/book/chapter-07#bidirectional-querying-when-a-relationship-is-equal).

## Graph paths in schema fields

Paths are not limited to `SELECT`; they can appear in [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md):

```surql
DEFINE FIELD employers
  ON TABLE person VALUE SELECT VALUE <-employs<-company FROM ONLY $this;

CREATE person:1, person:2, company:1;
RELATE company:1->employs->person:1;
person:1.*;
```

A plain `VALUE` field is computed when the record is created or updated; if the `RELATE` runs later, the field can be stale until an `UPDATE`:

```surql
UPDATE person:1;
```

```surql title="Output"
[
	{
		employers: [
			company:1
		],
		id: person:1
	}
]
```

A [computed field](/docs/reference/query-language/statements/define/field.md#restrictions-on-computed-fields) is recomputed on read:

```surql
DEFINE FIELD employers ON TABLE person COMPUTED <-employs<-company;

CREATE person:1, person:2, company:1;
RELATE company:1->employs->person:1;
person:1.*;
```

```surql title="Output"
{
	employers: [
		company:1
	],
	id: person:1
}
```

## Using SurrealDB Studio Explorer to debug paths

SurrealDB Studio’s [Explorer view](/docs/explore/studio.md) lets you step through records and relations one hop at a time, useful for building intuition for queries like `SELECT ->wrote->comment FROM user`.

Example setup with two outgoing edges:

```surql
CREATE user:mcuserson SET name = "User McUserson";
CREATE comment:one SET 
    text = "I learned something new!", 
    created_at = time::now();
CREATE cat:pumpkin SET name = "Pumpkin";

RELATE user:mcuserson->wrote->comment:one SET
	location = "Arizona",
	os = "Windows 11",
	mood = "happy";

RELATE user:mcuserson->likes->cat:pumpkin;
```

Walkthrough:

* Open `user`, then `user:mcuserson`.
* Open the **Relations** tab (outgoing `->`).
* Follow `wrote` into its edge record, then `comment` to the comment record, matching `->wrote->comment`.

Working backward in the Explorer is a good way to assemble a path while learning the syntax.

## Further reading

* [Recursive traversals](/docs/learn/data-models/graph/recursive-traversals.md) for `@.{n}` and nested recursive shapes.
* [Idioms: recursive paths](/docs/reference/query-language/language-primitives/idioms.md#recursive-paths)

---

Source: https://surrealdb.com/docs/learn/data-models/graph/knowledge-graph-patterns

# Knowledge graph patterns

Model entities and relationships as first-class data for traversal, search, and AI workflows using SurrealDB's graph features.

A knowledge graph models your domain as entities (people, products, documents, concepts) connected by typed relationships:

-   `document -> cites -> regulation`
-   `person -> works_at -> organisation`
-   `symptom -> associated_with -> condition`

This structure gives you a map of meaning that you can do the following with:

* traverse via single- or multi-hop queries
* enrich with metadata that applies to the relation between one record and another
* combine with search and AI workflows

In SurrealDB, knowledge graphs are built using:

* records as nodes (`person`, `document`, `concept`)
* graph relations as edges, created with [`RELATE`](/docs/reference/query-language/statements/relate.md)

Unlike simple links, edges are first-class records, so you can attach metadata to relationships.

## Core pattern: entities + typed relationships

A typical pattern looks like this:

```surql
DEFINE TABLE person SCHEMAFULL;
DEFINE TABLE organisation SCHEMAFULL;

DEFINE TABLE works_at SCHEMAFULL
  TYPE RELATION
  FROM person
  TO organisation;

-- Require relationship-specific data on each edge
DEFINE FIELD role        ON works_at TYPE string;
DEFINE FIELD started_at  ON works_at TYPE datetime;
```

Relationships can then be created using a `RELATE` statement:

```surql
RELATE person:alice->works_at->organisation:acme
  SET role = "Engineer",
      started_at = time::now();
```

This lets you query both the connection and the context of the connection.

## Design tips

### Use stable, explicit identities

Use clear table types (`person`, `document`, `clause`) and stable record IDs so relationships remain meaningful over time.

### Map out queries ahead of time and give thought to edge names

Mapping out queries ahead of time can help make a decision between how coarse- or fine-grained records and edges should be.

For example, you might want to use a single `wrote` edge to connect a `person` or similar record to their writing:

```surql
SELECT *,
  ->wrote->blog AS blogs,
  ->wrote->comment AS comments
FROM person;

SELECT *,
  ->wrote->(blog, comment) AS all_written
FROM person;
```

The more generic the type, the greater the chance that you may need to use a filter.

```surql
SELECT *,
  ->(wrote WHERE type = "blog")->item AS blogs,
  ->(wrote WHERE type = "comment")->item AS comments
FROM person;
```

A more fine-grained approach with separate blogged and commented edges can be more effective for knowledge-graph and AI use cases, because the relationship semantics are explicit in the graph itself rather than inferred from filters or record types.

```surql
SELECT *,
  ->blogged->blog AS blogs,
  ->commented->comment AS comments
FROM person;
```

As multiple edges can always be combined into a single query, you can make your logic as fine-grained as you can conceptually manage without needing to worry about this having an effect on the queries you put together. The only extra addition to such queries is that of the names of the edge tables involved.

```surql
SELECT *,
  ->(blogged, commented)->(blog, comment) AS all_written
FROM person;
```

## Industry-oriented examples

You can see these patterns applied in real-world schemas in our [sample industry schemas](/docs/learn/schema-management/schema-design/sample-industry-schemas.md) page.

---

Source: https://surrealdb.com/docs/learn/data-models/graph/overview

# Graph model

Learn how to think in a graph model and how SurrealDB represents nodes, edges, and properties, with links to guides on creating relations, traversal, record links, and more.

A graph database is specifically designed to store data as nodes and edges (relationships between nodes). With this model, connections are front and center, making it easier (and often faster) to query highly connected datasets, like [social networks](/blog/using-surrealdb-to-expose-organized-influence-campaigns), [planning and supply chain relationships](/docs/learn/schema-management/schema-design/sample-industry-schemas.md#project-planning), recommendation engines, or [fraud detection graphs](/blog/fraud-detection-with-surrealdb).

When using SurrealDB as a graph database, you typically care about both the entities in a system and how they relate to each other. It might be a user that “follows” another user, a product that “belongs” to a category, or a web page that “links to” another page. These relationships are first-class citizens rather than foreign keys or nested objects, so queries can traverse them directly.

Thinking in a graph database means expressing data as nodes and defining the edges that describe relationships, instead of focusing on how to break data into tables (relational) or embed data in documents (document model). This mindset puts the connections at the core of your design: each data point is a node with properties, and edges hold properties too, representing the context or metadata about those relationships.

## Core concepts of graph-oriented modelling

In any graph database, you deal with three fundamental elements:

- **Nodes (Vertices)**: Represent main entities or “things”. In a social network for example, nodes might be people. In a knowledge graph, nodes might be concepts. In a product recommendation engine, nodes might be items or customers.

- **Edges**: Represent relationships between nodes. These could be `follows`, `buys`, `likes`, `friend_of`, `in_category`, etc. They often include properties like timestamps or weights.

- **Properties**: Both nodes and edges can contain key-value pairs (properties). For a `user` node, properties might be `name` or `age`. For a `likes` edge, a property might be `strength` to indicate how strong the affinity between users is.

When you think in a graph, the modelling process shifts toward identifying the main entities in your application (the nodes) and how they relate to one another (the edges). Rather than flattening these relationships into foreign keys or embedding them in nested structures, you give them explicit representation and, often, explicit properties.

## Modelling data as a graph

How this works in practice in most graph databases is through "semantic triples", which describe a graph in a three-part structure:

- `subject -> predicate -> object`

Or:

- `node -> edge -> node`

Another way to think about this is in terms of nouns connected by verbs, such that it forms a sentence.

- `noun -> verb -> noun`

Or:

- `person -> order -> product`

## Where to go next

The guides in this section break down how SurrealDB implements these ideas in practice:

- [Creating relations](/docs/learn/data-models/graph/creating-relations.md): [`RELATE`](/docs/reference/query-language/statements/relate.md), edge tables, `in` / `out`, schema with `TYPE RELATION`, and edge cases such as relations before records exist.
- [Graph traversal](/docs/learn/data-models/graph/graph-traversal.md): arrow syntax, traversing from record IDs, flattening behaviour, graph paths in schema, and using SurrealDB Studio’s Explorer.
- [Recursive traversals](/docs/learn/data-models/graph/recursive-traversals.md): `@.{n}` paths and nested shapes along a graph.
- [Record links vs graph relations](/docs/learn/data-models/graph/record-links-vs-graph-relations.md): when to use each, metadata on edges, weighting, and delete behaviour.
- [Social network patterns](/docs/learn/data-models/graph/social-network-patterns.md): friendship-style edges, bidirectional queries, and interaction weighting examples.
- [Knowledge graph patterns](/docs/learn/data-models/graph/knowledge-graph-patterns.md): orienting nodes and edges for knowledge-style and industry schemas.

For statement-level reference and deeper query details, see:

* The [`RELATE` statement](/docs/reference/query-language/statements/relate.md#querying-graphs)
* [Idioms](/docs/reference/query-language/language-primitives/idioms.md) (including recursive paths)
* The [SurrealDB Fundamentals course](/learn/fundamentals)
* [Aeon's Surreal Renaissance](/learn/book), chapters 5 to 8 in particular

---

Source: https://surrealdb.com/docs/learn/data-models/graph/record-links-vs-graph-relations

# Record links vs graph relations

Choose between record links and graph edges for performance, schema, bidirectional references, metadata on relationships, weighting, and delete behaviour.

The first question when modelling connections is whether **graph edges** are the right tool: SurrealDB can also connect records with **record links**.

## Record links

A record link is a pointer from one record to another: any field that holds a record id. Record ids are efficient direct pointers and avoid table scans.

Example: one `user` with two `comment` records linked from the user:

```surql
LET $new_user = CREATE ONLY user SET name = "User McUserson";
-- Create a new comment, use the output to update the user
UPDATE $new_user SET comments += (CREATE ONLY comment SET 
    text = "I learned something new!", 
    created_at = time::now())
    .id;
UPDATE $new_user SET comments += (CREATE ONLY comment SET
    text = "I don't get it, can you explain?",
    created_at = time::now())
    .id;
```

Querying is like reading any other field:

```surql
SELECT 
    name, 
    comments.{ created_at, text }
FROM user;
```

```surql title="Output"
[
	{
		comments: [
			{
				created_at: d'2024-12-12T02:39:07.644Z',
				text: 'I learned something new!'
			},
			{
				created_at: d'2024-12-12T02:39:07.645Z',
				text: "I don't get it, can you explain?"
			}
		],
		name: 'User McUserson'
	}
]
```

### Reverse direction without edges

Historically, record links were unidirectional, so the other direction often needed a subquery, while graph edges made reverse walks easy:

```surql
SELECT 
    *,
    -- Check the `user` table's `comments` field
    -- for the id of the current comment
    (SELECT id, name FROM user WHERE $parent.id IN comments) AS author
FROM comment;

-- Equivalent graph query is much easier
-- to read and write
SELECT 
	*,
	<-wrote<-author
FROM comment;
```

```surql
[
	{
		author: [
			{
				id: user:f3t90z8uvns76sr3nxrd,
				name: 'User McUserson'
			}
		],
		created_at: d'2024-12-12T02:39:07.645Z',
		id: comment:gj1vtsd9d19z9afrc14j,
		text: "I don't get it, can you explain?"
	},
	{
		author: [
			{
				id: user:f3t90z8uvns76sr3nxrd,
				name: 'User McUserson'
			}
		],
		created_at: d'2024-12-12T02:39:07.644Z',
		id: comment:zhnbfopxspekknsi6vx6,
		text: 'I learned something new!'
	}
]
```

### Bidirectional record links (3.0+)

Since version `3.0.0`, a record link can be bidirectional by defining a field with the [`REFERENCE`](/docs/reference/query-language/language-primitives/record-references.md) clause and using a computed back-reference:

```surql
DEFINE FIELD comments ON user TYPE option<array<record<comment>>> REFERENCE;
DEFINE FIELD author ON comment COMPUTED <~user;

LET $new_user = CREATE ONLY user SET name = "User McUserson";
-- Create a new comment, use the output to update the user
UPDATE $new_user SET comments += (CREATE ONLY comment:one SET 
    text = "I learned something new!", 
    created_at = time::now())
    .id;
UPDATE $new_user SET comments += (CREATE ONLY comment:two SET
    text = "I don't get it, can you explain?",
    created_at = time::now())
    .id;

-- 'author' field is populated with the 'user' who wrote the comment
SELECT * FROM ONLY comment:one;

-- Regular queries on incoming references work too
SELECT text, <~user.id[0] AS commenter FROM comment;
```

```surql title="Output"
-------- Query --------

{
	author: [
		user:igi77zrlewsqemgjz4zi
	],
	created_at: d'2025-09-02T02:56:03.408Z',
	id: comment:one,
	text: 'I learned something new!'
}

-------- Query --------

[
	{
		commenter: user:igi77zrlewsqemgjz4zi,
		text: 'I learned something new!'
	},
	{
		commenter: user:igi77zrlewsqemgjz4zi,
		text: "I don't get it, can you explain?"
	}
]
```

## One-to-one, one-to-many and many-to-many

Each kind of relationship maps to a record link field, and the field definition decides how strict it is. The schema below models a blog with posts and tags:

| Relationship | Example | Field |
| --- | --- | --- |
| One-to-one | Each user owns at most one blog | `owner ON blog TYPE record<user>`, with a `UNIQUE` index |
| One-to-many | A blog has many posts | `blog ON post TYPE record<blog>`, stored on the "many" side |
| Many-to-many | A post has many tags, and a tag is on many posts | `tags ON post TYPE array<record<tag>>` |

```surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON user TYPE string;

DEFINE TABLE blog SCHEMAFULL;
DEFINE FIELD title ON blog TYPE string;
DEFINE FIELD owner ON blog TYPE record<user>;
DEFINE INDEX one_blog_per_owner ON blog FIELDS owner UNIQUE;
DEFINE FIELD posts ON blog COMPUTED <~post;

DEFINE TABLE tag SCHEMAFULL;
DEFINE FIELD name ON tag TYPE string;
DEFINE FIELD posts ON tag COMPUTED <~post;

DEFINE TABLE post SCHEMAFULL;
DEFINE FIELD title ON post TYPE string;
DEFINE FIELD blog ON post TYPE record<blog> REFERENCE ON DELETE CASCADE ASSERT $value.exists();
DEFINE FIELD tags ON post TYPE array<record<tag>> DEFAULT [] REFERENCE ASSERT $value.all(|$t| $t.exists());
```

Some details about this schema:

- **One-to-one needs the index.** `owner` holds one user, so each blog has one owner, but several blogs could name the same user. The `UNIQUE` index refuses a second blog for the same user.
- **The link is stored once.** A post stores its blog, and the blog does not store an array of posts. The `posts` field on `blog` is [computed](/docs/reference/query-language/language-primitives/record-references.md) from the incoming references with `<~post`, so it cannot go out of date. This needs `REFERENCE` on the field that holds the link.
- **`record<blog>` checks the table, not the record.** The type accepts any `blog:…` id, including one for a record that does not exist. `ASSERT $value.exists()` adds the check that a foreign key makes in a relational database, and `$value.all(|$t| $t.exists())` does the same for each element of an array.
- **An array field needs a value.** A `SCHEMAFULL` field of type `array<record<tag>>` refuses `NONE`, so a post created without tags fails unless the field has `DEFAULT []`, or uses `option<array<record<tag>>>` instead.
- **`ON DELETE CASCADE`** deletes a blog's posts when the blog is deleted. The [other `ON DELETE` options](/docs/reference/query-language/language-primitives/record-references.md) refuse the delete, leave or remove the link, or run an expression.

With some data in place, each check refuses a link that it should:

```surql
CREATE user:ann SET name = 'Ann';
CREATE blog:news SET title = 'News', owner = user:ann;
CREATE tag:rust SET name = 'rust';
CREATE tag:db SET name = 'databases';
CREATE post:one SET title = 'Hello', blog = blog:news, tags = [tag:rust, tag:db];
CREATE post:two SET title = 'Second', blog = blog:news;

CREATE post:three SET title = 'Lost', blog = blog:gone;
//- 'Found blog:gone for field `blog`, with record `post:three`, but field must conform to: $value.exists()'

CREATE post:four SET title = 'Typo', blog = blog:news, tags = [tag:rsut];
//- 'Found [tag:rsut] for field `tags`, with record `post:four`, but field must conform to: $value.all(|$t: any| ($t.exists()))'

CREATE blog:other SET title = 'Other', owner = user:ann;
//- 'Database index `one_blog_per_owner` already contains user:ann, with record `blog:news`'
```

Queries follow the links in either direction:

```surql
SELECT title, posts.title AS posts FROM blog:news;
//- [{ posts: ['Hello', 'Second'], title: 'News' }]

SELECT name, posts.title AS posts FROM tag:rust;
//- [{ name: 'rust', posts: ['Hello'] }]

SELECT title, blog.owner.name AS owner, tags.name AS tags FROM post:one;
//- [{ owner: 'Ann', tags: ['rust', 'databases'], title: 'Hello' }]
```

A many-to-many relationship can also be a graph edge, such as `RELATE post:one->tagged->tag:rust`. Use an edge when the relationship has data of its own, such as who added the tag and when, as the sections below describe.

## When to prefer record links

**Record links are preferred if:**

* Performance is the top priority.
* You do not need very complex multi-hop graph queries.
* You want the schema to declare what happens when a linked record is deleted (cascade, refuse, ignore, etc.) via [`ON DELETE`](/docs/reference/query-language/language-primitives/record-references.md) and related options.

## When to prefer graph relations

**Graph relations are preferred if:**

* You want to create links quickly **without** pre-defining every field, or you link across many record types in one go (for example `RELATE person:one->wrote->[blog:one, book:one, comment:one]` versus several `DEFINE FIELD` steps).
* You need expressive arrow syntax for multi-hop patterns such as `->wrote->comment<-wrote<-person->wrote->comment` starting from `person`.
* You want SurrealDB Studio’s Designer to visualise edges clearly.

Graph edges are **almost required** when the relationship itself carries metadata that belongs neither to the source nor target record alone (for example, the moment a user posted a comment):

```surql
{
    ip_addr_location: "Arizona",
    os: "Windows 11",
    current_mood: "Happy"
}
```

That data is about the **link event**, not the whole user or the comment body, so it belongs on an edge table.

The same applies to durable facts about the relationship:

```surql
{
	friends_since: d'2024-12-31T06:43:21.981Z',
	friendship_strength: 0.4
}
```

## Weighting and counting on graph tables

Graph tables are a natural place to store **weights** on the edge record, or to **derive** interaction strength by **counting** how many edges exist between the same endpoints. If you need that kind of scoring or event metadata on the relationship itself, graph relations are usually the right abstraction.

Worked examples (random NPC interactions, `knows` vs `greeted`, aggregations) live in [Social network patterns](/docs/learn/data-models/graph/social-network-patterns.md).

## When links are deleted

Record links can encode rich behaviour on delete (for example, removing an id from an array while appending to a history field), via [`REFERENCE` `ON DELETE`](/docs/reference/query-language/language-primitives/record-references.md).

Graph edges are simpler: if **either** endpoint record is deleted, the edge record is removed.

```surql
-- likes record created without problems
RELATE person:one->likes->person:two;
CREATE person:one, person:two;
DELETE person:one;
-- 'likes' record is now gone
SELECT * FROM likes;
```

Example of record-link cleanup:

```surql
DEFINE FIELD comments ON person TYPE option<array<record<comment>>> REFERENCE ON DELETE THEN {
    UPDATE $this SET
        deleted_comments += $reference,
        comments -= $reference;
};
```

For more on `ON DELETE` and references, see [Record references](/docs/reference/query-language/language-primitives/record-references.md).

---

Source: https://surrealdb.com/docs/learn/data-models/graph/recursive-traversals

# Recursive traversals

Use SurrealQL recursive path syntax and idioms to walk a graph to a chosen depth and return nested structures along a relationship.

[Recursive queries](/docs/reference/query-language/language-primitives/idioms.md#recursive-paths) let you follow a path to a specific depth without spelling out every hop.

## Family tree example

`person` records linked by `child_of`:

```surql
CREATE |person:1..16|;
-- parents of person:1
RELATE person:1->child_of->[person:2, person:3];
-- grandparents of person:1
RELATE person:2->child_of->[person:4, person:5];
RELATE person:3->child_of->[person:6, person:7];
-- great-grandparents of person:1
RELATE person:4->child_of->[person:8, person:9];
RELATE person:5->child_of->[person:10, person:11];
RELATE person:6->child_of->[person:12, person:13];
RELATE person:7->child_of->[person:14, person:15];
```

You can repeat the path manually:

```surql
SELECT 
    ->child_of->person AS parents,
    ->child_of->person->child_of->person AS grandparents,
    ->child_of->person->child_of->person->child_of->person AS great_grandparents
FROM ONLY person:1;
```

Or use recursive path repetition:

```surql
SELECT 
    @.{1}->child_of->person AS parents,
    @.{2}->child_of->person AS grandparents,
    @.{3}->child_of->person AS great_grandparents
FROM ONLY person:1;
```

Recursive syntax can also return a **single nested object** that repeats down a path:

```surql
-- Range to start at a depth of one, try to go down to depth of three
SELECT @.{3}.{
    id,
	-- At each depth, use this path to reach the next one
    parents: ->child_of->person.@
} FROM person:1;
```

Starting from a record id:

```surql
person:1.{3}.{
    id,
    parents: ->child_of->person.@
};
```

Recursive paths are defined in idioms and are not limited to graph traversal. A record link recurses through the same syntax, with no edges involved:

```surql
CREATE cat:one   SET likes = cat:two;
CREATE cat:two   SET likes = cat:three;
CREATE cat:three SET likes = cat:one;

cat:one.{1..3}.likes;
```

```surql title="Output"
cat:one
```

What a recursive path needs at each step is a record, or an array of records. That is what a link and an edge have in common. A step landing on any other value ends the query rather than continuing. See [Behaviour of recursive queries](/docs/reference/query-language/language-primitives/idioms.md#behaviour-of-recursive-queries).

## Trees of any depth

A tree, such as a set of categories or a folder structure, can be stored as one record per node with a `child_of` edge to its parent. Each node can then be read on its own like any other record, and a recursive path reads the tree in either direction without knowing its depth:

```surql
CREATE node:root, node:a, node:b, node:a1, node:a2, node:a1x;
RELATE [node:a, node:b]->child_of->node:root;
RELATE [node:a1, node:a2]->child_of->node:a;
RELATE node:a1x->child_of->node:a1;

-- Every ancestor of a node, nearest first
node:a1x.{..+collect}(->child_of->node);
//- [node:a1, node:a, node:root]

-- Every descendant of a node. Possible output, since the order of
-- siblings follows the IDs of their edges
node:root.{..+collect}(<-child_of<-node);
//- [node:a, node:b, node:a1, node:a2, node:a1x]
```

To get the tree or a subtree back in its nested shape, use `.@` to repeat the same structure at each level:

```surql
node:a.{..}.{ id, children: <-child_of<-node.@ };
```

```surql title="Possible output"
{
	children: [
		{
			children: [
				{ children: [], id: node:a1x }
			],
			id: node:a1
		},
		{ children: [], id: node:a2 }
	],
	id: node:a
}
```

Moving a subtree is one change: delete the node's `child_of` edge and relate it to its new parent. A path without an upper bound stops at a depth of 256.

## Further reading

* [Idioms: recursive paths](/docs/reference/query-language/language-primitives/idioms.md#recursive-paths)
* [Chapter 8 of Aeon's Surreal Renaissance](/learn/book/chapter-08#longer-relational-queries)

---

Source: https://surrealdb.com/docs/learn/data-models/graph/social-network-patterns

# Social network patterns

Model follows, friendships, and interaction histories with graph edges, using weighted relations and counted edges to rank “strongest” ties in a toy social graph.

Social products are a classic graph use case: **people** (or accounts) are nodes, and **follows**, **likes**, **friendships**, and **messages** are edges, often with timestamps or weights on the edge record.

SurrealDB fits this well because edges are tables: you can store how strong a tie is, or derive strength from repeated interactions, without cramming that into either user profile.

## Weight on the edge record

These examples use a small `npc` population and random pairwise interactions. Each `knows` edge accumulates a `greeted` counter whenever two NPCs interact again.

```surql
-- Create 4 'npc' records
CREATE |npc:1..5|;

FOR $npc IN SELECT * FROM npc {
    -- Give each npc 20 random interactions
    FOR $_ IN 0..20 {
      -- Looks for a random NPC, use array::complement to filter out self
      LET $counterpart = rand::enum(array::complement((SELECT *
        FROM npc), [$npc]));
      -- See if they have a relation yet
      LET $existing = SELECT * FROM knows WHERE in = $npc.id
        AND out = $counterpart.id;
      -- If relation exists, increase 'greeted' by one
      IF !!$existing {
        UPDATE $existing SET greeted += 1;
      -- Otherwise create the relation and set 'greeted' to 1
      } ELSE {
        RELATE $npc->knows->$counterpart SET greeted = 1;
      }  
    };
};

SELECT 
	id, 
	->knows.{ like_strength: greeted, with: out } AS relations
	FROM npc;
```

```surql title="Which NPC each NPC likes the most"
[
	{
		id: npc:1,
		relations: [
			{
				like_strength: 8,
				with: npc:3
			},
			{
				like_strength: 8,
				with: npc:4
			},
			{
				like_strength: 4,
				with: npc:2
			}
		]
	},
	{
		id: npc:2,
		relations: [
			{
				like_strength: 10,
				with: npc:1
			},
			{
				like_strength: 4,
				with: npc:3
			},
			{
				like_strength: 6,
				with: npc:4
			}
		]
	},
	{
		id: npc:3,
		relations: [
			{
				like_strength: 6,
				with: npc:2
			},
			{
				like_strength: 3,
				with: npc:4
			},
			{
				like_strength: 11,
				with: npc:1
			}
		]
	},
	{
		id: npc:4,
		relations: [
			{
				like_strength: 7,
				with: npc:1
			},
			{
				like_strength: 6,
				with: npc:3
			},
			{
				like_strength: 7,
				with: npc:2
			}
		]
	}
]
```

## Counting parallel edges instead

If each interaction is its own edge record, **aggregate** to find the strongest ties:

```surql
-- Create 4 'npc' records
CREATE |npc:1..5|;

FOR $npc IN SELECT * FROM npc {
    -- Give each npc 20 random interactions
    FOR $_ IN 0..20 {
      -- Looks for a random NPC, use array::complement to filter out self
      LET $counterpart = rand::enum(array::complement((SELECT *
        FROM npc), [$npc]));
      RELATE $npc->greeted->$counterpart;
    };
};

SELECT 
	count() AS like_strength, 
	in AS npc, 
	out AS counterpart
FROM greeted
GROUP BY npc, counterpart;
```

```surql title="Which NPC each NPC likes the most"
[
	{
		counterpart: npc:2,
		like_strength: 6,
		npc: npc:1
	},
	{
		counterpart: npc:3,
		like_strength: 9,
		npc: npc:1
	},
	{
		counterpart: npc:4,
		like_strength: 5,
		npc: npc:1
	},
	{
		counterpart: npc:1,
		like_strength: 9,
		npc: npc:2
	},
	{
		counterpart: npc:3,
		like_strength: 6,
		npc: npc:2
	},
	{
		counterpart: npc:4,
		like_strength: 5,
		npc: npc:2
	},
	{
		counterpart: npc:1,
		like_strength: 10,
		npc: npc:3
	},
	{
		counterpart: npc:2,
		like_strength: 7,
		npc: npc:3
	},
	{
		counterpart: npc:4,
		like_strength: 3,
		npc: npc:3
	},
	{
		counterpart: npc:1,
		like_strength: 6,
		npc: npc:4
	},
	{
		counterpart: npc:2,
		like_strength: 4,
		npc: npc:4
	},
	{
		counterpart: npc:3,
		like_strength: 10,
		npc: npc:4
	}
]
```

## Friendship and symmetric ties

For mutual “friend” relations where direction should not matter, combine [unique indexes on sorted endpoints](/docs/learn/data-models/graph/creating-relations.md#unique-index-for-relations-between-equals) with [`<->` traversal](/docs/learn/data-models/graph/graph-traversal.md#querying-symmetric-between-equals-relations) so you query both orientations consistently.

## Related guides

* [Record links vs graph relations](/docs/learn/data-models/graph/record-links-vs-graph-relations.md), when edges win for interaction metadata and scoring.
* [Graph traversal](/docs/learn/data-models/graph/graph-traversal.md), arrow syntax for multi-hop social feeds.

---

Source: https://surrealdb.com/docs/learn/data-models/time-series/aggregation-queries

# Aggregation queries

Build downsampling with pre-computed table views, live queries, drop tables, and DEFINE EVENT for anomaly detection; compare SurrealDB to specialised TSDBs.

Time-series data is usually written at full resolution and read at a coarser one, which means aggregating as it arrives rather than on every query. This page covers the four SurrealDB features that do that, and how the result compares with a specialised time-series database.

## Modelling metrics

For doing metrics in SurrealDB you can choose one or combine:

- **Pre-computed table views**
- **Live Queries**
- **Drop tables**
- **Custom events**

### Pre-computed table views

Our [pre-computed table views](/docs/reference/query-language/statements/define/table.md#pre-computed-table-views) are most similar to event-based, incrementally updating, materialised views. Practically, this means our downsampled metrics will always be up to date as it incrementally updates in near real-time when we add more records to the sensor_readings table.

```surql
-- Define a table view which aggregates data from the sensor_readings table
DEFINE TABLE daily_measurements_by_location AS
	SELECT
    id[0] AS location,
		time::day(id[2]) AS day,
		math::mean(temperature_celsius) AS avg_temperature_celsius,
		math::mean(humidity) AS avg_humidity_percent
	FROM sensor_readings
	GROUP BY id[0];

SELECT * FROM daily_measurements_by_location;
```

```surql title="Output"
[
	{
		avg_humidity_percent: 55dec,
		avg_temperature_celsius: 28.4dec,
		day: 13,
		id: daily_measurements_by_location:[
			location:Longleat_House
		],
		location: location:Longleat_House
	}
]
```

For real-time visualisation of our metrics we can then use [Live Queries](/docs/reference/query-language/statements/live-select.md) to stream real-time updates to our client, such as a BI dashboard or embedded analytics code.

```surql
LIVE SELECT * FROM daily_measurements_by_location;
```

### Drop tables

[Drop tables](/docs/reference/query-language/statements/define/table.md#example-usage) are tables that drop all writes once they have been written. A view can be defined on a table whether it is a `DROP` table or not, but `DROP` is recommended if you have no reason to directly query the table that provides the data for the aggregated view.

```surql
-- Drop all writes to the sensor_readings table.
-- We don't need every these readings and can view them
-- via daily_measurements_by_location instead
DEFINE TABLE sensor_readings DROP;
```

These tables can be very useful in a time series context if you want to capture very high-frequency data but only care about storing the aggregated downsampled metrics. They are typically used in combination with either the table views or custom events, such that the metrics are calculated then the underlying data is automatically dropped.

Combining drop tables, table views and live queries gives an event-based, real-time solution that is easy to set up and covers capturing events, creating metrics, dropping stale data and live selects for visualisation.

### Custom events

For something more bespoke, you can [create your own event triggers](/docs/reference/query-language/statements/define/event.md) based on when a record is created, updated or deleted. You can include any valid SurrealQL inside the event.

For example, we can create a simple real-time anomaly detection and notification solution using just SurrealQL events and functions in 5 steps.

1. Define an event to trigger when a record is added to the sensor_readings table.
2. Get the desired time range you want to track.
3. Calculate both the upper and lower threshold for an outlier, using the standard Q1 - 1.5 * IQR formula for the low outliers and Q3 + 1.5 * IQR formula for the high outliers.
4. Check if the current temperature is a low or high outlier.
5. Send an [http::post](/docs/reference/query-language/functions/database-functions/http.md#httppost) request with the outlier details.

```surql
-- Trigger the event on when a record is created
DEFINE EVENT sensor_anomaly_notification
  ON sensor_readings WHEN $event = 'CREATE'
THEN {
    -- Get the desired time range you want to track
    -- here we're grabing just the past hour
    LET $location = $after[0];
    LET $sensor = $after[1];
    LET $temp_past_hour = (
            SELECT VALUE temperature_celsius FROM sensor_readings:[
                $location,
                $sensor,
                time::now() - 1h,
                ]..=[
                $location,
                $sensor,
                time::now()
            ]);
    -- Calculate both the upper and lower threshold for an outlier
    -- using the standard Q1 - 1.5 * IQR formula for the low outliers
    LET $low_outliers = (
        RETURN math::percentile($temp_past_hour,
          25) - 1.5 * math::interquartile($temp_past_hour)
    );
    -- Q3 + 1.5 * IQR formula for the high outliers
    LET $high_outliers = (
        RETURN math::percentile($temp_past_hour,
          75) + 1.5 * math::interquartile($temp_past_hour)
    );
    
    -- If a low outlier is found send a http post request
    -- with the outlier details
    IF $after.temperature_celsius < $low_outliers {
        http::post('https://jsonplaceholder.typicode.com/posts', {
            id: rand::ulid(),
            outlier: $after,
            message: 'Outlier Detected: low temperature'
        });
    };

    -- If a high outlier is found send a http post request
    -- with the outlier details
    IF $after.temperature_celsius > $high_outliers {
        http::post('https://jsonplaceholder.typicode.com/posts', {
            id: rand::ulid(),
            outlier: $after,
            message: 'Outlier Detected: high temperature'
        });
    };
};
```

### Async events

If you need an event to execute in the background out of the main transaction in which they are called, you can use an [async event](/docs/reference/query-language/statements/define/event.md#async-events). This can be useful for events that take a certain amount of time to process. The tradeoff is that your read data will not be consistent until the event or events have finished processing. This is what is known as *eventual consistency*.

## SurrealDB vs specialised time series databases

There are many specialised time series databases out there, so where does SurrealDB fit in?

The advantages SurrealDB has over specialised time series databases are:

- That you can combine our time series functionality with the rest of our multi-model database features. For example, doing full-text search and vector search on your log data.
- No need to learn another query language just for time series. SurrealDB has a unified query language for all its features.

- Connect and enrich your metrics easily, instead of having them being siloed in a separate system. You can have all your data in one place with zero ETL for your various use cases. Whether you’re doing transactional, analytical, ML and AI applications, SurrealDB covers a lot of the use cases a modern application needs.

The advantages specialised time series databases have over SurrealDB currently are:

- More advanced time series features such as custom data retention policies and better data compression.

Whether you pick SurrealDB for your time series use cases depends mostly on whether you are looking to lower your total system complexity or if you are looking for another specialised solution.

For raw event storage and complex record ID patterns, see [IoT and telemetry patterns](/docs/learn/data-models/time-series/iot-and-telemetry-patterns.md).

---

Source: https://surrealdb.com/docs/learn/data-models/time-series/iot-and-telemetry-patterns

# IoT and telemetry patterns

Model sensor readings with nested fields or complex record IDs, use range queries on array-based IDs, and attach metadata through record links.

Sensor data arrives constantly and is almost always read back by time range, which makes the record ID the main modelling decision. This page works through an IoT example covering nested fields, array-based record IDs, range queries over them, and metadata attached through record links.

## Modelling time series data in SurrealDB

Let’s explore a practical example using IoT sensor data.

### Modelling events

The normal way of modelling data in SurrealDB would be as fields in a record, such as `coordinates` or `location`.

```surql
CREATE sensor:ARF8394AAA SET coordinates = (-2.2743, 51.1857);
CREATE location:Longleat_House SET built = d'1580-01-01';

CREATE sensor_readings CONTENT {
    timestamp: time::now(),
    location: location:Longleat_House,
    sensor: sensor:ARF8394AAA,
    temperature_celsius: 28.4,
    humidity_percent: 55
};
```

### Complex record IDs

A more optimised way of working with the `sensor_readings` table in a time series context would however be using an array-based record ID, otherwise known as a complex record ID.

```surql
-- Array-based record IDs
CREATE sensor_readings:[
	location:Longleat_House,
	sensor:ARF8394AAA,
    time::now(),
    28.4
];
```

Storing the same information as a record or as part of the ID might look similar at first glance, but internally it’s optimised for efficient range selection through [range queries](/docs/reference/query-language/language-primitives/data-types/record-ids.md#record-ranges) on record IDs. As all values in SurrealDB can be [compared and sorted](/docs/reference/query-language/language-primitives/data-types/values.md#comparing-and-ordering-values), putting the most important information into the record ID itself allows you to query only a range of record IDs instead of a whole table.

This means less effort selecting the right indexes or partitions, since the ID field already orders the data, and a range read keeps the performance of a key-value lookup regardless of size.

```surql
-- Select all the temperature readings from the start until now 
-- from a specific sensor
SELECT 
    id[0] AS location,
    id[2] AS at,
    id[3] AS temperature_celsius
FROM sensor_readings:[
	location:Longleat_House,
    sensor:ARF8394AAA,
    time::now() - 1d
]..=[
    location:Longleat_House,
    sensor:ARF8394AAA,
	time::now(),
];
```

```surql title="Output"
[
	{
		at: d'2025-04-17T04:04:54.842Z',
		location: location:Longleat_House,
		temperature_celsius: 28.4f
	}
]
```

This is not the only way of doing it. You can also have the metadata in the ID and sensor data in the record itself, like in the example below. Now the record ID only contains the fields useful in a range query, while the remaining fields are kept in the record itself.

```surql
CREATE sensor_readings:[
	location:Longleat_House,
	sensor:ARF8394AAA,
    time::now()
] CONTENT {
    temperature_celsius: 28.4,
    humidity: 55
};

-- Select all the temperature readings from the start until now 
-- from a specific sensor
SELECT 
    id[0] AS location,
    id[2] AS at,
    temperature_celsius
FROM sensor_readings:[
	location:Longleat_House,
    sensor:ARF8394AAA,
    time::now() - 1d
]..=[
    location:Longleat_House,
    sensor:ARF8394AAA,
	time::now(),
];
```

```surql title="Output"
[
	{
		at: d'2025-04-17T05:25:00.030Z',
		location: location:Longleat_House,
		temperature_celsius: 28.4f
	}
]
```

### Ids inside ids

The last thing to note here is that we’ve actually been using record IDs inside our complex record IDs! This is known as a [record link](/docs/reference/query-language/language-primitives/record-links.md), which allows us to reduce the fields in our main time series table to only the necessary ones by offloading most of the metadata to connected tables.

In our case we have:

- `location:Longleat_House`, which refers to the `Longleat_House` ID in the `location` table. There we put all the metadata about the location itself such as [geo coordinates](/docs/reference/query-language/language-primitives/data-types/geometries.md).
- `sensor:ARF8394AAA`, which refers to the `ARF8394AAA` ID on the `sensor` table. There we could put all the metadata about the sensor such as location, firmware, when it was bought and when it needs maintenance.

Getting the connected data needs no table scan either, because each link points directly to a specific record on a specific table. In the example below, with time for the sensor readings set exactly to the hour (`2024-08-13T05:00:00`), the record ID is easy to predict.

```surql
CREATE sensor:ARF8394AAA SET coordinates = (-2.2743, 51.1857);
CREATE location:Longleat_House SET built = d'1580-01-01';

-- location + sensor + easy to predict timestamp
CREATE sensor_readings:[
	location:Longleat_House,
	sensor:ARF8394AAA,
    d'2024-08-13T05:00:00Z'
] CONTENT {
    temperature_celsius: 28.4,
    humidity: 55
};

-- Select all fields in the ID and the coordinates field from the sensor table
SELECT id, id[1].coordinates AS sensor_coordinates
FROM sensor_readings:[
	location:Longleat_House,
	sensor:ARF8394AAA,
    d'2024-08-13T05:00:00Z'
];
```

```surql title="Output"
[
	{
		id: sensor_readings:[
			location:Longleat_House,
			sensor:ARF8394AAA,
			d'2024-08-13T05:00:00Z'
		],
		sensor_coordinates: (-2.2743, 51.1857)
	}
]
```

For aggregations, materialised views, and retention-style patterns built on top of these events, see [Aggregation queries](/docs/learn/data-models/time-series/aggregation-queries.md).

---

Source: https://surrealdb.com/docs/learn/data-models/time-series/overview

# Time-series model

Learn how time-series workloads centre on timestamps, tags, and aggregations in SurrealDB, and where to find guides on bucketing, IoT-style modelling, and metrics.

A time series database is optimised for storing, querying, and managing time-stamped data. Rather than treating the timestamp as just another attribute, TSDBs place time at the forefront of data modelling, indexing, retention, and query optimisation. The queries in time series workloads often include:

* Finding or aggregating values over time windows (e.g., last 5 minutes, last 30 days).
* Detecting patterns or anomalies (peaks, troughs, rolling averages).
* Combining real-time and historical data for a holistic view of a system’s behaviour.

This section helps you understand how to think like a time series database user, and how SurrealDB can accommodate these needs.

## Core concepts of time series modelling

- Timestamp: Every record includes at least one timestamp field, typically indicating when the data was created, measured, or observed.

- Measurement / metrics: Time series data often revolves around measurements, like temperature, CPU usage, or stock prices. Each measurement can have multiple fields or tags.

- Tags (or labels): Extra dimensions that categorise data. For instance, a sensor reading might have a `location` or a `device_id` tag. These tags help you filter or group data.

- Aggregations: Because time series data can be massive (think thousands of data points per second), it is common to store or generate aggregated values (e.g., hourly averages or daily sums) for faster analytics.

- Retention policies: Time series datasets can grow exponentially. Many TSDBs support retention policies that drop or downsample older data while keeping recent data at high granularity.

- Time buckets: It is typical to group data into buckets (e.g., 1-minute, 5-minute, or 1-day intervals) for analytics.

## Benefits of using time series databases

- Efficient ingestion: TSDBs are optimised for high write throughput, as time series data often arrives in large bursts or continuous streams.

- Fast time-based queries: Data structures are specialised for retrieving data over specific time windows or intervals.

- Built-in aggregation and downsampling: Many TSDBs offer native functions to summarise data by minute, hour, or other intervals.

- Scalability: Time partitioning or sharding strategies make it easier to handle large volumes of time series data.

- Retention control: You can automatically expire older data or keep only aggregated versions to save storage.

## Where to go next

- [Time buckets and windowing](/docs/learn/data-models/time-series/time-buckets-and-windowing.md): bucketing, retention, and window-style thinking.
- [Aggregation queries](/docs/learn/data-models/time-series/aggregation-queries.md): pre-computed table views, live queries, drop tables, events, and how SurrealDB compares to specialised TSDBs.
- [IoT and telemetry patterns](/docs/learn/data-models/time-series/iot-and-telemetry-patterns.md): sensor readings, complex record IDs, and linking metadata through record IDs.

## Going further

- [Using SurrealDB as a time series database](/docs/learn/data-models/time-series/using-as-time-series-database.md) - the full pattern end to end

---

Source: https://surrealdb.com/docs/learn/data-models/time-series/time-buckets-and-windowing

# Time buckets and windowing

Group time-stamped records into buckets with time::floor and GROUP BY, plan retention and downsampling, and align queries with windowed analytics workloads.

Time series analytics almost always relies on windows: “per minute”, “last 24 hours”, “calendar month”. In SurrealDB you express these ideas with ordinary SurrealQL - filtering on timestamps or on components of [complex record IDs](/docs/learn/data-models/time-series/iot-and-telemetry-patterns.md), and grouping with functions such as `time::day()` where appropriate.

## Time buckets

A bucket is a fixed or sliding interval that groups raw events into summaries: counts, sums, averages, or percentiles. Buckets are how dashboards show smooth lines instead of millions of raw points.

When you design buckets, choose the grain that matches how people consume the data (operations often want 1-minute or 5-minute data; executives may only need daily rollups).

**Example - hourly energy readings:** [`time::floor()`](/docs/reference/query-language/functions/database-functions/time.md#timefloor) snaps each `ts` down to the start of a duration (here `1h`), and [`GROUP BY`](/docs/reference/query-language/clauses/group.md) collapses every record in that window into one aggregate.

```surql
CREATE measurement SET ts = d"2025-04-10T09:15:00Z", kwh = 1.2;
CREATE measurement SET ts = d"2025-04-10T09:42:00Z", kwh = 0.8;
CREATE measurement SET ts = d"2025-04-10T10:05:00Z", kwh = 2.0;

SELECT
	count(), time::floor(ts, 1h) AS hour_bucket
FROM measurement
GROUP BY hour_bucket;
```

```surql title="Output"
[
	{
		count: 2,
		hour_bucket: d'2025-04-10T09:00:00Z'
	},
	{
		count: 1,
		hour_bucket: d'2025-04-10T10:00:00Z'
	}
]
```

For calendar-aligned buckets (whole days or months), [`time::group()`](/docs/reference/query-language/functions/database-functions/time.md#timegroup) is often simpler than picking a duration.

## Aligning windows to business time

Decide whether windows are wall-clock (calendar buckets) or relative (“last 15 minutes” rolling). Range queries on record IDs or timestamp fields should use the same convention across writers and readers so aggregates line up.

For concrete sensor and metrics examples, continue with [IoT and telemetry patterns](/docs/learn/data-models/time-series/iot-and-telemetry-patterns.md) and [Aggregation queries](/docs/learn/data-models/time-series/aggregation-queries.md).

---

Source: https://surrealdb.com/docs/learn/data-models/time-series/using-as-time-series-database

# Using SurrealDB as a time series database

In this guide, you will learn how to “think” in a time-series database and show how SurrealDB helps you implement these concepts seamlessly.

A time series database is optimised for storing, querying, and managing time-stamped data. Rather than treating the timestamp as just another attribute, TSDBs place time at the forefront of data modelling, indexing, retention, and query optimisation. The queries in time series workloads often include:

* Finding or aggregating values over time windows (e.g., last 5 minutes, last 30 days).
* Detecting patterns or anomalies (peaks, troughs, rolling averages).
* Combining real-time and historical data for a holistic view of a system’s behaviour.

This guide will help you understand how to think like a time series database user, and how SurrealDB can accommodate these needs.

## Core concepts of time series modelling

- **Timestamp**: Every record includes at least one timestamp field, typically indicating when the data was created, measured, or observed.

- **Measurement / Metrics**: Time series data often revolves around measurements, like temperature, CPU usage, stock prices. Each measurement can have multiple fields or tags.

- **Tags (or Labels)**: Extra dimensions that categorize data. For instance, a sensor reading might have a `location` or a `device_id` tag. These tags help you filter or group data.

- **Aggregations**: Because time series data can be massive (think thousands of data points per second), it’s common to store or generate aggregated values (e.g., hourly averages or daily sums) for faster analytics.

- **Retention Policies**: Time series datasets can grow exponentially. Many TSDBs support retention policies that drop or downsample older data while keeping recent data at high granularity.

- **Time Buckets**: It’s typical to group data into buckets (e.g., 1-minute, 5-minute, or 1-day intervals) for analytics.

## Benefits of using time series databases

- **Efficient Ingestion**: TSDBs are optimised for high write throughput, as time series data often arrives in large bursts or continuous streams.

- **Fast Time-Based Queries**: Data structures are specialized for retrieving data over specific time windows or intervals.

- **Built-In Aggregation and Downsampling**: Many TSDBs offer native functions to summarize data by minute, hour, or other intervals.

- **Scalability**: Time partitioning or sharding strategies make it easier to handle large volumes of time series data.

- **Retention Control**: You can automatically expire older data or keep only aggregated versions to save storage.

## Modelling time series data in SurrealDB

The following sections work through a practical example using IoT sensor data.

### Modelling events
The normal way of modelling data in SurrealDB would be as fields in a record, such as `coordinates` or `location`.

```surql
CREATE sensor:ARF8394AAA SET coordinates = (-2.2743, 51.1857);
CREATE location:Longleat_House SET built = d'1580-01-01';

CREATE sensor_readings CONTENT {
    timestamp: time::now(),
    location: location:Longleat_House,
    sensor: sensor:ARF8394AAA,
    temperture_celsius: 28.4,
    humidity_percent: 55
};
```

### Complex record IDs

A more optimised way of working with the `sensor_readings` table in a time series context would however be using an array-based record ID, otherwise known as a complex record ID.

```surql
-- Array-based record IDs
CREATE sensor_readings:[
	location:Longleat_House,
	sensor:ARF8394AAA,
    time::now(),
    28.4
];
```

Storing the same information as a record or as part of the ID might look similar at first glance, but internally it’s optimised for efficient range selection through [range queries](/docs/reference/query-language/language-primitives/data-types/record-ids.md#record-ranges) on record IDs. As all values in SurrealDB can be [compared and sorted](/docs/reference/query-language/language-primitives/data-types/values.md#comparing-and-ordering-values), putting the most important information into the record ID itself allows you to query only a range of record IDs instead of a whole table.

This means less effort selecting the right indexes or partitions, since the ID field already orders the data, and a range read keeps the performance of a key-value lookup regardless of size.

```surql
-- Select all the temperature readings from the start until now 
-- from a specific sensor
SELECT 
    id[0] AS location,
    id[2] AS at,
    id[3] AS temperature_celsius
FROM sensor_readings:[
	location:Longleat_House,
    sensor:ARF8394AAA,
    time::now() - 1d
]..=[
    location:Longleat_House,
    sensor:ARF8394AAA,
	time::now(),
];
```

```surql title="Output"
[
	{
		at: d'2025-04-17T04:04:54.842Z',
		location: location:Longleat_House,
		temperature_celsius: 28.4f
	}
]
```

This is not the only way of doing it. You can also have the metadata in the ID and sensor data in the record itself, like in the example below. Now the record ID only contains the fields useful in a range query, while the remaining fields are kept in the record itself.

```surql
CREATE sensor_readings:[
	location:Longleat_House,
	sensor:ARF8394AAA,
    time::now()
] CONTENT {
    temperature_celsius: 28.4,
    humidity: 55
};

-- Select all the temperature readings from the start until now 
-- from a specific sensor
SELECT 
    id[0] AS location,
    id[2] AS at,
    temperature_celsius
FROM sensor_readings:[
	location:Longleat_House,
    sensor:ARF8394AAA,
    time::now() - 1d
]..=[
    location:Longleat_House,
    sensor:ARF8394AAA,
	time::now(),
];
```

```surql title="Output"
[
	{
		at: d'2025-04-17T05:25:00.030Z',
		location: location:Longleat_House,
		temperature_celsius: 28.4f
	}
]
```

### IDs inside IDs
The complex record IDs above also contain record IDs of their own. This is known as a [record link](/docs/reference/query-language/language-primitives/record-links.md), which allows us to reduce the fields in our main time series table to only the necessary ones by offloading most of the metadata to connected tables.

In our case we have:

- `location:Longleat_House`, which refers to the `Longleat_House` ID in the `location` table. There we put all the metadata about the location itself such as [geo coordinates](/docs/reference/query-language/language-primitives/data-types/geometries.md).
- `sensor:ARF8394AAA`, which refers to the `ARF8394AAA` ID on the `sensor` table. There we could put all the metadata about the sensor such as location, firmware, when it was bought and when it needs maintenance.

Getting the connected data needs no table scan either, because each link points directly to a specific record on a specific table. In the example below, with time for the sensor readings set exactly to the hour (`2024-08-13T05:00:00`), the record ID is easy to predict.

```surql
CREATE sensor:ARF8394AAA SET coordinates = (-2.2743, 51.1857);
CREATE location:Longleat_House SET built = d'1580-01-01';

-- location + sensor + easy to predict timestamp
CREATE sensor_readings:[
	location:Longleat_House,
	sensor:ARF8394AAA,
    d'2024-08-13T05:00:00Z'
] CONTENT {
    temperature_celsius: 28.4,
    humidity: 55
};

-- Select all fields in the ID and the coordinates field from the sensor table
SELECT id, id[1].coordinates AS sensor_coordinates
FROM sensor_readings:[
	location:Longleat_House,
	sensor:ARF8394AAA,
    d'2024-08-13T05:00:00Z'
];
```

```surql title="Output"
[
	{
		id: sensor_readings:[
			location:Longleat_House,
			sensor:ARF8394AAA,
			d'2024-08-13T05:00:00Z'
		],
		sensor_coordinates: (-2.2743, 51.1857)
	}
]
```

The next step is to turn this event data into metrics.

### Modelling metrics
For doing metrics in SurrealDB you can choose one or combine:

- **Pre-computed table views**
- **Live Queries**
- **Drop tables**
- **Custom events**

#### Pre-computed table views
Our [pre-computed table views](/docs/reference/query-language/statements/define/table.md#pre-computed-table-views) are most similar to event-based, incrementally updating, materialised views. Practically, this means our downsampled metrics will always be up to date as it incrementally updates in near real-time when we add more records to the sensor_readings table.

```surql
-- Define a table view which aggregates data from the sensor_readings table
DEFINE TABLE daily_measurements_by_location AS
	SELECT
    id[0] AS location,
		time::day(id[2]) AS day,
		math::mean(temperature_celsius) AS avg_temperture_celsius,
		math::mean(humidity) AS avg_humidity_percent
	FROM sensor_readings
	GROUP BY id[0];

SELECT * FROM daily_measurements_by_location;
```

```surql title="Output"
[
	{
		avg_humidity_percent: 55dec,
		avg_temperture_celsius: 28.4dec,
		day: 13,
		id: daily_measurements_by_location:[
			location:Longleat_House
		],
		location: location:Longleat_House
	}
]
```

For real-time visualisation of our metrics we can then use [Live Queries](/docs/reference/query-language/statements/live-select.md) to stream real-time updates to our client, such as a BI dashboard or embedded analytics code.

```surql
LIVE SELECT * FROM daily_measurements_by_location;
```

#### Drop tables

[Drop tables](/docs/reference/query-language/statements/define/table.md#example-usage) are tables that drop all writes once they have been written. A view can be defined on a table whether it is a `DROP` table or not, but `DROP` is recommended if you have no reason to directly query the table that provides the data for the aggregated view.

```surql
-- Drop all writes to the sensor_readings table.
-- We don't need every these readings and can view them
-- via daily_measurements_by_location instead
DEFINE TABLE sensor_readings DROP;
```

These tables can be very useful in a time series context if you want to capture very high-frequency data but only care about storing the aggregated downsampled metrics. They are typically used in combination with either the table views or custom events, such that the metrics are calculated then the underlying data is automatically dropped.

Combining drop tables, table views and live queries gives an event-based, real-time solution that is easy to set up and covers capturing events, creating metrics, dropping stale data and live selects for visualisation.

#### Custom events
For something more bespoke, you can [create your own event triggers](/docs/reference/query-language/statements/define/event.md) based on when a record is created, updated or deleted. You can include any valid SurrealQL inside the event.

For example, we can create a simple real-time anomaly detection and notification solution using just SurrealQL events and functions in 5 steps.

1. Define an event to trigger when a record is added to the sensor_readings table.
2. Get the desired time range you want to track.
3. Calculate both the upper and lower threshold for an outlier, using the standard Q1 - 1.5 * IQR formula for the low outliers and Q3 + 1.5 * IQR formula for the high outliers.
4. Check if the current temperature is a low or high outlier.
5. Send an [http::post](/docs/reference/query-language/functions/database-functions/http.md#httppost) request with the outlier details.

```surql
-- Trigger the event on when a record is created
DEFINE EVENT sensor_anomaly_notification
  ON sensor_readings WHEN $event = 'CREATE'
THEN {
    -- Get the desired time range you want to track
    -- here we're grabing just the past hour
    LET $location = $after[0];
    LET $sensor = $after[1];
    LET $temp_past_hour = (
            SELECT VALUE temperature_celsius FROM sensor_readings:[
                $location,
                $sensor,
                time::now() - 1h,
                ]..=[
                $location,
                $sensor,
                time::now()
            ]);
    -- Calculate both the upper and lower threshold for an outlier
    -- using the standard Q1 - 1.5 * IQR formula for the low outliers
    LET $low_outliers = (
        RETURN math::percentile($temp_past_hour,
          25) - 1.5 * math::interquartile($temp_past_hour)
    );
    -- Q3 + 1.5 * IQR formula for the high outliers
    LET $high_outliers = (
        RETURN math::percentile($temp_past_hour,
          75) + 1.5 * math::interquartile($temp_past_hour)
    );
    
    -- If a low outlier is found send a http post request
    -- with the outlier details
    IF $after.temperature_celsius < $low_outliers {
        http::post('https://jsonplaceholder.typicode.com/posts', {
            id: rand::ulid(),
            outlier: $after,
            message: 'Outlier Detected: low temperature'
        });
    };

    -- If a high outlier is found send a http post request
    -- with the outlier details
    IF $after.temperature_celsius > $high_outliers {
        http::post('https://jsonplaceholder.typicode.com/posts', {
            id: rand::ulid(),
            outlier: $after,
            message: 'Outlier Detected: high temperature'
        });
    };
};
```

### Async events

If you need an event to execute in the background out of the main transaction in which they are called, you can use an [async event](/docs/reference/query-language/statements/define/event.md#async-events). This can be useful for events that take a certain amount of time to process. The tradeoff is that your read data will not be consistent until the event or events have finished processing. This is what is known as *eventual consistency*.

## SurrealDB vs specialised time series databases
There are many specialised time series databases out there, so where does SurrealDB fit in?

The advantages SurrealDB has over specialised time series databases are:

- That you can combine our time series functionality with the rest of our multi-model database features. For example, doing full-text search and vector search on your log data.
- No need to learn another query language just for time series. SurrealDB has a unified query language for all its features.

- Connect and enrich your metrics easily, instead of having them being siloed in a separate system. You can have all your data in one place with zero ETL for your various use cases. Whether you’re doing transactional, analytical, ML and AI applications, SurrealDB covers a lot of the use cases a modern application needs.

The advantages specialised time series databases have over SurrealDB currently are:

- More advanced time series features such as custom data retention policies and better data compression.

Whether you pick SurrealDB for your time series use cases depends mostly on whether you are looking to lower your total system complexity or if you are looking for another specialised solution.

---

Source: https://surrealdb.com/docs/learn/data-models/vector-search/embedding-pipelines

# Embedding pipelines

Store vector fields on records, size embeddings for throughput and accuracy, and trim dimensions when models allow.

Embeddings are produced **outside** SurrealDB by your chosen model (API or local). The database’s job is to store the resulting arrays and index them for retrieval.

To store vectors in SurrealDB, you typically define a field within your data schema dedicated to holding the vector data. These vectors represent data points in embedding space and can be used for various applications, from recommendation systems to image recognition. Below is an example of how to create records with vector embeddings:

```surql
CREATE Document:1 CONTENT {
   items: [
    {
      content: "apple",
      embedding: [0.00995, -0.02680, -0.01881, -0.08697]
    }
  ]
};
```

There are no strict rules or limitations regarding the length of the embeddings, and they can be as large as needed. Just keep in mind that larger embeddings lead to more data to process and that can affect performance and query times based on your physical hardware.

In fact, embeddings retrieved from a model can be cut down to any length you prefer if the accuracy is still acceptable for your use case.

For end-to-end similarity examples, see [Similarity search](/docs/learn/data-models/vector-search/similarity-search.md). For HNSW, DISKANN, and brute-force trade-offs, see [Vector indexes](/docs/learn/data-models/vector-search/vector-indexes.md).

---

Source: https://surrealdb.com/docs/learn/data-models/vector-search/hybrid-search

# Hybrid search

Compare lexical full-text search with vector similarity on the same dataset and fuse rankings with search::rrf and related helpers.

Full-text search and vector search find different things, and the two together usually rank better than either alone. This page compares them on one dataset, then fuses their rankings with `search::rrf` and related helpers.

## Vector search vs full-text search

SurrealDB supports [full-text search](/docs/learn/data-models/full-text-search/overview.md) and Vector Search. Full-text search (FTS) involves indexing documents using an [FTS index](/docs/reference/query-language/statements/define/indexes.md#full-text-search-fulltext-index) that makes use of an [analyzer](/docs/reference/query-language/statements/define/analyzer.md) that breaks down text using [tokenizers](/docs/reference/query-language/statements/define/analyzer.md#tokenizers) and [filters](/docs/reference/query-language/statements/define/analyzer.md#filters).

<img src="~/assets/img/lead.png" alt="Google search for the word 'lead'" />

The image above is a Google search for the word “lead”, a word with more than one definition (and pronunciation!). Lead can mean 'taking initiative', as well as the chemical element with the symbol 'Pb'.

Consider this in the context of a database of liquid samples which note down harmful chemicals that are found in them.

In the example below, we have a table called `liquids` with a `sample` field and a `content` field. Next, we can define a [full-text index](/docs/reference/query-language/statements/define/indexes.md#full-text-search-fulltext-index) on the `content` field by first defining an analyzer called `liquid_analyzer`. We can then [define an index](/docs/reference/query-language/statements/define/indexes.md) on the content field in the liquid table and set our [custom analyzer](/docs/reference/query-language/statements/define/analyzer.md) (`liquid_analyzer`) to search through the index.

Then, using the select statement to retrieve all the samples containing the chemical lead will also bring up samples that mention the word `lead`.

```surql
-- Insert a sample & content field into a liquids table
INSERT INTO liquids [
    {sample:'Sea water', content: 'The sea water contains some amount of lead'},
    {sample:'Tap water', content: 'The team lead by Dr. Rose found out that the tap water in was potable'},
    {sample:'Sewage water', content: 'High amounts of a were found in Sewage water'}
];
-- Define an analyzer for the liquid table and an index on the content field with the analyzer
DEFINE ANALYZER liquid_analyzer TOKENIZERS blank,class,camel,punct FILTERS snowball(english);
DEFINE INDEX liquid_content ON liquids FIELDS content FULLTEXT ANALYZER liquid_analyzer BM25 HIGHLIGHTS;
-- Retrieve all the samples containing the chemical lead will also bring up samples that simply mention the word lead
SELECT
  sample,
  content
FROM liquids
WHERE content @0@ 'lead';
```

If you read through the content of the tap water sample, you’ll notice that it does not contain any lead in it but it has the mention of the word `lead` under “The team lead by Dr. Rose…” which means that the team was guided by Dr. Rose.

The search pulled up both the records although the tap water sample had no lead in it. This example shows us that while full-text search does a great job at matching query terms with indexed documents, on its own it may not be the best solution for use cases where the query terms have deeper context and scope for ambiguity.

For vector-side retrieval on the same example, see [Similarity search](/docs/learn/data-models/vector-search/similarity-search.md).

## Hybrid search functions

As mentioned above, full-text search and vector search can both be used in SurrealDB. In addition, some functions exist inside the [`search::`](/docs/reference/query-language/functions/database-functions/search.md) namespace that take both full-text and vector arguments in order to produce a single unified output.

Here is an example of one of them called [`search::rrf()`](/docs/reference/query-language/functions/database-functions/search.md#searchrrf) which does this using an algorithm called reciprocal rank fusion.

```surql
-- Sample data --
CREATE test:1 SET text = "Graph databases are great.", embedding = [0.10, 0.20, 0.30];
CREATE test:2 SET text = "Relational databases store tables.", embedding = [0.05, 0.10, 0.00];
CREATE test:3 SET text = "This document mentions graphs.", embedding = [0.20, 0.10, 0.25];

-- Analyzer used by the full‑text index
DEFINE ANALYZER simple TOKENIZERS class, punct FILTERS lowercase, ascii;

-- Full‑text index
DEFINE INDEX idx_text
  ON TABLE test FIELDS text FULLTEXT ANALYZER simple BM25;
```

**HNSW (in-memory)**

```surql
DEFINE INDEX idx_embedding
    ON TABLE test 
    FIELDS embedding 
    HNSW DIMENSION 3 DIST COSINE;
```

**DISKANN (on-disk)**

_(since v3.1.0)_

```surql
DEFINE INDEX idx_embedding
    ON TABLE test 
    FIELDS embedding 
    DISKANN DIMENSION 3 DIST COSINE TYPE F32;
```

For very large embedding sets that do not fit comfortably in RAM, prefer DISKANN. It is **not available on WASM** builds.

```surql
-- Query vector (whatever your embedding model produced for "graph databases")
LET $qvec = [0.12, 0.18, 0.27];

-- Vector search: top 2 nearest neighbours
LET $vs = SELECT id FROM test  WHERE embedding <|2,100|> $qvec;

-- Full‑text search: top 2 lexical matches
LET $ft = SELECT id, search::score(1) as score FROM test
          WHERE text @1@ 'graph' ORDER BY score DESC LIMIT 2;

-- Fuse with Reciprocal Rank Fusion (k defaults to 60 if omitted)
search::rrf([$vs, $ft], 2, 60);
```

> [!NOTE]
> On a small dataset the lexical half of a hybrid query can contribute membership without contributing order. BM25 clamps the weight of any term appearing in half or more of the indexed documents to zero, so `search::score` returns `0` for every match and the `ORDER BY score DESC` above has nothing to sort on. Reciprocal rank fusion then folds in an arbitrary ordering of the matched records. See [why a score can be 0](/docs/learn/data-models/full-text-search/scoring-and-ranking.md#why-a-score-can-be-0).

---

Source: https://surrealdb.com/docs/learn/data-models/vector-search/overview

# Vector search model

Learn how vector embeddings represent semantic similarity, how SurrealDB stores and queries vectors alongside other models, and where to find guides on pipelines, similarity, indexes, hybrid search, and RAG-style retrieval.

A vector database is specialised for storing high-dimensional vectors and for efficiently performing queries on them. Rather than searching on exact values or text-based queries, vector databases let you search based on semantic similarity. For instance, in a text embedding scenario, you can find documents that are semantically similar to a given query, even if they do not share the same keywords.

This allows for usage in areas such as:

- Recommendation systems: Suggest items (movies, products, etc.) similar to what a user has liked based on learned embeddings.
- Image or audio search: Identify images or audio clips semantically similar to a given sample.
- Clustering and classification: Perform unsupervised clustering of data points or quickly identify which category a vector is close to.

With SurrealDB’s query language you can define vector fields, store numeric arrays as embeddings, create indexes, and perform similarity queries. SurrealDB unifies these features in one database, so embeddings, documents and graph relationships share a single source of truth instead of being spread across a dedicated vector database, a separate document database and a graph database.

Relational and document models focus on well-defined schemas and relationships. Vector databases revolve around embeddings: numerical representations of objects (like text, images, audio snippets, etc.) in a continuous vector space. This allows you to design data structures and queries to exploit these embeddings for similarity search or AI-driven retrieval.

## Core concepts of vector-oriented modelling

Vector search is a search mechanism that goes beyond traditional keyword matching and text-based search methods to capture deeper characteristics and similarities between data.

Vector search isn't new to the world of data science. [Gerard Salton](https://en.wikipedia.org/w/index.php?title=Gerard_Salton&oldid=1334543561), known as the Father of Information Retrieval, introduced the Vector Space Model, cosine similarity, and TF-IDF for information retrieval around 1960.

Vector search converts data such as text, images, or sounds into numerical vectors, called vector embeddings. You can think of vector embeddings as cells. In the same way that cells form the basic structural and biological unit of all known living organisms, vector embeddings serve as the basic units of data representation in vector search.

In practice, embeddings are typically dense vectors of real numbers that capture the semantic or contextual meaning of data. For instance, in Natural Language Processing (NLP), a word or sentence can be transformed into a vector of length 128, 256, 768, or even more dimensions. The idea is that similar objects (in meaning) end up having similar vector representations, making it possible to compute how close they are in the vector space.

Embeddings themselves are not generated by the database. Instead, they depend on which model is used to generate them. Various companies such as OpenAI and Mistral have both free and paid models, while many other [free models](/docs/build/integrations/embeddings-providers/fastembed.md) exist to generate embeddings.

Inside a database they will be stored in this sort of manner.

```surql
[
	{
		embedding: [
			0.0007022718782536685,
			0.004178352188318968,
			0.009888353757560253,
            -- and so on for 128, 256, 768, or even more numbers
		],
		text: "To be, or not to be: that is the question."
	},
	{
		embedding: [
			-0.027426932007074356,
			0.0008020889363251626,
			-0.02949262224137783
		],
		text: "All the world’s a stage, and all the men and women merely players."
	},
	{
		embedding: [
			-0.05859993398189545,
			-0.011999601498246193,
			-0.06185592710971832
		],
		text: "The course of true love never did run smooth."
	}
]
```

## Where to go next

The guides in this section break down how to store embeddings and query them in practice:

- [Embedding pipelines](/docs/learn/data-models/vector-search/embedding-pipelines.md): defining vector fields, record shapes, and dimension trade-offs.
- [Similarity search](/docs/learn/data-models/vector-search/similarity-search.md): worked examples, the `vector::` function family, and KNN-style filtering.
- [Vector indexes](/docs/learn/data-models/vector-search/vector-indexes.md): brute force vs HNSW vs DISKANN, parameters, and the query cheat sheet.
- [Hybrid search](/docs/learn/data-models/vector-search/hybrid-search.md): combining lexical full-text search with vector retrieval, including `search::rrf()`.
- [RAG architecture patterns](/docs/learn/data-models/vector-search/rag-architecture-patterns.md): retrieval-oriented conclusions and further reading.

For statement-level reference, see [Vector functions](/docs/reference/query-language/functions/database-functions/vector.md) and [`DEFINE INDEX` (vector search)](/docs/reference/query-language/statements/define/indexes.md#vector-search-indexes).

---

Source: https://surrealdb.com/docs/learn/data-models/vector-search/rag-architecture-patterns

# RAG architecture patterns

Map retrieval-augmented generation to SurrealDB using chunk storage, embeddings, hybrid search, filters, and operational concerns for RAG.

Retrieval-augmented generation (RAG) combines retrieval from your own data with generation from a language model. Instead of relying only on the model’s training data, you ground answers in chunks of text (and sometimes structured facts) that you control. SurrealDB is then used for storage and retrieval, allowing you to keep chunk text, metadata, access rules, and vector embeddings in one database and query them with SurrealQL.

## What RAG changes

A plain LLM call answers from parametric memory only. RAG adds a knowledge plane to this, in which documents are split into chunks, each chunk gets an embedding from a model you choose, and at query time you retrieve the most relevant chunks before (or while) the LLM writes an answer.

## Typical pipeline stages

1. Ingest: Load sources (files, web pages, tickets, database exports).
2. Chunk: Split text into segments with stable boundaries (headings, paragraphs, token limits) and optional overlap so ideas are not split awkwardly.
3. Embed: Call an embedding API or local model; store the resulting vector with each chunk. See [Embedding pipelines](/docs/learn/data-models/vector-search/embedding-pipelines.md) and the [embeddings integrations](/docs/build/integrations/embeddings-providers/fastembed.md) for options outside the database.
4. Index: Define [vector indexes](/docs/learn/data-models/vector-search/vector-indexes.md) (for example HNSW) on embedding fields, and optionally [full-text indexes](/docs/learn/data-models/full-text-search/overview.md) on chunk text for lexical search.
5. Retrieve: For a user question, embed the query (or use the same model family as the corpus), run similarity search, optionally fuse with keyword results (see [Hybrid search](/docs/learn/data-models/vector-search/hybrid-search.md)).
6. Generate: Pass the top chunks as context to the LLM, with instructions to cite or stay within that context.

## Retrieval patterns

- Dense retrieval: KNN over embeddings with [`vector::distance::knn()`](/docs/reference/query-language/functions/database-functions/vector.md#vectordistanceknn) and the patterns in [Similarity search](/docs/learn/data-models/vector-search/similarity-search.md). Good for paraphrases and conceptual match.
- Hybrid retrieval: Combine dense scores with [full-text search](/docs/learn/data-models/full-text-search/overview.md) when exact product names, error codes, or legislation matter; use [`search::rrf()`](/docs/learn/data-models/vector-search/hybrid-search.md) or related helpers as described under [Hybrid search](/docs/learn/data-models/vector-search/hybrid-search.md).

Re-ranking (a second model that scores the top *k* candidates) is often implemented in the application layer; SurrealDB supplies the candidate set efficiently.

## Chunking, metadata, and citations

There is no single best chunk size: smaller chunks improve precision but lose surrounding context; larger chunks add context but dilute embeddings. Many teams store title, heading path, or summary fields to improve retrieval without inflating the embedded body.

For citations, persist enough metadata to map a chunk back to a human-readable source (page, anchor, ticket id). The LLM should only “see” what you retrieved; clear provenance reduces hallucinated references.

## Operations and quality

- Embedding model changes usually require re-embedding the corpus or maintaining a version dimension; plan migrations before switching dimensions or distance metrics.
- Staleness: when sources update, replace or invalidate affected chunks so answers do not quote obsolete text.
- Evaluation: track retrieval hit rate, user thumbs-up/down, or offline benchmarks on labelled questions.

## Resources

- [Vector search cheat sheet](/docs/learn/data-models/vector-search/vector-indexes.md#vector-search-cheat-sheet)
- [Vector search overview](/docs/learn/data-models/vector-search/overview.md)
- [Hybrid search](/docs/learn/data-models/vector-search/hybrid-search.md)
- [Full-text search overview](/docs/learn/data-models/full-text-search/overview.md)
- [Embedding pipelines](/docs/learn/data-models/vector-search/embedding-pipelines.md)
- [Vector functions](/docs/reference/query-language/functions/database-functions/vector.md)
- [Vector search indexes](/docs/reference/query-language/statements/define/indexes.md#vector-search-indexes)
- [FastEmbed and embeddings integrations](/docs/build/integrations/embeddings-providers/fastembed.md)
- [YouTube: Vector search intro](https://www.youtube.com/watch?v=MqddPmgKSCs)

---

Source: https://surrealdb.com/docs/learn/data-models/vector-search/similarity-search

# Similarity search

Run SurrealQL similarity queries with the liquids example, use vector:: distance and similarity helpers, and filter KNN results with predicates.

Vector search finds records by meaning rather than by the words they contain. This page runs similarity queries over an example dataset, covering the `vector::` distance and similarity helpers and how to filter KNN results with predicates.

## Vector search in SurrealDB

<img src="~/assets/img/image/light/VC.png" darkSrc="~/assets/img/image/dark/VC.png" alt="What is Vector Search" />

Vector search in SurrealDB can be used in place of full-text search, or together with it.

For example, still using the same `liquids` table, you can store the chemical composition of the liquid samples in a vector format.

```surql
-- Insert a sample & content field into a liquids table
INSERT INTO liquidsVector [
    {
        sample:'Sea water', 
        content: 'The sea water contains some amount of lead', 
        embedding: [0.1, 0.2, 0.3, 0.4] },
    {
        sample:'Tap water', 
        content:
          'The team lead by Dr. Rose found out that the tap water in was potable',
        embedding:[1.0, 0.1, 0.4, 0.3]
    },
    {
        sample:'Sewage water', 
        content: 'High amounts of a were found in Sewage water', 
        embedding : [0.4, 0.3, 0.2, 0.1]
    }
];
```
Notice that we have added an `embedding` field to the table. This field will store the vector embeddings of the content field so we can perform vector searches on it.

```surql
-- Define a vector index on the liquidsVector table for embedding field
DEFINE INDEX mt_pts ON liquidsVector FIELDS embedding HNSW DIMENSION 4 DIST COSINE TYPE F32;
-- Insert a sample & content field into a liquids table
INSERT INTO liquidsVector [
    {
        sample:'Sea water',
        content: 'The sea water contains some amount of lead',
        embedding: [0.1, 0.2, 0.3, 0.4] },
    {
        sample:'Tap water',
        content: 'The team lead by Dr. Rose found out that the tap water in was potable',
        embedding:[1.0, 0.1, 0.4, 0.3]
    },
    {
        sample:'Sewage water',
        content: 'High amounts of a were found in Sewage water',
        embedding : [0.4, 0.3, 0.2, 0.1]
    }
];
-- Add embeddings for what lead as a harmful substance should be.
LET $lead_harmful = [0.15, 0.25, 0.35, 0.45];
-- Select the sample and content from the liquids table with cosine similarity
SELECT sample, content, vector::similarity::cosine(embedding, $lead_harmful) AS dist FROM liquidsVector WHERE embedding <|2,COSINE|> $lead_harmful;
```

In the example above you can see that the results are more accurate. The search pulled up only the results in which the word "lead" was used to mean the material, while the final `liquidsVector` record had the lowest score. This is the advantage of using vector search over full-text search.

Another use case for vector search is in the field of facial recognition. For example, if you wanted to search for an actor or actress who looked like you from an extensive dataset of movie artists, you would first use an LLM model to convert the artist's images and details into vector embeddings and then use SurrealQL to find the artist with the most resemblance to your face vector embeddings. The more characteristics you decide to include in your vector embeddings, the higher the dimensionality of your vector will be, potentially improving the accuracy of the matches but also increasing the complexity of the vector search.

## Computation on vectors: "vector::" package of functions

SurrealDB provides [vector functions](/docs/reference/query-language/functions/database-functions/vector.md) for most of the major numerical computations done on vectors. They include functions for element-wise addition, division and even normalisation.

They also include similarity and distance functions, which help in understanding how similar or dissimilar two vectors are.
Usually, the vector with the smallest distance or the largest cosine similarity value (closest to 1) is deemed the most similar to the item you are trying to search for.

<img src="~/assets/img/image/light/distance-metrics.png" darkSrc="~/assets/img/image/dark/distance-metrics.png" alt="Vector functions available in SurrealDB" />

The choice of distance or similarity function depends on the nature of your data and the specific requirements of your application.

In the liquids examples, we assumed that the embeddings represented the harmfulness of lead (as a substance). We used the [`vector::similarity::cosine`](/docs/reference/query-language/functions/database-functions/vector.md#vectorsimilaritycosine) function because cosine similarity is typically preferred when absolute distances are less important, but proportions and direction matter more.

## Filtering through vector search

The [`vector::distance::knn()`](/docs/reference/query-language/functions/database-functions/vector.md#vectordistanceknn) function from SurrealDB returns the distance computed between vectors by the KNN operator. This operator can be used to avoid recomputation of the distance in every `select` query.

Consider a scenario where you’re searching for actors who look like you but they should have won an Oscar. You set a flag, which is true for actors who’ve won the golden trophy.

Let’s create a dataset of actors and define an approximate vector index on the embeddings field. This walkthrough uses **HNSW**; from SurrealDB 3.1 you can instead use **DISKANN** when your vectors no longer fit comfortably in memory. See the page on [vector indexes](/docs/learn/data-models/vector-search/vector-indexes.md) for trade-offs and supported `TYPE` / `DIST` combinations.

```surql
-- Create a dataset of actors with embeddings and flags
CREATE actor:1 SET name = 'Actor 1', embedding = [0.1, 0.2, 0.3, 0.4], flag = true;
CREATE actor:2 SET name = 'Actor 2', embedding = [0.2, 0.1, 0.4, 0.3], flag = false;
CREATE actor:3 SET name = 'Actor 3', embedding = [0.4, 0.3, 0.2, 0.1], flag = true;
CREATE actor:4 SET name = 'Actor 4', embedding = [0.3, 0.4, 0.1, 0.2], flag = true;

-- Define an embedding to represent a face
LET $person_embedding = [0.15, 0.25, 0.35, 0.45];

-- Define an HNSW index on the actor table
DEFINE INDEX hnsw_pts ON actor FIELDS embedding HNSW DIMENSION 4;

-- Select actors who look like you and have won an Oscar
SELECT id, flag, vector::distance::knn() AS distance FROM actor
  WHERE flag = true AND embedding <|2,40|> $person_embedding ORDER BY distance;
```

```surql
[
	[
		{
			distance: 0.09999999999999998f,
			flag: true,
			id: actor:1
		},
		{
			distance: 0.412310562561766f,
			flag: true,
			id: actor:4
		}
	]
];
```

`actor:1` and `actor:4` have the closest resemblance with your query vector among those who have also won an Oscar.

### How the filter is applied

Because `embedding` is indexed, the `flag = true` condition is *not* applied after the nearest neighbours have been gathered. Instead SurrealDB applies it *during* the approximate (HNSW or DISKANN) search, so candidates that fail the condition are discarded as the graph is traversed and never occupy one of the `K` result slots. This keeps the result both correct and efficient:

* Correct, because you still receive up to `K` records that match the condition.
* Efficient, since the search does not spend its slots on records that would only be filtered out afterwards.

SurrealDB applies the condition in one of two ways:

* **Checked on each candidate.** The search reads the record of each candidate it visits and tests the condition. The example above runs this way, because `flag` has no index of its own.
* **Pre-filtered through an index.** When another index gives exactly the records that the condition matches, SurrealDB reads that index first into an allow-list, and the search admits only records on the list without reading them. See [pre-filtering with an index](#pre-filtering-with-an-index).

### Confirming the pushed-down filter with `EXPLAIN`

_(since v3.1.5)_

The [`EXPLAIN`](/docs/reference/query-language/statements/explain.md) clause can be used in the same query to see the query plan. The pushed-down condition appears as a `predicate` attribute on the `KnnScan` operator:

```surql
EXPLAIN SELECT id, flag, vector::distance::knn() AS distance FROM actor
  WHERE flag = true AND embedding <|2,40|> $person_embedding ORDER BY distance;
```

```surql title="Output"
'SelectProject [ctx: Db] [projections: id, flag, distance]
    SortByKey [ctx: Db] [sort_keys: distance ASC]
        Compute [ctx: Db] [fields: distance = vector::distance::knn(...)]
            Filter [ctx: Db] [predicate: flag = true]
                KnnScan [ctx: Db] [index: hnsw_pts, k: 2, ef: 40, dimension: 4, predicate: flag = true]'
```

The `predicate: flag = true` on the `KnnScan` line is the condition being evaluated inside the index search. If the attribute is missing and no bitmap operator appears beneath `KnnScan`, the condition is not applied during the search (for example, when the vector field is not indexed). A DISKANN index produces an identical `KnnScan` line.

### Pre-filtering with an index

_(since v3.3.0)_

A condition is pre-filtered when an index identifies exactly the records it matches. That is the case for:

* An equality or range condition, or an `OR` of them, on a field that has a standard or unique index and a declared type that cannot hold an array, such as `TYPE bool` or `TYPE string`. An array value has one index entry per element, so without that type the index could list records the condition does not match.
* A full-text `@@` condition on a field with a full-text index.

A `NOT` condition, or any other condition, is still checked on each candidate. When a `WHERE` clause mixes the two kinds, the qualifying conditions form the allow-list and the rest are checked on each candidate. A query with a `WITH` clause or a `VERSION` clause is not pre-filtered.

Defining a type and an index for `flag` in the example above is enough for its condition to qualify:

```surql
DEFINE FIELD flag ON actor TYPE bool;
DEFINE INDEX idx_flag ON actor FIELDS flag;

EXPLAIN ANALYZE SELECT id, flag, vector::distance::knn() AS distance FROM actor
  WHERE flag = true AND embedding <|2,40|> $person_embedding ORDER BY distance;
```

```surql title="Sample output"
'SelectProject [ctx: Db] [projections: id, flag, distance] {rows: 2, batches: 1, elapsed: 393.21µs}
    SortByKey [ctx: Db] [sort_keys: distance ASC] {rows: 2, batches: 1, elapsed: 369.46µs}
        Compute [ctx: Db] [fields: distance = vector::distance::knn(...)] {rows: 2, batches: 1, elapsed: 334.87µs}
            Filter [ctx: Db] [predicate: flag = true] {rows: 2, batches: 1, elapsed: 303.08µs}
                KnnScan [ctx: Db] [index: hnsw_pts, k: 2, ef: 40, dimension: 4, prefilter_tier: exact] {rows: 2, batches: 1, elapsed: 276.58µs}
                    BitmapIndexScan [ctx: Db] [index: idx_flag, access: = true] {rows: 3, batches: 0, elapsed: 0ns}

Total rows: 2'
```

The condition now appears as a `BitmapIndexScan` beneath `KnnScan`, which found the three actors with `flag = true`. `KnnScan` carries no `predicate`, because no condition is left to check during the search.

The `prefilter_tier` attribute, reported under `EXPLAIN ANALYZE`, shows how the search used the allow-list. The choice depends on the number of records on it:

| `prefilter_tier` | Records on the allow-list | What the search does |
| --- | --- | --- |
| `exact` | Up to 2,000 | Computes the distance to each allowed record without using the vector index, so the result is the exact nearest `K`. |
| `graph` | Up to 100,000 | Searches the vector index, admitting only allowed records, with the search width `ef` multiplied by 4 to make up for the candidates the list rejects. The boosted width is at most 1,024, and never less than the `ef` in the query. |
| `graph_unboosted` | More than 100,000 | Searches the vector index, admitting only allowed records, with the `ef` in the query. |
| `fallback` | Not built | An index read passed the `SURREAL_BITMAP_BRANCH_BUDGET` limit, so every condition is checked on each candidate, as it is without pre-filtering. |

The thresholds are set with `SURREAL_KNN_PREFILTER_EXACT_THRESHOLD`, `SURREAL_KNN_PREFILTER_EF_BOOST_THRESHOLD`, `SURREAL_KNN_PREFILTER_EF_BOOST` and `SURREAL_KNN_PREFILTER_EF_MAX`, and `SURREAL_KNN_PREFILTER_ENABLED=false` turns pre-filtering off. See [environment variables](/docs/reference/cli/db/environment-variables.md). The allow-list is built with the same bitmaps that [combine indexes](/docs/learn/querying/concepts-and-guides/query-optimisation.md#how-surrealdb-combines-indexes) in other queries.

> [!NOTE]
> Indexes built by SurrealDB 3.2 keep working on 3.3 without a rebuild, but a search is only pre-filtered when both the vector index and the index on the filtered field were defined or rebuilt on 3.3. An optional [`REBUILD INDEX`](/docs/reference/query-language/statements/rebuild.md) brings a 3.2 index into pre-filtered search.

For HNSW and DISKANN configuration and the KNN cheat sheet, see [Vector indexes](/docs/learn/data-models/vector-search/vector-indexes.md).

---

Source: https://surrealdb.com/docs/learn/data-models/vector-search/vector-indexes

# Vector indexes

Choose brute force, HNSW, or DISKANN vector indexes, tune DIMENSION and distance metrics, and use the query cheat sheet with vector::distance::knn().

The simplest way to search through your vector embeddings and data in SurrealDB is the [brute force approach](/docs/reference/query-language/statements/define/indexes.md#brute-force-method).

Brute force search compares a query vector against all vectors in the dataset to find the closest match. As this is a brute-force approach, you do not create an index for this approach.

The brute force approach for finding the nearest neighbour is generally preferred in the following use cases:

- Small datasets / limited query vectors: For applications with small datasets, the overhead of building and maintaining an index might outweigh its benefits. In such cases, the brute force approach is optimal.
- Guaranteed accuracy: Since the brute force method compares the query vector against every vector in the dataset, it guarantees finding the exact nearest vectors based on the chosen distance metric (like Euclidean, Manhattan, etc.).
- Benchmarking models: The brute force approach can be used as a reference to help benchmark the performance of other approximate alternatives like HNSW or DISKANN.

While brute force can give you exact results, it's computationally expensive for large datasets.

In most cases, you do not need a 100% exact match, and you can give it up for faster, high-dimensional searches to find the approximate nearest neighbour to a query vector.

Vector indexes provide this kind of approximate search.

## HNSW and DISKANN

SurrealDB offers two **approximate** graph indexes for k-nearest-neighbour search:

| Index | Best for | Storage |
| --- | --- | --- |
| [HNSW](https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world) ([`DEFINE INDEX … HNSW`](/docs/reference/query-language/statements/define/indexes.md#hnsw-hierarchical-navigable-small-world)) | Low-latency ANN when the graph fits comfortably in memory with headroom for the bounded vector cache | In-memory hot graph + persistence |
| [DISKANN](https://arxiv.org/abs/1907.01668) ([`DEFINE INDEX … DISKANN`](/docs/reference/query-language/statements/define/indexes.md#diskann-disk-based-approximate-nearest-neighbours)) *(SurrealDB 3.1+)* | Very large corpora where RAM cannot hold the full graph - full-precision vectors and graph stay in the key-value store and are paged through a bounded cache | Key-value-backed graph + bounded in-memory cache (not on **WASM** targets) |

Both are [proximity graph](/docs/reference/query-language/statements/define/indexes.md#hnsw-hierarchical-navigable-small-world)-style indexes. Queries use the same [`<|K, …|>` KNN operator shapes](/docs/reference/query-language/language-primitives/operators.md#knn); the optimiser picks the index when distances and types line up.

## Vector search cheat sheet

- **HNSW** - efficient in-memory approximation for high dimensions or large in-RAM datasets.
- **DISKANN** - disk-oriented approximation for embeddings that exceed practical memory for a pure HNSW graph.
- **Brute force** - when you do not define an index, when you want exact nearest neighbours, or when you pass an explicit distance function to the query that does not route to your index.

### HNSW index

| Parameter     | Default   | Options                 | Description   |
| ------------- | --------- | ----------------------- | ------------- |
| DIMENSION     |           |                         | Size of the vector
| DIST          | EUCLIDEAN | EUCLIDEAN, COSINE, MANHATTAN | Distance function
| TYPE          | F32       | F64, F32, I64, I32, I16 | Vector type
| EFC           | 150       |                         | EF construction
| M             | 12        |                         | Max connections per element
| M0            | 24        |                         | Max connections in the lowest layer
| LM            | 0.40242960438184466f |              | Multiplier for level generation. This value is automatically calculated with a value considered as optimal.

Examples:

```surql
-- User statement:
DEFINE INDEX hnsw_idx ON pts FIELDS point HNSW DIMENSION 4;
-- Defaults to:
DEFINE INDEX hnsw_idx ON pts FIELDS point HNSW DIMENSION 4 DIST EUCLIDEAN TYPE F32 EFC 150 M 12 M0 24 LM 0.40242960438184466f;
-- Users are strongly suggested not to set an LM value, as
-- it is computed based on other parameters. Only users
-- completely versed in the field should manually set it
```

For more details, see the [`DEFINE INDEX` statement](/docs/reference/query-language/statements/define/indexes.md#hnsw-hierarchical-navigable-small-world) documentation.

### DISKANN index

_(since v3.1.0)_

| Parameter  | Default   | Options | Description |
| ---------- | --------- | ------- | ----------- |
| DIMENSION  |           |         | Vector dimension |
| DIST       | EUCLIDEAN | EUCLIDEAN, COSINE, INNER_PRODUCT, COSINE_NORMALIZED | Distance (narrower set than HNSW) |
| TYPE       | F32       | F32, F16, I8, U8 | Element encoding (`COSINE_NORMALIZED` requires `F32` or `F16`) |
| DEGREE     | 64        | > 0     | Target maximum graph degree |
| L_BUILD    | 100       | > 0     | Construction search-list size |
| ALPHA      | 1.2       |         | DiskANN pruning parameter |
| HASHED_VECTOR | off    |         | Optional hash-stabilised vector keys |

```surql
DEFINE INDEX diskann_idx
  ON pts FIELDS point DISKANN DIMENSION 4 DIST COSINE TYPE F32;
```

DISKANN keeps its graph and full-precision vectors in the key-value store and answers queries through a bounded in-memory cache, so resident memory is bounded by the cache rather than by the dataset. (DISKANN does not apply product quantisation; per-vector size is governed only by the chosen `TYPE`.) The cache defaults to 256 MiB and is shared across all DISKANN indexes; cap it with [`SURREAL_DISKANN_CACHE_SIZE`](/docs/reference/cli/db/environment-variables.md#cache-config).

See [`DEFINE INDEX` → DISKANN](/docs/reference/query-language/statements/define/indexes.md#diskann-disk-based-approximate-nearest-neighbours) for the full memory model and platform notes (including **no WASM support**).

### Querying

```surql
DEFINE INDEX hnsw_idx ON pts FIELDS point HNSW DIMENSION 4;

LET $vector = [2,3,4];
SELECT
    id,
    vector::distance::knn() as dist  -- distance from $vector
                                     -- knn reuses the value computed during
                                     -- the query, in this case the euclidean
                                     -- distance
FROM pts
WHERE point
    <|2|>  -- return 2, in this case using the distance function defined in the
           -- index: euclidean
    $vector;
```

With a DISKANN index defined on `point`, the same `<|K, EF|>` approximate form applies; the second number bounds the dynamic candidate list for search (see the [KNN operator](/docs/reference/query-language/language-primitives/operators.md#knn)).

| Functions                                 |     |
| ------------------------------------------------ | --- |
| `vector::distance::knn()`                        | reuses the value computed during the query
| `vector::distance::chebyshev(point, $vec)`    |
| `vector::distance::euclidean(point, $vec)`    |
| `vector::distance::hamming(point, $vec)`      |
| `vector::distance::manhattan(point, $vec)`    |
| `vector::distance::minkowski(point, $vec, 3)` | third param is [𝑝](#notes)
| `vector::distance::mahalanobis(point, $vec, $cov)` | third param is the covariance matrix, not an index distance
| `vector::similarity::cosine(point, $vec)`     |
| `vector::similarity::jaccard(point, $vec)`    |
| `vector::similarity::pearson(point, $vec)`    |
| `vector::similarity::spearman(point, $vec)`   |

**WHERE statement**

<table>
    <thead>
        <tr>
            <th scope="col">Query</th>
            <th scope="col">HNSW index</th>
            <th scope="col">DISKANN index</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Query"><code>&lt;|2|></code></td>
            <td scope="row" data-label="HNSW index">uses distance function defined in index</td>
            <td scope="row" data-label="DISKANN index">same when the index distance matches</td>
        </tr>
        <tr>
            <td scope="row" data-label="Query"><code>&lt;|2, EUCLIDEAN|></code></td>
            <td scope="row" data-label="HNSW index">brute force method</td>
            <td scope="row" data-label="DISKANN index">brute force method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Query"><code>&lt;|2, COSINE|></code></td>
            <td scope="row" data-label="HNSW index">brute force method</td>
            <td scope="row" data-label="DISKANN index">brute force method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Query"><code>&lt;|2, MANHATTAN|></code></td>
            <td scope="row" data-label="HNSW index">brute force method</td>
            <td scope="row" data-label="DISKANN index">brute force method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Query"><code>&lt;|2, MINKOWSKI, 3|></code></td>
            <td scope="row" data-label="HNSW index">brute force method (third param is <a href="#notes">𝑝</a>)</td>
            <td scope="row" data-label="DISKANN index">brute force method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Query"><code>&lt;|2, CHEBYSHEV|></code></td>
            <td scope="row" data-label="HNSW index">brute force method</td>
            <td scope="row" data-label="DISKANN index">brute force method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Query"><code>&lt;|2, HAMMING|></code></td>
            <td scope="row" data-label="HNSW index">brute force method</td>
            <td scope="row" data-label="DISKANN index">brute force method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Query"><code>&lt;|2, 10|></code></td>
            <td scope="row" data-label="HNSW index">second param is effort*</td>
            <td scope="row" data-label="DISKANN index">same approximate form - second value bounds the candidate list</td>
        </tr>
    </tbody>
</table>

\* **effort** - for HNSW and DISKANN, the second number in `<|K, N|>` tells the engine how far to search along the graph. Both algorithms are approximate and may miss some vectors.

### Notes

- Verify index utilisation in queries using the [`EXPLAIN FULL` clause](/docs/reference/query-language/statements/select.md#the-explain-clause). E.g: `SELECT id FROM pts WHERE point <|10|> [2,3,4,5] EXPLAIN FULL;`
- 𝑝 values: (more about 𝑝 in [Minkowski distance](https://en.wikipedia.org/wiki/Minkowski_distance))
  - 2<sup>0</sup> = 1 → manhattan/diamond ◇
  - 2<sup>1</sup> = 2 → euclidean/circle ○
  - 2<sup>2</sup> = 4 → squircle ▢
  - 2<sup>∞</sup> = ∞ → square □

---

Source: https://surrealdb.com/docs/learn/extensions

# Extensions

Extend SurrealDB with custom modules and WASM plugins. Done through the Surrealism extension system.

_(since v3.0.0)_

>[!NOTE]
> Surrealism plugins remain an experimental functionality under active development. This guide assumes SurrealDB 3.1.0 or later. Version 3.1.0 made [many additions](https://github.com/surrealdb/surrealdb/pull/7082) to Surrealism's core functionality. We look forward to feedback on the plugin system, either via raising an issue or PR on the [SurrealDB repo](https://github.com/surrealdb/surrealdb), or on our [Discord server](https://discord.gg/surrealdb) - the `#surrealism` channel is a good place to start.

SurrealDB's extension system, known as Surrealism, lets you add custom functionality to the database engine without modifying its core. Extensions are compiled to WebAssembly (WASM) and loaded at runtime, which means they run in a sandboxed environment with predictable performance characteristics.

This section covers:

- [Plugins](/docs/learn/extensions/plugins/overview.md) - an overview of the plugin architecture, how plugins are discovered and loaded, and the types of extensions you can create.
- [Guides](/docs/learn/extensions/guides/creating-custom-modules.md) - step-by-step walkthroughs for creating custom modules, understanding the module architecture, and working with WASM plugins.
- [Attribute reference](/docs/learn/extensions/guides/surrealism-attribute-reference.md) - options for the `#[surrealism]` attribute used to mark functions as Surrealism functions, such as `writeable`, `comment`, `init`, and namespaced exports.

## Guides

- [Using WASM plugins](/docs/learn/extensions/guides/using-wasm-plugins.md) - load and call a compiled plugin

---

Source: https://surrealdb.com/docs/learn/extensions/guides/creating-custom-modules

# Creating custom modules

Step-by-step guide to scaffolding a Surrealism module, exposing functions with attributes, compiling and loading into SurrealDB.

This page walks you through the options available when building a [Surrealism](/docs/learn/extensions/plugins/overview.md) module from scratch.

## Prerequisites

You need a recent [Rust toolchain](https://www.rust-lang.org/tools/install), the `wasm32-wasip2` target, and the SurrealDB CLI (`surreal`) on your `PATH`.

```sh
rustup target add wasm32-wasip2
```

Install SurrealDB from the [installation guide](/docs/running/installation.md) if you have not already.

## Scaffold a module project

The fastest way to start is `surreal db module init`:

```sh
surreal db module init
```

This creates the project scaffold (`Cargo.toml`, `surrealism.toml`, `.cargo/Config.toml` with flags needed for the WASI build, and `src/lib.rs`) ready for Surrealism builds.

```toml title=".cargo/Config.toml flags"
[build]
rustflags = ["--cfg", "tokio_unstable"]
```

To automatically set the name and organisation for a project, you can use the following flags:

```sh
surreal db module init --headless --org surrealdb --name my_surreal_module ./my_surreal_module
```

## Create a Rust project manually

Create a new library crate for your module:

```sh
cargo new --lib my_surreal_module
cd my_surreal_module
```

Configure the library as a dynamic WASM library and add the Surrealism SDK plus any other crates you need to `Cargo.toml`, following versions recommended for your SurrealDB release:

```toml
[lib]
crate-type = ["cdylib"]
```

For a full annotated example, follow the [quick tutorial](/docs/learn/extensions/plugins/quick-tutorial.md).

## Configure `surrealism.toml`

Add a `surrealism.toml` file at the crate root next to `Cargo.toml`.

A minimal example:

```toml
[package]
organisation = "surrealdb"
name = "demo"
version = "1.0.0"

[capabilities]
allow_scripting = true
allow_arbitrary_queries = true
allow_functions = ["fn::test"]
allow_net = ["127.0.0.1:8080"]
```

Optional attached filesystem:

```toml
[attach]
fs = "fs"
```

When `[attach] fs = "fs"` is present, the `fs/` folder in your project is packed into the module archive as a read-only filesystem available to the module at runtime.

## Annotate exported functions

Expose functions to SurrealQL by annotating them with `#[surrealism]`. Only functions you mark this way are included in the module and callable from the database.

```rust
use surrealism::surrealism;

#[surrealism]
fn can_drive(age: i64) -> bool {
    age >= 18
}

#[surrealism(writeable, comment = "Creates a value in module KV")]
fn kv_set_value(key: String, value: String) -> bool {
    // implementation omitted
    true
}
```

For the full attribute reference (`writeable`, `comment`, `init`, and namespaced modules), see [Surrealism attribute reference](/docs/learn/extensions/guides/surrealism-attribute-reference.md).

## Compile with `surreal db module`

From your project directory, compile to a `.surli` artefact with the Surreal CLI:

```sh
surreal db module build --out demo.surli .
```

For faster local iteration, use debug builds. This works in the same way as `cargo build --debug` in skipping optimisation passes to speed up compiling time in exchange for lower performance.

```sh
surreal db module build --debug --out demo.surli .
```

See the [module command reference](/docs/reference/cli/db/commands/module.md) for all flags.

## Load into SurrealDB

Upload the `.surli` file with [`DEFINE BUCKET`](/docs/reference/query-language/statements/define/bucket.md) so SurrealDB can store it, then register exported functions with [`DEFINE MODULE`](/docs/reference/query-language/statements/define/module.md). The exact names and paths must match your bucket and module identifiers.

## Test your functions

Connect with the CLI, [SurrealDB Studio](/docs/explore/studio.md), or an SDK and invoke your functions from SurrealQL. Confirm return values and error handling match what you expect.

If something fails:

- verify that experimental capabilities include `surrealism` (and `files` if using buckets/files);
- verify the module was rebuilt after source changes;
- verify your SurrealDB build supports the Surrealism version your module targets.

For a high-level picture of how these steps fit together, see the [Surrealism overview](/docs/learn/extensions/plugins/overview.md).

---

Source: https://surrealdb.com/docs/learn/extensions/guides/module-architecture

# Module architecture

How Surrealism modules are packaged, sandboxed, and invoked by SurrealDB, including exports metadata, runtime limits, and load strategy.

[Surrealism](/docs/learn/extensions/plugins/overview.md) modules are Rust crates packaged as `.surli` archives. Each archive contains your compiled component plus metadata about exported `#[surrealism]` functions. SurrealDB reads this metadata to map SurrealQL calls to module exports without recompiling the database.

## Component target and sandbox

Surrealism builds target WASI Preview 2 (`wasm32-wasip2`) and run through the WASM component model runtime. Your code does not execute as a native library on the host. The runtime boundary provides sandboxing where memory, execution time, filesystem access, and host capabilities can be constrained by server policy.

## Interaction with the engine

The module does not link directly into SurrealDB’s core. Instead, the engine loads it in the Surrealism runtime, resolves exports, and marshals values between SurrealQL and your Rust functions through the Surrealism ABI. You work with SDK-exposed types and host imports, not arbitrary process memory.

At build time, Surrealism also extracts an exports manifest (names, argument types, return type, `writeable` flag, optional comment) and stores it in the archive. That allows metadata lookups without instantiating the module for every query-planning or introspection operation.

## Runtime execution model

Surrealism uses pooled module controllers so invocations can reuse warm execution contexts instead of rebuilding runtime state for each call.

- **Pool ceiling:** per-module controller pool size is capped by `SURREAL_SURREALISM_MAX_POOL_SIZE`.
- **Timeout mode:** strict timeout behaviour can be configured by module/runtime settings.
- **Resource ceilings:** memory, execution time, and module KV limits can be enforced with Surrealism environment variables.

For a complete list of server-level variables, see [environment variables](/docs/reference/cli/db/environment-variables.md#surrealism-config).

## Module loading strategy

By default, SurrealDB eagerly compiles defined Surrealism modules at startup to reduce first-call latency. If you prefer lazy loading, start the server with:

```bash
surreal db start --lazy-surrealism
```

Or set:

**Bash**

```bash
SURREAL_LAZY_SURREALISM=true surreal db start
```

**PowerShell**

```powershell
$env:SURREAL_LAZY_SURREALISM = "true"
surreal db start
```

## Lifecycle

The end-to-end lifecycle is: **build** your crate with [`surreal db module`](/docs/reference/cli/db/commands/module.md), **upload** the `.surli` archive (typically via [`DEFINE BUCKET`](/docs/reference/query-language/statements/define/bucket.md)), **define** the module ([`DEFINE MODULE`](/docs/reference/query-language/statements/define/module.md)), then **invoke** functions from queries.

Operational teams often automate upload and definition in CI so test and production databases stay aligned. The [overview](/docs/learn/extensions/plugins/overview.md) summarises this flow.

## Security

Treat every module as privileged code that can affect data your database can access. Sandboxing limits host access, but a module can still implement logic that leaks or corrupts data if you expose it to the wrong scopes. Load only WASM you trust, pin versions, and follow least privilege for namespaces, databases, and credentials that call into Surrealism functions.

---

Source: https://surrealdb.com/docs/learn/extensions/guides/surrealism-attribute-reference

# Surrealism attribute reference

Reference for the #[surrealism] attribute options, including writeable functions, comments, init hooks, and namespaced exports.

The `#[surrealism]` attribute controls which Rust items are exported from your module and how SurrealDB should treat them.

## Basic export

Use `#[surrealism]` on a function to expose it to SurrealQL:

```rust
use surrealism::surrealism;

#[surrealism]
fn can_drive(age: i64) -> bool {
    age >= 18
}
```

After packaging and `DEFINE MODULE`, this function is callable through your module path.

## `writeable`

Mark functions that perform writes with `writeable`:

```rust
#[surrealism(writeable)]
fn kv_set_value(key: String, value: String) -> bool {
    // implementation snipped
    true
}
```

This metadata is used by the query planner so read-only and write-capable functions can be scheduled with appropriate transaction modes.

## `comment = "..."`

Attach human-readable metadata to an export:

```rust
#[surrealism(comment = "Parses a decimal string into an integer")]
fn parse_number(input: String) -> Result<i64, String> {
    input.parse::<i64>().map_err(|e| e.to_string())
}
```

The comment appears in module metadata outputs such as:

- `surreal db module info`
- `INFO FOR DB STRUCTURE` module export metadata

## `init`

Register one initialisation hook that runs once after module instantiation:

```rust
#[surrealism(init)]
fn warm_cache() {
    // implementation omitted
}
```

Use this for one-time setup such as preloading attached files or priming in-module caches.

## Namespaced modules

Apply `#[surrealism]` to modules to group exports under namespaces:

```rust
#[surrealism]
mod math {
    use surrealism::surrealism;

    #[surrealism]
    pub fn add(a: i64, b: i64) -> i64 {
        a + b
    }

    #[surrealism]
    pub mod util {
        use surrealism::surrealism;

        #[surrealism]
        pub fn negate(value: i64) -> i64 {
            -value
        }
    }
}
```

Exports are prefixed by namespace, for example:

- `math::add`
- `math::util::negate`

## Related docs

- [Creating custom modules](/docs/learn/extensions/guides/creating-custom-modules.md)
- [Module architecture](/docs/learn/extensions/guides/module-architecture.md)
- [Module command reference](/docs/reference/cli/db/commands/module.md)

---

Source: https://surrealdb.com/docs/learn/extensions/guides/using-wasm-plugins

# Using WASM plugins

How to load a pre-built Surrealism module archive into SurrealDB, register it, inspect exports, and call functions from SurrealQL.

You do not have to author Rust yourself to benefit from Surrealism. Many teams consume a **pre-built `.surli` archive** produced by another team or pipeline. Authoring a module means writing Rust, configuring `surrealism.toml`, and running [`surreal db module`](/docs/reference/cli/db/commands/module.md) yourself. This page focuses on the consumer workflow.

## Storing the module archive

SurrealDB needs access to the `.surli` binary. Use [`DEFINE BUCKET`](/docs/reference/query-language/statements/define/bucket.md) to create or configure bucket storage, then upload the `.surli` file according to your environment’s path and permissions model.

## Registering the module

Once the binary is stored, [`DEFINE MODULE`](/docs/reference/query-language/statements/define/module.md) associates that artefact with a module name and exposes the compiled functions to SurrealQL. The module definition must line up with the exported function names and signatures from the build.

## Inspecting exports before use

If you receive a module from another team, inspect it locally before upload:

```bash
surreal db module info demo.surli
surreal db module sig --fnc can_drive demo.surli
```

These commands let you verify function signatures and metadata (such as writeable/comment annotations) before registration.

## Calling functions from SurrealQL

After the module is defined, call its exported functions by qualifying the function with the registered module path. Consult your module’s documentation for exact names and parameters.

If you upgrade the module archive, repeat upload and module definition steps (or your deployment automation) so the running instance picks up the new build.

## Further reading

- [Surrealism overview](/docs/learn/extensions/plugins/overview.md) - how compilation, buckets, and modules fit together.
- [`DEFINE BUCKET` reference](/docs/reference/query-language/statements/define/bucket.md) - file storage for WASM.
- [`DEFINE MODULE` reference](/docs/reference/query-language/statements/define/module.md) - registering Surrealism functions.

---

Source: https://surrealdb.com/docs/learn/extensions/plugins/further-examples

# Further examples

Further examples of Surrealism attributes, namespaced exports, and CLI commands

More examples of Surrealism in practice can be seen inside the [surrealism repo](https://github.com/surrealdb/surrealdb/tree/main/surrealism).

## Surrealism Rust code demo

[This demo](https://github.com/surrealdb/surrealdb/tree/main/surrealism/demo) contains examples of the `#[surrealism]` attribute in real code.

### Renaming an export

You can rename a function on the SurrealDB side:

```rust
#[surrealism(name = "other")]
fn can_drive_bla(age: i64) -> bool {
    age >= 18
}
```

### Setting a default export

You can make a function the default, allowing calls via the module path alone:

```rust
#[surrealism(default)]
fn def(age: i64) -> bool {
    age >= 18
}
```

### Attaching export comments

```rust
#[surrealism(comment = "Parses a string into a number")]
fn parse_number(input: String) -> Result<i64, String> {
    input.parse::<i64>().map_err(|e| e.to_string())
}
```

## CLI examples

[This file](https://github.com/surrealdb/surrealdb/blob/main/surrealism/test/test.sh) shows ways to work with module archives directly from the CLI.

```bash
# Scaffold a new module project
surreal db module init

# Build quickly while iterating
surreal db module build --debug --out demo.surli .

# See function signature
surreal db module sig --fnc can_drive demo.surli

# Run function with argument 
surreal db module run --fnc can_drive --arg 17 demo.surli
```

---

Source: https://surrealdb.com/docs/learn/extensions/plugins/overview

# Surrealism plugins

Compile Rust into WASM modules callable from SurrealQL.

Surrealism extensions are written in Rust, compiled into `.surli` modules, and loaded into a running SurrealDB instance. This gives you access to the Rust ecosystem while keeping extensions sandboxed within the database.

Surrealism was built in order to allow users to extend SurrealDB in ways that benefit them, without needing to make any changes to the code of SurrealDB itself. Some potential use cases are:

* Adding functions to create fake / mock data for testing.
* Accessing functionality in Rust crates that are too specific to merge into SurrealDB itself, such as [language-specific functionality](https://crates.io/crates/hangeul), [custom case conversions](https://docs.rs/convert_case/latest/convert_case/enum.Case.html), or [quantitative finance](https://docs.rs/crate/RustQuant/latest).
* Anything else you have built in your own code that you would like to access inside SurrealDB.

## How Surrealism works

Surrealism works by using the following steps:

* Use the [`surreal db module init`](/docs/reference/cli/db/commands/module.md) command to begin a new project. This command works in a similar manner to `cargo new` in scaffolding a new project with its own `toml` file (`surrealism.toml`).
* Annotate functions to export with `#[surrealism]`.
* Use the `surreal db module build` command to build the module.
* In SurrealDB, [allow access to module files](/docs/reference/query-language/statements/define/bucket.md) and [define a module](/docs/reference/query-language/statements/define/module.md) to access the functions.

<img src="~/assets/img/surrealdb/extension/surrealism_flow-light.png" darkSrc="~/assets/img/surrealdb/extension/surrealism_flow.png" alt="A flowchart showing the steps involved to turn regular Rust code into a compiled WASM binary that can be accessed from a running SurrealDB instance." />

## What is available in Surrealism

Current Surrealism releases include:

* function-level metadata such as `writeable` and `comment` via `#[surrealism(...)]`;
* one-time module initialisation with `#[surrealism(init)]`;
* namespaced exports with `#[surrealism] mod ...`;
* optional read-only attached filesystem data packaged into the module archive;
* server-side resource controls for execution time, memory, and module KV limits.

The next pages walk through the workflow and APIs:

- [Quick tutorial](/docs/learn/extensions/plugins/quick-tutorial.md)
- [Creating custom modules](/docs/learn/extensions/guides/creating-custom-modules.md)
- [Surrealism attribute reference](/docs/learn/extensions/guides/surrealism-attribute-reference.md)

## More examples

- [Further examples](/docs/learn/extensions/plugins/further-examples.md) - additional plugins to read through

---

Source: https://surrealdb.com/docs/learn/extensions/plugins/quick-tutorial

# Quick tutorial

A quick tutorial showing the steps involved in turning regular Rust code into SurrealDB-accessible WASM functions.

This tutorial turns ordinary Rust code into a SurrealDB-accessible WASM module, from `surreal db module init` through to calling the function from SurrealQL.

## Getting started: regular Rust code

Starting a Surrealism project is best done using the `surreal db module init` command, which is similar to `cargo new` when starting a project in Rust. The `Cargo.toml` file will include the following line to instruct cargo to generate a `.wasm` file instead of the standard `.rlib` file when compiling Rust.

```toml
[lib]
crate-type = [ "cdylib" ]
```

The command will also ask for the project and organisation name, which will lead to the following `surrealism.toml` file.

```toml
target = "rust"
abi = 2

[package]
organisation = "my_organisation"
name = "surrealism_fun"
version = "1.0.0"

[capabilities]
allow_scripting = false
allow_arbitrary_queries = false
allow_functions = []
allow_net = []
strict_timeout = true

[attach]
```

You'll also notice a few sample functions inside `lib.rs`. To internalise how Surrealism works, let's delete them and begin with three of our own functions that we would like to call using SurrealQL.

One is a simple function that returns a bool.

```rust
fn can_drive(age: i64) -> bool {
    age >= 18
}
```

The second returns a `Result`.

```rust
fn parse_number(input: String) -> Result<i64, ParseIntError> {
    input.parse::<i64>()
}
```

And the third returns a `User` struct via a function called `random_user()` which uses the [fake](https://docs.rs/fake/4.4.0/fake/index.html) crate to create a user with a random English first name, French middle name, and German last name. This struct is annotated with the `SurrealValue` trait, allowing its return value to be understood on the SurrealDB side.

```rust
#[derive(Debug, SurrealValue)]
pub struct User {
    first_name: String,
    middle_name: String,
    last_name: String,
	age: i32
}

pub fn random_user() -> User {
    User {
        first_name: FirstName(EN).fake(),
        middle_name: FirstName(FR_FR).fake(),
        last_name: LastName(DE_DE).fake(),
		age: random_range(10..=50)
    }
}
```

Here is all of the code.

```rust
use fake::faker::name::raw::*;
use fake::{Fake, locales::*};
use rand::random_range;
use surrealdb_types::SurrealValue;
use std::num::ParseIntError;

#[derive(Debug, SurrealValue)]
pub struct User {
    first_name: String,
    middle_name: String,
    last_name: String,
	age: i32
}

fn can_drive(age: i64) -> bool {
    age >= 18
}

fn parse_number(input: String) -> Result<i64, ParseIntError> {
    input.parse::<i64>()
}

pub fn random_user() -> User {
    User {
        first_name: FirstName(EN).fake(),
        middle_name: FirstName(FR_FR).fake(),
        last_name: LastName(DE_DE).fake(),
		age: random_range(10..=50)
    }
}
```

## Annotating functions

The functions that we want to call from inside SurrealQL can now be exposed by adding the `#[surrealism]` annotation.

```rust
use fake::faker::name::raw::*;
use fake::{Fake, locales::*};
use rand::random_range;
use surrealdb_types::SurrealValue;
use surrealism::surrealism;
use std::num::ParseIntError;

#[derive(Debug, SurrealValue)]
pub struct User {
    first_name: String,
    middle_name: String,
    last_name: String,
	age: i32
}

#[surrealism]
fn can_drive(age: i64) -> bool {
    age >= 18
}

#[surrealism]
fn parse_number(input: String) -> Result<i64, ParseIntError> {
    input.parse::<i64>()
}

#[surrealism]
pub fn random_user() -> User {
    User {
        first_name: FirstName(EN).fake(),
        middle_name: FirstName(FR_FR).fake(),
        last_name: LastName(DE_DE).fake(),
		age: random_range(10..=50)
    }
}
```

With this done, the Rust code can now be compiled via the [`module build`](/docs/reference/cli/db/commands/module.md) command in the CLI. This is followed with the `--out` flag and the output file name/path (e.g. `demo.surli`). If you are in a separate folder, you can follow this with the path to the Rust code with functions to expose.

```bash
surreal db module build --out demo.surli /Users/my_name/my_rust_code
```

For faster local iteration:

```bash
surreal db module build --debug --out demo.surli /Users/my_name/my_rust_code
```

The file will now be compiled at either your current location or the one specified like in the command above: `/Users/my_name/my_rust_code`.

That's the module archive that SurrealDB can point to!

## Calling the Surrealism file from SurrealDB

The Surrealism file is ready to be used. All that is left now is to start a database with two environment variables. They are:

* `SURREAL_CAPS_ALLOW_EXPERIMENTAL=files,surrealism`, which allows the [files](/docs/reference/query-language/statements/define/bucket.md) and surrealism features to be used in the first place. Files can be used in SurrealDB once a `DEFINE BUCKET` statement has been used to set up the location for the files to be stored.
* `SURREAL_BUCKET_FOLDER_ALLOWLIST="/Users/my_name/my_rust_code/"`, which tells the database that it's okay to access this folder when using the files feature.

Now it's time to [start the database](/docs/reference/cli/db/commands/start.md) with the `surreal db start` command and these two env vars.

**Bash**

```bash
SURREAL_CAPS_ALLOW_EXPERIMENTAL=files,surrealism SURREAL_BUCKET_FOLDER_ALLOWLIST="/Users/my_name/my_rust_code/" surreal db start --user root --pass secret
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "files,surrealism"
$env:SURREAL_BUCKET_FOLDER_ALLOWLIST = "C:\Users\my_name\my_rust_code"
surreal db start --user root --pass secret
```

You can then connect through [SurrealDB Studio](/docs/explore/studio.md) or the CLI with the [surreal db sql](/docs/reference/cli/db/commands/sql.md) command:

**Bash**

```bash
SURREAL_CAPS_ALLOW_EXPERIMENTAL=files,surrealism SURREAL_BUCKET_FOLDER_ALLOWLIST="/Users/my_name/my_rust_code/" surreal db sql --user root --pass secret
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "files,surrealism"
$env:SURREAL_BUCKET_FOLDER_ALLOWLIST = "C:\Users\my_name\my_rust_code"
surreal db sql --user root --pass secret
```

We're almost there! Only two statements left and we can start accessing these functions.

The first is a [DEFINE BUCKET](/docs/reference/query-language/statements/define/bucket.md) statement to create a bucket called `test`, linked to the folder that we have allowed the database to access. The "file:/" prefix in this statement tells the `DEFINE BUCKET` statement that this path is to a file, not storage in memory.

```surql
DEFINE BUCKET test BACKEND "file:/users/my_name/my_rust_code";
```

Now that we have a bucket called `test`, the `demo.surli` file will be available to us at the f"test:/demo.surli" path. The `f` there is an instruction to treat the string as a file path instead of just a regular string.

Then we can [define a module](/docs/reference/query-language/statements/define/module.md) which will hold all the tests. We'll call it `mod::test` and connect it to the file. The keyword that does the connecting changed in 3.3, so pick the tab that matches your version.

**SurrealDB 3.3 and later**

`FROM` replaces `AS`, and `UNSIGNED` is required because only modules published to Silo carry a signature.

```surql
DEFINE MODULE mod::test FROM f"test:/demo.surli" UNSIGNED;
```

**Before SurrealDB 3.3**

`AS` connects the module name to the file.

```surql
DEFINE MODULE mod::test AS f"test:/demo.surli";
```

The functions are now ready to call. The `parse_number()` function is available at the `mod::test::parse_number` path.

```surql
mod::test::parse_number("10");
//- 10
mod::test::parse_number("Hi I'm number");
//- 'Thrown error: WASM function returned error: invalid digit found in string'
```

Next, we can use `random_user()` to create some random users.

```surql
CREATE user CONTENT mod::test::random_user();
//- [{ age: 18, first_name: 'Thomas', id: user:hr8ohmn36zrpv3zthhnf, last_name: 'Meier', middle_name: 'Ninon' }]
CREATE user CONTENT mod::test::random_user();
//- [{ age: 13, first_name: 'Verda', id: user:zg0ucdjizdp71fzq9syc, last_name: 'Schuster', middle_name: 'Clarisse' }]
CREATE user CONTENT mod::test::random_user();
//- [{ age: 45, first_name: 'Zelda', id: user:rulp2bf82twrh94ifhsh, last_name: 'Berger', middle_name: 'Noah' }]
```

That leaves us with one function left to try out, the `can_drive()` function.

```surql
SELECT 
    first_name + ' ' + middle_name + ' ' + last_name AS name, 
    mod::test::can_drive(age) AS can_drive
FROM user;
```

Two of them can drive, but not Verda Clarisse Schuster who is far too young.

```surql
[
    { can_drive: true,  name: 'Thomas Ninon Meier' }, 
    { can_drive: true,  name: 'Zelda Noah Berger' }, 
    { can_drive: false, name: 'Verda Clarisse Schuster' }
]
```

---

Source: https://surrealdb.com/docs/learn/querying

# Querying

SurrealQL, the SDKs and GraphQL: SQL-like syntax for SurrealDB. Graphs, links, and practical querying tips.

This section covers the ways to write and send queries to your SurrealDB database. The main focus is on the SurrealQL query language, which strongly resembles traditional SQL but differs in a number of ways to accommodate patterns such as graph traversal, record links, and other features unique to SurrealDB.

Queries can also be written without using direct SurrealQL through methods such as SDKs in your preferred programming language, [GraphQL](/docs/learn/querying/graphql/overview.md), or [GQL](/docs/learn/querying/gql/overview.md) (ISO graph pattern queries on the `/gql` endpoint). The [Postgres wire protocol](/docs/reference/rest-api/postgres-protocol.md) lets standard Postgres clients (`psql`, JDBC, etc.) connect and run SurrealQL or GQL with tabular results. Built-in [`eval::*`](/docs/reference/query-language/functions/database-functions/eval.md) functions can also run SurrealQL or GQL query strings from inside a transaction when explicitly enabled.

This section also includes tips for writing efficient queries. For more detail, the [reference](/docs/reference/query-language.md) section of the documentation has separate pages for each statement, data type, clause and more for the entire SurrealQL query language.

## Concepts and guides

- [Parameterised queries](/docs/learn/querying/concepts-and-guides/parameterised-queries.md) - pass values as parameters instead of building query strings
- [Working with types](/docs/learn/querying/concepts-and-guides/working-with-types.md) - how values are typed, cast and asserted
- [Custom functions](/docs/learn/querying/concepts-and-guides/custom-functions.md) - name and reuse a query with `DEFINE FUNCTION`
- [Subqueries and advanced patterns](/docs/learn/querying/concepts-and-guides/subqueries-and-advanced-patterns.md) - nest queries and compose results
- [Transactions](/docs/learn/querying/concepts-and-guides/transactions.md) - group statements so they succeed or fail together
- [Idempotent operations](/docs/learn/querying/concepts-and-guides/idempotent-operations.md) - write statements that are safe to retry
- [Error handling](/docs/learn/querying/concepts-and-guides/error-handling.md) - what a failed statement returns, and how to react to it
- [Sequences](/docs/learn/querying/concepts-and-guides/sequences.md) - generate monotonic numbers without a race
- [Sessions and scoping](/docs/learn/querying/concepts-and-guides/sessions-and-scoping.md) - what a session carries, and how long it lasts
- [Query optimisation](/docs/learn/querying/concepts-and-guides/query-optimisation.md) - read a query plan and index for it
- [Bulk operations and data import](/docs/learn/querying/concepts-and-guides/bulk-operations-and-data-import.md) - load many records efficiently
- [Connecting from serverless & edge](/docs/learn/querying/concepts-and-guides/connecting-from-serverless-and-edge.md) - connection patterns where processes are short-lived
- [Representations and codecs](/docs/learn/querying/concepts-and-guides/representations-and-codecs.md) - how values are encoded over each protocol
- [Testing](/docs/learn/querying/concepts-and-guides/testing.md) - assert query results as part of a test suite

## Performance

- [Performance best practices](/docs/learn/querying/performance/performance-best-practices.md) - what to measure, and what usually costs the most

## Custom APIs

- [Middleware](/docs/learn/querying/custom-apis/middleware.md) - run code before and after a custom API handler

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/bulk-operations-and-data-import

# Bulk operations and data import

Bulk INSERT patterns in SurrealQL, plus Surreal Sync, HTTP import, /sql for side effects, and SurrealDB Studio CSV.

A bulk operation can either be one involving many records in one SurrealQL statement, or loading a file or stream from outside the database.

## In-query bulk patterns

An [`INSERT`](/docs/reference/query-language/statements/insert.md) statement is most commonly used for bulk operations. An `INSERT` can even use the output from another query as its input values:

```surql
INSERT INTO archive (SELECT * FROM readings WHERE city = 'London');
```

An `INSERT` can use an array of objects or classic SQL tuple syntax.

```surql
INSERT INTO person [
	{ id: "jaime", name: "Jaime" },
	{ id: "tobie", name: "Tobie" },
];

INSERT INTO company (name, founded)
VALUES
	('Acme Inc.', '1967-05-03'),
	('SurrealDB', '2021-09-10');
```

The `ON DUPLICATE KEY UPDATE` clause can be used to add extra logic when a unique key clashes instead of failing the operation.

```surql
INSERT INTO city (id,
  population,
  at_year) VALUES ("Calgary",
  1665000,
  2024)
ON DUPLICATE KEY UPDATE
	population = $input.population,
	at_year = $input.at_year;
```

## Tools and HTTP endpoints

Importing data from external sources can be done through a number of methods.

- **[Surreal Sync](https://github.com/surrealdb/surreal-sync/)** helps migrate from other databases and streams into SurrealDB. See the [migrations overview](/docs/build/migrating/from-other-databases/overview.md) for how it fits with the other import methods.

- **`POST /import`** is the HTTP endpoint for importing SurrealQL at volume. Imports must include an `OPTION IMPORT` line which instructs the server to skip events, live queries, query output and so on. For details, see [`POST /import`](/docs/reference/rest-api/http-protocol.md#import).

- **`POST /sql`** runs SurrealQL like a normal query session. Use this endpoint when you **want side effects** during a load such as events and live query behaviour. The [HTTP API](/docs/reference/rest-api/http-protocol.md#sql) documents the `/sql` endpoint; the [CLI `import`](/docs/reference/cli/db/commands/import.md) page describes the same trade-off for file-based imports.

- **SurrealDB Studio** can import SurrealQL files and **CSV** from the Explorer view (choose fields, map to a table, and create records). See [exploring database records / import](/docs/explore/studio.md).

For large one-off file loads from the shell, the **[`surreal db import`](/docs/reference/cli/db/commands/import.md)** command is the usual companion to `POST /import`.

## Reading a large result

A query answered with the [`query`](/docs/reference/rest-api/rpc-protocol.md#query) RPC method or `POST /sql` returns each statement's result in one response, so a client that selects a whole large table receives all of it at once. There are two ways to receive it in smaller parts:

- **`query_stream`** (available since version 3.3.0) sends a statement's records in frames of up to 256 while the statement runs, over a WebSocket connection. The Rust and JavaScript SDKs do not use it yet, so a client calls it directly through the [RPC protocol](/docs/reference/rest-api/rpc-protocol.md#query_stream).
- **Pages** with [`LIMIT` and `START`](/docs/reference/query-language/clauses/limit.md#use-in-pagination) split the result into separate queries, which works with every SDK and protocol.

To move a whole database to another server, [`surreal db export`](/docs/reference/cli/db/commands/export.md) and `surreal db import` are usually simpler than either.

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/connecting-from-serverless-and-edge

# Connecting from serverless & edge

Serverless and edge environments change how applications connect to databases.

Instead of long-lived processes with persistent connections, serverless and edge code runs in short-lived, stateless executions, often across multiple regions.

Some examples of environments in which this pattern is used are AWS Lambda, Vercel Functions / Edge Functions, Cloudflare Workers, and Deno Deploy. For Vercel specifically, see [SurrealDB on Vercel](/docs/build/integrations/vercel.md), which covers Next.js, the AI SDK, eve and Vercel Sandbox.

SurrealDB works well in these environments when it is used with the connection patterns on this page.

These patterns are for application code that connects to a SurrealDB server running somewhere else, such as a [SurrealDB Cloud](/docs/running/cloud.md) instance or a self-hosted server. The server itself is designed to run as a separate long-lived service. The [`surrealdb-server`](https://crates.io/crates/surrealdb-server) crate does make its HTTP and RPC router available to a Rust application, but that crate is SurrealDB's internal API and can change between patch releases.

Each function instance also cannot open its own embedded datastore on shared storage. A RocksDB or SurrealKV data directory can be open in only one process at a time, and the second process that opens the same path fails with an error such as `Database at mydb/LOCK is already locked by another process`. Mounting the directory from a network file system such as Amazon EFS does not let several function instances use it at once. To share data between function instances, run SurrealDB as a separate server or use SurrealDB Cloud, and connect to it with the patterns on this page. [Embedding SurrealDB in Rust](/docs/reference/rust/embedding.md) describes the lock in more detail.

## Serverless vs. traditional

The following chart summarises the differences between the two approaches.

| Traditional server | Serverless / edge |
|------------------|------------------|
| Long-lived process | Short-lived execution |
| Persistent connections | No guaranteed reuse |
| Connection pooling | Not available |
| Single region | Often multi-region |
| Warm memory | Frequent cold starts |

As many serverless and edge environments don't support TCP sockets, connecting over HTTP is generally used. As no persistent connection is required, this allows you to create a new client, connect, run queries and then exit.

```javascript
import { Surreal } from 'surrealdb';

const db = new Surreal();

await db.connect('https://your-instance');

await db.signin({
  username: 'your-user',
  password: 'your-pass',
});

await db.use({
  namespace: 'app',
  database: 'prod',
});

const result = await db.query('SELECT * FROM user');
```

## Authentication patterns

Authentication by password uses [secure salted and hashed passwords](/learn/book/chapter-15#a-bit-of-cryptography) that rely on algorithms that are compute-heavy and purposely take a few hundred milliseconds per attempt.

```javascript
// Basic approach: authenticate per request
await db.signin({ username: '...', password: '...' });
```

If you can store pre-generated tokens securely in environment variables or platform secrets, then using a token directly may be preferred.

```javascript
await db.authenticate('your-token');
```

## Batching queries

The flexibility of SurrealQL allows you to batch and optimise queries, performing multiple operations inside a single request instead of multiple requests. Take this example to create and link two records that might be done over three operations, returning the first two results through an SDK to finally perform a `RELATE` operation at the end.

```surql
-- Request 1
CREATE person:one;

-- Request 2
CREATE person:two;

-- Request 3
RELATE person:one->likes->person:two;
```

This can be sent as a single request, and can even be written as a single query that both creates and relates the records in question.

```surql
RELATE
	(CREATE ONLY person:one RETURN VALUE id)
->likes->
	(CREATE ONLY person:two RETURN VALUE id);
```

## When not to use serverless patterns

Serverless and edge connections are not always the best choice.

Consider a long-lived backend service if you need:

* Live queries or subscriptions
* Real-time updates over WebSockets
* High-frequency queries from the same process
* Persistent connection state

In these cases, a traditional backend with a persistent SurrealDB connection will be more efficient.

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/custom-functions

# Custom functions

Learn how to define and call database functions with fn::, optional arguments, and permissions.

[`DEFINE FUNCTION`](/docs/reference/query-language/statements/define/function.md) registers reusable SurrealQL under a name beginning with the `fn::` prefix. You need appropriate access (root / namespace / database owner or editor), and you must [`USE`](/docs/reference/query-language/statements/use.md) the right namespace and database first. For more clauses and edge cases, see the reference documentation.

## Define and call

Custom functions are always named with the `fn::` prefix.

```surql
-- Simple function: build a string from an argument
DEFINE FUNCTION fn::greet($name: string) {
	"Hello, " + $name + "!"
};

RETURN fn::greet("Tobie");
```

```surql
-- More than one parameter; body can use LET, subqueries, RETURN, and so on
DEFINE FUNCTION fn::relation_exists($in: record, $tb: string, $out: record) {
	LET $results = SELECT VALUE id FROM type::table($tb) WHERE in = $in
	  AND out = $out;
	RETURN array::len($results) > 0;
};
```

## Optional trailing arguments

If the last parameters use `option<T>`, callers may omit them.

```surql
DEFINE FUNCTION fn::last_option($required: number, $optional: option<number>) {
	RETURN {
		required_present: type::is_number($required),
		optional_present: type::is_number($optional),
	};
};

RETURN fn::last_option(1, 2);  -- both set
RETURN fn::last_option(1);      -- optional omitted
```

## Annotated return types

Adding `-> type` is recommended for extra type safety, as well as readability. Though the first example can clearly only return a `number` due to its two arguments being of the same type, it is easier for a casual reader to know what sort of value is being returned.

```surql
DEFINE FUNCTION fn::combine($one: number, $two: number) -> number {
	$one + $two
};

-- Accept any input; coercion to the return type happens when returning
DEFINE FUNCTION fn::combine_any($one: any, $two: any) -> number {
	$one + $two
};
```

You can also return [literal union types](/docs/reference/query-language/language-primitives/data-types/literals.md) for structured data or a fallback string instead of [throwing](/docs/reference/query-language/statements/throw.md):

```surql
DEFINE FUNCTION fn::age_and_name($user_num: int) -> { age: int, name: string } | string {
	LET $user = type::record("user", $user_num);
	IF $user.exists() {
		$user.{ name, age }
	} ELSE {
		"Couldn't find user number " + <string>$user_num + "!"
	}
};
```

## Recursion

Though not a commonly used pattern in SurrealQL, a function may call itself. Below, each step relates the first record in a list to all the rest, then recurses on the tail until fewer than two records remain.

```surql
DEFINE FUNCTION fn::relate_all($records: array<record>) {
	IF $records.len() < 2 {
		-- stop recursion
	} ELSE {
		LET $first = $records[0];
		LET $remainder = $records[1..];
		FOR $counterpart IN $remainder {
			RELATE $first->to->$counterpart;
		};
		fn::relate_all($remainder);
	}
};

CREATE |person:1..8|;
fn::relate_all(SELECT VALUE id FROM person);
SELECT id, ->to->? FROM person;
```

## Permissions

`PERMISSIONS` controls whether [record users](/docs/learn/security/authentication/users.md#record-users) may invoke the function: `FULL` (typical default), `NONE`, or `WHERE` with a boolean expression (often involving `$auth`).

```surql
-- PERMISSIONS NONE: record users cannot call this (admins / root still can, depending on setup)
DEFINE FUNCTION fn::fetchAllPaymentDetails() -> array {
	SELECT stored_cards.expiry_year FROM payment_details LIMIT 5
} PERMISSIONS NONE;
```

```surql
-- PERMISSIONS WHERE: only when the expression is true (here, admin flag on $auth)
DEFINE FUNCTION fn::fetchAllProducts() -> array {
	SELECT * FROM product LIMIT 10
} PERMISSIONS WHERE $auth.admin = true;
```

## Creating or replacing a definition

Clauses like `IF NOT EXISTS` and `OVERWRITE` can be used when defining a function.

```surql
-- IF NOT EXISTS: only define when no function with this name exists yet (no-op if it does)
DEFINE FUNCTION IF NOT EXISTS fn::example() {};

-- OVERWRITE: replace an existing definition with this one
DEFINE FUNCTION OVERWRITE fn::example() {};
```

## Functions as API middleware

Handlers for [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) middleware are ordinary `fn::` functions with a specific signature. See [custom middleware](/docs/reference/query-language/statements/define/api.md#custom-middleware) and the learn page on [middleware](/docs/learn/querying/custom-apis/middleware.md) for more details.

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/error-handling

# Error handling

Handle errors in SurrealQL with THROW, validation guards and ASSERT on fields to stop queries and return clear failures.

Error handling in SurrealQL allows you to stop execution and return a meaningful error when something goes wrong. This is most commonly done using the `THROW` statement.

## Throwing an error

You can use `THROW` to immediately stop a query and return an error to the client:

```surql
THROW "Something went wrong";
```

When executed, the query is aborted and the error is returned.

`THROW` is often used to validate input or guard business logic:

```surql
IF !$email {
    THROW "Email is required";
};

IF $amount <= 0 {
    THROW "Transfer amount must be greater than zero";
};
```

`THROW` is especially useful inside manual transactions.

```surql
BEGIN TRANSACTION;

UPDATE account:one SET balance -= 150;

IF account:one.balance < 0 {
    THROW "Insufficient funds";
};

COMMIT TRANSACTION;
```

## Returning custom error messages

The value passed to THROW is returned to the client.

```surql
THROW "Invalid username or password";
```

You can also include dynamic data:

```surql
THROW "User not found: " + <string>$username;
```

Or even structured data.

```surql
THROW {
    code: 400,
    message: "Invalid request"
};
```

## Avoiding errors

Type safety and strict definitions can be used to avoid throwing errors based on custom logic. For example, the `ASSERT` clause in a [DEFINE FIELD](/docs/reference/query-language/statements/define/field.md) statement can be used to ensure that a statement will fail if the string for the `email` field provided is not a valid email.

```surql
DEFINE FIELD email ON TABLE user TYPE string
  ASSERT string::is_email($value);
```

Working with a schema in this way allows throwing errors to be taken care of by the definitions themselves as opposed to writing custom logic.

## Assertions while debugging

When you are iterating on a query in the CLI or SurrealDB Studio, it is not always obvious which step in a long [method chain](/docs/reference/query-language/functions/database-functions/#method-syntax) produced an unexpected value. The [`value::expect()`](/docs/reference/query-language/functions/database-functions/value.md#valueexpect) function, available from SurrealDB 3.1, checks a condition on the current value and returns that same value when the closure is `true`, or fails the statement with a clear error (and an optional custom message).

```surql
CREATE person:one SET name = "Tommy", city = "London";
CREATE person:two SET name = "Billy", city = "London";

LET $records = SELECT * FROM person WHERE city = "London";

$records
    .expect(|$n| $n.len() > 0, "Expected at least one person in London")
    .map(|$person| { name: $person.name + " from London" });
```

```surql title="Output"
[
    { name: 'Tommy from London' }, 
    { name: 'Billy from London' }
];
```

This can be used for temporary invariants while debugging. For permanent rules, prefer `DEFINE FIELD … ASSERT` on the schema. `.expect()` clones the value it receives, so remove it from hot paths once you are finished debugging.

## SDK error handling

SurrealDB uses a [single public API error type](https://github.com/surrealdb/surrealdb/blob/main/surrealdb/types/src/error.rs#L37) that is shared by SDKs. As the repo states, the error type is:

> Designed to be returned from public APIs (including over the wire). It is wire-friendly and non-lossy: serialisation preserves `kind`, `message`, and optional `details`. Use this type whenever an error crosses an API boundary (e.g. server response, SDK method return).
>
> The `details` field is flattened into the serialised object, so the wire format contains `kind` (string) and optionally `details` (object) at the same level as `code` and `message`. The optional `cause` field allows error chaining so that SDKs can receive and display full error chains.

This error will then be handled in a different manner depending on the programming language the SDK is written for.

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/idempotent-operations

# Idempotent operations

Make SurrealQL idempotent with UPSERT, safe deletes and unique indexes so retries do not corrupt data.

Idempotency is a key concept when building reliable applications, especially APIs, event-driven systems, and distributed architectures.

In simple terms, an operation is **idempotent** if running it multiple times produces the same result as running it once.

Fun fact: *idempotent* comes from the Latin word *idem* which means "same" (while "potent" means to have power). Most readers have seen this word in its abbreviated form at the bottom of academic journals:

* United States v. Martinez-Fuerte, 428 U.S. 543, 545 (1976).
* *Id.* at 547.

## Why idempotency matters

In real-world systems, requests can be retried, messages can be delivered more than once, and network failures can interrupt operations.

However, if your database queries are idempotent then you can safely retry them without corrupting data.

## Idempotent patterns in SurrealQL

### 1. Using `UPSERT`

`UPSERT` is the most common way to ensure idempotency.

```surql
UPSERT user:123 SET name = "Alice", age = 30;
```

The above query will create the record if it does not exist, and update it if it does exist. Running it multiple times will result in the same final state, at least for the fields `name` and `age`.

### 2. Deleting records

A `DELETE` statement will remove the record(s) on first execution, and executing the same statement again will have no effect. A `DELETE` statement returns an empty array by default.

```surql
DELETE user:123;
```

### 3. Creating relations (`RELATE`)

The following statement is not idempotent on its own, as multiple relations can be created between records.

```surql
RELATE user:123->likes->post:456;
```

However, it can be made idempotent by first defining a [unique index](/docs/reference/query-language/statements/define/indexes.md#composite-index) on the `in` and `out` fields. Doing so will result in the `RELATE` statement above always resulting in a single graph edge between the two records.

```surql
DEFINE INDEX only_one ON likes FIELDS in, out UNIQUE;
```

Alternatively, from version 3.1.5, you can supply an explicit edge record ID in the `RELATE` path (for example `->likes:[person:one, post:one]->`) or in an [`INSERT RELATION`](/docs/reference/query-language/statements/insert.md#insert-relation-tables) statement.

A duplicate explicit ID on `INSERT RELATION` returns an error unless you add `ON DUPLICATE KEY UPDATE`. For more, see [Explicit edge record IDs](/docs/reference/query-language/statements/relate.md#handling-duplicate-edge-record-ids).

### 4. Getting a record, or creating it if it does not exist

When a record is reached through a relation rather than a known ID, such as the account that belongs to a phone number, `UPSERT` cannot find it by ID. A [defined function](/docs/reference/query-language/statements/define/function.md) can return the existing record, or create it and its relation if there is none. A function body runs as one transaction, so the new record and the relation are written together or not at all:

```surql
-- One account per phone number
DEFINE INDEX one_account_per_mobile ON identity_has FIELDS out UNIQUE;

DEFINE FUNCTION fn::account_for_mobile($mobile: record<mobile>) -> record<account> {
    LET $existing = $mobile<-identity_has<-account;
    IF $existing {
        RETURN $existing[0];
    };
    UPSERT $mobile;
    LET $account = CREATE ONLY account RETURN VALUE id;
    RELATE $account->identity_has->$mobile;
    RETURN $account;
};

-- Both calls return the same account
fn::account_for_mobile(mobile:176823181);
fn::account_for_mobile(mobile:176823181);
```

The `UNIQUE` index covers the case where two calls for the same number run at the same time. Both find no account, but only the first `RELATE` succeeds. The second fails on the index, and its transaction rolls back, including the account it created, so the caller can retry and receive the existing account.

## Non-idempotent examples

The following examples will produce different results on every execution, and thus are not idempotent.

```sql
UPDATE user:123 SET login_count += 1;
UPDATE user:123 SET tags += "new";
```

## Using idempotency to simplify queries

Instead of writing application logic like this:

```text
if user exists:
  update
else:
  create
```

You can simplify it with a single idempotent query.

```surql
UPSERT user:123 SET name = "Alice", status = "active";
```

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/parameterised-queries

# Parameterised queries

Queries can make use of parameters which hold stored values, even those from the output of a previous query.

Parameters are stored values which can then be used in subsequent queries. To define a parameter in SurrealQL, use the [`LET`](/docs/reference/query-language/statements/let.md) statement. Parameter names must begin with a `$` (dollar sign) character.

## Defining parameters within SurrealQL

```surql
-- Define the parameter
LET $suffix = "Morgan Hitchcock";
-- Use the parameter
CREATE person SET name = "Tobie " + $suffix;
//- [{ id: person:3vs17lb9eso9m7gd8mml, name: 'Tobie Morgan Hitchcock' }]

-- (Another way to do the same)
CREATE person SET name = string::join(" ", "Jaime", $suffix);
//- [{ id: person:xh4zbns5mgmywe6bo1pi, name: 'Jaime Morgan Hitchcock' }]
```

A parameter can store any value, including the result of a query.

```surql
-- Assuming the CREATE statements from the previous example
LET $founders = SELECT * FROM person;
$founders.{
    name,
    company
};
```

```surql title="Output"
[
	{
		company: 'SurrealDB',
		name: 'Jaime Morgan Hitchcock'
	},
	{
		company: 'SurrealDB',
		name: 'Tobie Morgan Hitchcock'
	}
]
```

## Scope of parameters

Parameters persist across the current connection, and thus can be reused between different namespaces and databases. In the example below, a created `person` record assigned to a parameter is reused in a query in a completely different namespace and database.

```surql
LET $billy = CREATE ONLY person:billy SET name = "Billy";
-- Fails as `person:billy` already exists
CREATE person CONTENT $billy;

USE NAMESPACE other_namespace;
USE DATABASE other_database;
-- Succeeds as `person:billy` does not yet
-- exist in this namespace and database
CREATE person CONTENT $billy;
```

Parameters can be defined using SurrealQL as shown above, or can be passed in using the client libraries as request variables.

## Redefining and shadowing parameters

Parameters in SurrealQL are immutable. The same parameter can be redefined using a `LET` statement.

```surql
LET $my_name = "Alucard";
LET $my_name = "Sypha";
RETURN $my_name;
```

```surql title="Output"
'Sypha'
```

## Defining parameters within client libraries

SurrealDB's client libraries allow parameters to be passed in as JSON values, which are then converted to SurrealDB data types when the query is run. The following example shows a variable being used within a SurrealQL query from the JavaScript library.

```javascript
let people = await surreal.query("SELECT * FROM article WHERE status INSIDE $status", {
	status: ["live", "draft"],
});
```

The JavaScript SDK can also take a variable written directly into the query text. With the `surql` template tag, each `${...}` value is sent as a parameter, not joined into the string, so the query is safe from injection. [Bound queries](/docs/reference/javascript/concepts/bound-queries.md) has more examples.

```javascript
import { surql } from "surrealdb";

const status = ["live", "draft"];
let people = await surreal.query(surql`SELECT * FROM article WHERE status INSIDE ${status}`);
```

## Reserved variable names

SurrealDB automatically predefines certain variables depending on the type of operation being performed. For example, `$this` and `$parent` are automatically predefined for subqueries so that the fields of one can be compared to another if necessary. In addition, the predefined variables `$access`, `$auth`, `$token`, and `$session` are protected variables used to give access to parts of the current database configuration and can never be overwritten.

```surql
LET $access = true;
LET $auth = 10;
LET $token = "Mytoken";
LET $session = rand::int(0, 100);
```

```surql title="Output"
-------- Query 1 --------

"'access' is a protected variable and cannot be set"

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

"'auth' is a protected variable and cannot be set"

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

"'token' is a protected variable and cannot be set"

-------- Query 4 --------

"'session' is a protected variable and cannot be set"
```

For a complete list of reserved parameter names, see [this section](/docs/reference/query-language/language-primitives/parameters.md#reserved-variable-names) in the API documentation.

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/query-optimisation

# Query optimisation

Practical ideas for faster SurrealQL: EXPLAIN, record ranges, async events, denormalised flags, and indexes.

This page contains a number of tips you can use to optimise your queries in SurrealDB. For more details on each pattern, see the linked pages in the API documentation.

## Inspecting the plan with `EXPLAIN`

Prefix a read-only statement with `EXPLAIN` to see how the database plans to run it. Add `ANALYZE` if you want timing and row metrics as well. As the output of this statement is informational and may change between versions, be sure not to build tooling that depends on an exact shape.

```surql
EXPLAIN SELECT * FROM person WHERE email = 'user@example.com';
```

Full syntax and options can be found in the [`EXPLAIN` reference](/docs/reference/query-language/statements/explain.md).

## Record ranges

When you can identify records by record ID order (for example numeric or time-ordered IDs), selecting with a range on the ID (`table:start..end`) avoids scanning the whole table. A `WHERE` filter over the same records can be much more expensive because it typically implies a wider scan.

```surql
SELECT * FROM person:1..1000;
```

See [record IDs](/docs/reference/query-language/language-primitives/data-types/record-ids.md) and [record ranges in `SELECT`](/docs/reference/query-language/statements/select.md#record-ranges).

## Async events

By default, events run in the same transaction as the write that triggers them, which keeps behaviour easy to reason about but can slow commits if event logic is heavy.

Using the [`ASYNC` clause in a `DEFINE EVENT` statement](/docs/reference/query-language/statements/define/event.md#async-events) runs the handler **after** the triggering transaction. This leads to lower write latency, with the caveat that the handler opts out of the ACID guarantees that apply by default to all transactions. As such, it should only be used when this tradeoff is acceptable.

More context: [Reactive patterns](/docs/learn/schema-management/events-and-triggers/reactive-patterns.md).

## Pre-allocated fields (denormalised flags)

If a query repeatedly does a lookup or subquery only to answer a yes/no question (“is this user registered?”), consider storing the answer in a field. For example, an `is_registered` field updated when the user completes registration is more efficiently written ahead of time as a boolean value as opposed to using an extra `SELECT` inside another query.

This will still need a strategy to keep the flag up to date, but allows you to avoid paying the check cost on every read.

## Indexes and iterators

- Define indexes that match real filter and sort patterns; see [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md).
- The [`WITH`](/docs/reference/query-language/clauses/with.md) clause can force or restrict which index the planner uses when you need predictable behaviour (for example comparing plans with `EXPLAIN`).

## How SurrealDB combines indexes

_(since v3.3.0)_

When a `WHERE` clause holds several conditions that indexes can answer, SurrealDB can use all of those indexes together rather than choosing one and checking the other conditions record by record. Each record in a table has a numeric document ID that every index on the table shares. Reading an index for one condition gives a bitmap of the IDs that match it, and bitmaps combine with set operations that read no records. Only the records left after the last operation are fetched, and the full `WHERE` clause is checked again on each of them, as are the table and field permissions.

The conditions combine as follows:

- `AND` intersects the bitmaps, starting from the index with the most selective condition.
- `OR`, at the top level or inside an `AND`, can become a union when every branch has an index, so a record that matches several branches is fetched once.
- `AND NOT` subtracts one bitmap from another. The subtracted condition must be a full-text `@@` match, or be on a field whose declared type cannot hold an array, such as `TYPE string` or `TYPE bool`. An array value has one index entry per element, so its bitmap would remove records the condition does not match.
- A single-hop graph condition such as `->wrote->post CONTAINS post:one` becomes a bitmap of the records connected to that record, and is intersected with the index bitmaps. It needs at least one index-backed condition beside it.
- A `count()` with `GROUP ALL` is the size of the final bitmap when every condition is represented exactly: an indexed field with a declared type that cannot hold an array, or a full-text `@@` match. The count then reads no records.

```surql
DEFINE FIELD category ON article TYPE string;
DEFINE FIELD published ON article TYPE bool;
DEFINE INDEX idx_category ON article FIELDS category;
DEFINE INDEX idx_published ON article FIELDS published;

EXPLAIN SELECT count() FROM article WHERE category = 'databases' AND published = true GROUP ALL;
```

```surql title="Output"
"IndexCountScan [ctx: Db] [source: article, condition: category = 'databases' AND published = true]
    BitmapAnd [ctx: Db]
        BitmapIndexScan [ctx: Db] [index: idx_category, access: = 'databases']
        BitmapIndexScan [ctx: Db] [index: idx_published, access: = true]
"
```

The planner keeps its other plans where they are faster: when an index already returns the records in the `ORDER BY` order, including `ORDER BY id`, when a `LIMIT` has no `ORDER BY` so that the scan can stop early, and when the statement has a `WITH` clause or a `VERSION` clause.

Index reads are bounded by `SURREAL_BITMAP_BRANCH_BUDGET`, 250,000 entries by default, where `0` removes the limit. In an `AND`, the first index is always read in full. A later branch that reads more than the limit, or more than 64 times the number of records left so far, is dropped, and its condition is checked on each fetched record instead. In a top-level `OR`, a branch that passes the limit makes the query fall back to reading the indexes separately. The limit keeps a condition that matches most of the table from making the combined plan slower than the plan it replaced. See [environment variables](/docs/reference/cli/db/environment-variables.md).

The same bitmaps also [pre-filter vector searches](/docs/learn/data-models/vector-search/similarity-search.md#pre-filtering-with-an-index). The plan operators are listed under [bitmap index fusion](/docs/reference/query-language/clauses/explain.md#bitmap-index-fusion) in the `EXPLAIN` reference.

> [!NOTE]
> An index built by SurrealDB 3.2 keeps answering queries on 3.3 without a rebuild, but it does not record the document IDs that these plans read, so it takes no part in them. An optional [`REBUILD INDEX`](/docs/reference/query-language/statements/rebuild.md) brings it into these plans. An index defined on 3.3 takes part from the start.

## Fast table counts

For `SELECT count() … GROUP ALL` over a whole table, a `COUNT` index maintains a running total instead of scanning every record each time. See the note under [`SELECT` - `COUNT` index](/docs/reference/query-language/statements/select.md#using-a-count-index-to-speed-up-count-in-group-all-queries).

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/representations-and-codecs

# Representations and codecs

Choose the right built-in when moving data between strings, bytes, tokens, patches, and executable query text.

_(since v3.2.0)_

Many SurrealDB built-ins do the same broad job: take a value in **representation A** and produce a value in **representation B**. As these tend to be found over various parts of the documentation, this page is a map to introduce them in a single location and help you pick the right one.

## Quick overview

| I want to… | Use | Reference |
| --- | --- | --- |
| Serialise a value to JSON or CBOR (or Base64 bytes) | `encoding::json::*`, `encoding::cbor::*`, `encoding::base64::*` | [Encoding functions](/docs/reference/query-language/functions/database-functions/encoding.md) |
| Parse JSON/CBOR bytes back into SurrealQL values | `encoding::json::decode`, `encoding::cbor::decode` | [Encoding functions](/docs/reference/query-language/functions/database-functions/encoding.md) |
| Preview how an analyzer tokenizes text | `search::analyze` | [Search functions](/docs/reference/query-language/functions/database-functions/search.md#searchanalyze) |
| Extract part of an email or URL string | `parse::email::*`, `parse::url::*` | [Parse functions](/docs/reference/query-language/functions/database-functions/parse.md) |
| Diff or patch a value with JSON Patch | `value::diff`, `value::patch` | [Value functions](/docs/reference/query-language/functions/database-functions/value.md) |
| Coerce or inspect types | `type::*`, casts (`<datetime>`, `<bytes>`, …) | [Type functions](/docs/reference/query-language/functions/database-functions/type.md) |
| **Run a query string at runtime** | `eval::surql`, `eval::gql` | [Eval functions](/docs/reference/query-language/functions/database-functions/eval.md) |

## Serialisation (`encoding::*`)

**Reversible codecs** for wire formats and storage:

- [`encoding::json::encode`](/docs/reference/query-language/functions/database-functions/encoding.md#encodingjsonencode) / [`decode`](/docs/reference/query-language/functions/database-functions/encoding.md#encodingjsondecode) - JSON text
- [`encoding::cbor::encode`](/docs/reference/query-language/functions/database-functions/encoding.md#encodingcborencode) / [`decode`](/docs/reference/query-language/functions/database-functions/encoding.md#encodingcbordecode) - [CBOR](/docs/reference/rest-api/cbor-protocol.md) bytes
- [`encoding::base64::encode`](/docs/reference/query-language/functions/database-functions/encoding.md#encodingbase64encode) / [`decode`](/docs/reference/query-language/functions/database-functions/encoding.md#encodingbase64decode) - Base64 text for binary payloads

Typical uses: [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) request bodies, file bucket payloads, and SDK interchange. Each pair is designed for a round trip: decoding an encoded value gives back the original.

```surql
LET $payload = { event: 'signup', user: 'tobie' };

-- Value → JSON text → value again
encoding::json::decode(encoding::json::encode($payload));
```

```surql title="Output"
{ event: 'signup', user: 'tobie' }
```

Related one-way or format-specific helpers elsewhere include `string::html::encode`, `geo::hash::encode`, and [`crypto::*`](/docs/reference/query-language/functions/database-functions/crypto.md) hashes.

## Text analysis (`search::analyze`)

[`search::analyze`](/docs/reference/query-language/functions/database-functions/search.md#searchanalyze) runs a named [`DEFINE ANALYZER`](/docs/reference/query-language/statements/define/analyzer.md) pipeline on a string and returns an array of tokens. It is **lossy** (stemming, filtering) and mirrors what full-text indexing does - useful for debugging analyzers before you index.

Here the "format" is whatever pipeline you defined on the analyzer.

```surql
DEFINE ANALYZER demo_blank TOKENIZERS blank;

search::analyze("demo_blank", "SurrealDB graph queries");
```

```surql title="Output"
['SurrealDB', 'graph', 'queries']
```

Compare the tokens above with what you get after adding `FILTERS lowercase, snowball(english)` - the same input string produces a different token list, which is why `search::analyze` is handy when tuning an analyzer before you create a [`FULLTEXT`](/docs/reference/query-language/statements/define/indexes.md) index.

## Structured parsing (`parse::*`)

[`parse::url::*`](/docs/reference/query-language/functions/database-functions/parse.md) and [`parse::email::*`](/docs/reference/query-language/functions/database-functions/parse.md) extract one component from a structured string. There is no round-trip - you get a field value, not a reassembled URL.

```surql
{
	domain: parse::url::domain("https://surrealdb.com/docs"),
	user: parse::email::user("tobie@surrealdb.com"),
};
```

```surql title="Output"
{ domain: 'surrealdb.com', user: 'tobie' }
```

## In-value transforms (`value::*`)

[`value::diff`](/docs/reference/query-language/functions/database-functions/value.md#valuediff) and [`value::patch`](/docs/reference/query-language/functions/database-functions/value.md#valuepatch) move between a SurrealQL value and JSON Patch operations. They pair naturally with [changefeeds](/docs/learn/querying/real-time/changefeeds.md) and [`LIVE SELECT DIFF`](/docs/learn/querying/real-time/live-queries.md).

```surql
LET $before = { title: 'Weekly update', status: 'draft' };
LET $after = { title: 'Weekly update', status: 'published' };
LET $patch = value::diff($before, $after);

value::patch($before, $patch);
```

```surql title="Output"
{ title: 'Weekly update', status: 'published' }
```

`value::diff` produced the patch; `value::patch` applied it. The same pair works when you receive patch operations from a client or a live diff stream.

## Dynamic evaluation (`eval::*`)

_(since v3.2.0)_

[`eval::surql`](/docs/reference/query-language/functions/database-functions/eval.md#evalsurql) and [`eval::gql`](/docs/reference/query-language/functions/database-functions/eval.md#evalgql) parse and **execute** query text in the caller's transaction. That is fundamentally different from encoding:

- Input is **executable** SurrealQL or [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md), not a static wire format.
- **Denied by default** - requires [`allow-eval-query`](/docs/learn/security/authorization/capabilities.md#eval-queries) and the [arbitrary-query](/docs/learn/security/authorization/capabilities.md#arbitrary-queries) gate.
- Nested writes affect the caller's transaction; transaction-control statements are rejected.

Use `eval::*` when the query string is only known at runtime. Prefer [`DEFINE FUNCTION`](/docs/reference/query-language/statements/define/function.md), [`DEFINE API`](/docs/reference/query-language/statements/define/api.md), or normal client queries when the shape of the work is fixed at deploy time.

```surql
-- Query text from a table, config row, or user input (requires allow-eval-query)
LET $template = "$greeting + ', ' + $name";
eval::surql($template, { greeting: 'Hello', name: 'world' });
```

```surql title="Output"
'Hello, world'
```

For [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md) strings, use `eval::gql` instead - same bindings object, plus [`--allow-eval-query`](/docs/learn/security/authorization/capabilities.md#eval-queries). See [Eval functions](/docs/reference/query-language/functions/database-functions/eval.md) for setup and examples.

## See also

- [Database functions catalogue](/docs/reference/query-language/functions/database-functions.md)
- [Parameterised queries](/docs/learn/querying/concepts-and-guides/parameterised-queries.md) - `$parameters` at the SurrealQL layer (contrast with `eval` bindings)
- [Capabilities](/docs/learn/security/authorization/capabilities.md)

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/sequences

# Sequences

SurrealDB sequences for monotonic numeric identifiers, and how they differ from default randomly generated record IDs.

Sometimes you need a monotonic number: an order line, a ticket counter, or an audit sequence that always moves forward and never hands the same value to two different writers. SurrealDB sequences are shared, durable generators that provide this on a single node or in a cluster, without needing to construct the logic in application code.

## When a sequence helps

Use a sequence when:

* You care about strictly increasing integers (or a predictable step) rather than opaque identifiers.
* Multiple clients or nodes may allocate values at the same time, and collisions would be painful.
* You are happy for the value to be numeric and database-owned, not derived from your domain model alone.

If you only need a unique id and ordering is secondary, other patterns (record IDs, ULIDs, hashes) may be simpler. Sequences are the better choice when order and uniqueness are both part of the contract.

## Sequences vs. manual incrementing numbers

Auto-incrementing numbers are quick to put together inside a SurrealQL query.

```surql
DEFINE FUNCTION fn::get_next() -> number {
    UPSERT ONLY the:number SET val += 1 RETURN VALUE val    
};

fn::get_next(); -- 1
fn::get_next(); -- 2
fn::get_next(); -- 3
fn::get_next(); -- 4
```

This produces a number that will always increase by 1, and is rolled back during a failed transaction. As such, if `fn::get_next()` returns the value 4, you can be certain that values 1, 2, and 3 also exist.

This works well on one server, but it gets awkward in distributed setups: everyone contends on the same record, and you pay for coordination on every allocation.

SurrealDB sequences use a batch idea instead: each node reserves a range of values, hands them out locally, and only talks to shared storage when the range runs out. In practice this reduces coordination traffic under load and as you scale out.

You declare a sequence with a name, then ask for the next value when you need it. Here is the shape of the workflow:

```surql
DEFINE SEQUENCE order_line;

-- Later, whenever you need the next number:
sequence::nextval('order_line');
```

Note that a sequence is never rolled back. Each number is guaranteed to be unique and a greater value than any of the ones before, but any sequences used in a failed transaction will simply not be used. This means that a sequence that returns the number 4 is only guaranteed to be the greatest number thus far, but not that the numbers 1, 2, or 3 have been used in any successful transactions.

In other words, ordinary data changes can roll back with a transaction but sequence advances do not.

The following sketch shows the behaviour in which the sequence moves forward even when a transaction aborts, but a counter field does not.

```surql
DEFINE SEQUENCE seq;
CREATE my:counter SET val = 0;

sequence::nextval('seq');  -- first value
my:counter.val;            -- still 0 until we update it

BEGIN TRANSACTION;
sequence::nextval('seq');           -- second value (consumed)
UPDATE my:counter SET val += 1;    -- counter becomes 1
CANCEL TRANSACTION;                -- counter rolls back to 0

sequence::nextval('seq');           -- third value: sequence did not roll back
UPDATE my:counter SET val += 1;    -- counter is now 1
```

Design with that rule in mind: sequences are for identity and ordering, not for values that must stay in lock step unless you accept gaps.

## Optional configurations: batch size, start, and timeout

You can tune how big each reserved batch is, where counting starts, and how long the database should wait when acquiring a new batch. Larger batches mean fewer coordination trips; smaller batches mean less “wasted” headroom if a node stops. A timeout that is too tight can make allocation fail under pressure.

## A separate counter for each group

A number that restarts for each group, such as a reference number for each article category, needs one counter for each value of the category field. A record ID can hold the category, so `counter:['news']` and `counter:['sport']` are two separate counters, and `UPSERT` creates each one the first time it is used. Putting the counter in a field's `DEFAULT` numbers every new record, including records from a bulk `INSERT`.

```surql
DEFINE FIELD reference ON article TYPE int READONLY
    DEFAULT (UPSERT ONLY counter:[$this.category] SET val += 1 RETURN VALUE val);

CREATE article SET category = 'news';
CREATE article SET category = 'news';
CREATE article SET category = 'sport';

SELECT category, reference FROM article ORDER BY category, reference;
```

```surql title="Output"
[
	{ category: 'news', reference: 1 },
	{ category: 'news', reference: 2 },
	{ category: 'sport', reference: 1 }
]
```

To use sequences instead, define one sequence for each category and build its name from the field, as in `DEFAULT sequence::nextval('article_' + $this.category)` with `DEFINE SEQUENCE article_news START 1`. A record whose category has no sequence fails with `The sequence 'article_weather' does not exist`, so define the sequence before the first record in a new category.

## Where to read more

* [`DEFINE SEQUENCE`](/docs/reference/query-language/statements/define/sequence.md) - full statement reference.
* [Sequence functions](/docs/reference/query-language/functions/database-functions/sequence.md) - `sequence::nextval` and related usage.
* [Transactions](/docs/learn/querying/concepts-and-guides/transactions.md) - how commits and rollbacks interact with ordinary writes.

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/sessions-and-scoping

# Sessions and scoping

A session stores information about the current connection.

A session holds the state a connection carries between queries: the active namespace and database, the authenticated user, and any metadata set on it. This page covers what that context contains and how it is read and changed.

## What is session context?

A session stores information about the current connection, including:

- The active namespace
- The active database
- Session metadata

This context is automatically used by all queries.

## Switching context with `USE`

You can change the current namespace and database using the `USE` statement:

```surql
USE NS my_namespace DB my_database;
```

After running this, all queries will operate within that context.

The name `main` is used as the default name for the current namespace and database [when starting SurrealDB](/docs/reference/cli/db/commands/sql.md#default-namespace-and-database).

## Parameters

As parameters are set on the connection level, setting a parameter name to a value is one way to persist a value across different namespaces and databases.

In the following example, `person:one` is allowed to be created inside a different namespace and database as that record ID does not yet exist inside `other_ns/other_db`. The `$person` parameter is what enabled the `person:one` value to be reused.

```surql
LET $person = CREATE ONLY person:one;
USE NS other_ns DB other_db;
CREATE $person;
```

Beyond this, the only way to persist values beyond the current session is to use [an SDK](/docs/languages/javascript.md) or [an extension](/docs/learn/extensions/plugins/overview.md).

## Accessing session information

You can access session details using [`session::*`](/docs/reference/query-language/functions/database-functions/session.md) functions.

```surql
-- Current namespace
session::ns();
-- Current database
session::db();
-- Session ID
session::id();
```

## Using session context in queries

You can use session information inside queries:

```surql
IF session::db() != "production" {
    THROW "This query must run in the production database";
};
-- Debugging session state
{
    namespace: session::ns(),
    database: session::db(),
    session: session::id()
};
```

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/subqueries-and-advanced-patterns

# Subqueries and advanced patterns

Nested SELECTs, CREATE as a value, $parent and $this, latest record per group, and graph paths that behave like inner queries.

A query that is nested inside another one is called a subquery. Subqueries are executed first, enabling their output to be used inside a larger query.

## Subquery examples

### `SELECT` inside `SELECT`

A `SELECT` can be used to populate the value of a field, which can be given an alias via the `AS` keyword.

```surql
SELECT 
	*,
	SELECT * FROM events WHERE type = 'activity' LIMIT 5 AS history
FROM user;
```

To refer to part of the outer query, the preset `$parent` parameter can be used.

```surql
SELECT 
	*,
	SELECT * FROM events WHERE host == $parent.id AS hosted_events
FROM user;
```

### Other statements inside a larger query

Other statements such as `CREATE`, `UPDATE` and so on can be used as subqueries as well. This can be useful to combine multiple queries into one or keep statements that should either succeed or fail together inside a single transaction.

```surql
CREATE ONLY person:billy SET
    father = CREATE ONLY person:pete RETURN VALUE id,
    mother = CREATE ONLY person:brenda RETURN VALUE id;
```

```surql title="Output"
{
	father: person:pete,
	id: person:billy,
	mother: person:brenda
}
```

Patterns such as appending a new comment id to a record are covered in [record references](/docs/reference/query-language/language-primitives/record-references.md). See also [`RETURN`](/docs/reference/query-language/statements/return.md).

### Assigning one record from a subquery

A `SELECT` subquery returns an array, even when one record matches, so it cannot be assigned directly to a field of type `record<…>`. Two forms return a single record ID instead: `SELECT VALUE id ...` followed by `[0]`, or `SELECT VALUE id FROM ONLY ... LIMIT 1`.

```surql
DEFINE FIELD category ON post TYPE record<category>;
CREATE category:tech SET name = 'Tech';

-- The subquery returns an array, so this fails
CREATE post:1 SET category = (SELECT VALUE id FROM category WHERE name = 'Tech');
//- "Couldn't coerce value for field `category` of `post:1`: Expected `record<category>` but found `[category:tech]`"

CREATE post:2 SET category = (SELECT VALUE id FROM category WHERE name = 'Tech')[0];
//- [{ category: category:tech, id: post:2 }]

CREATE post:3 SET category = (SELECT VALUE id FROM ONLY category WHERE name = 'Tech' LIMIT 1);
//- [{ category: category:tech, id: post:3 }]
```

`VALUE` matters as well: without it, the subquery returns objects such as `{ id: category:tech }` rather than record IDs. When no record matches, both forms give `NONE`, which a `record<category>` field refuses. Use `option<record<category>>` for a field that may be left empty.

## `$parent` and `$this`

In nested contexts, SurrealDB predefines:

- **`$this`** - the current record in the **inner** scope.
- **`$parent`** - the current record in the **enclosing** scope.

They let an inner `SELECT` relate its `WHERE` clause to the record being processed outside.

```surql
SELECT
    name,
    SELECT VALUE name FROM user
      WHERE member_of = $parent.member_of AS group_members
FROM user
WHERE name = "User1";
```

```surql
SELECT
    *,
    SELECT VALUE id FROM person
      WHERE $this.name = $parent.name AS people_with_same_name
FROM person;
```

Full detail: [Reserved variables - `$parent`, `$this`](/docs/reference/query-language/language-primitives/parameters.md#parent-this).

## Latest record per group

A common pattern is to return the **most recently modified record** for each distinct value of a field - the equivalent of a `ROW_NUMBER() OVER (PARTITION BY … ORDER BY …)` window in SQL. In SurrealQL you can combine [`GROUP BY`](/docs/reference/query-language/clauses/group.md), [`.map()`](/docs/reference/query-language/functions/database-functions/array.md#arraymap), and a nested [`SELECT`](/docs/reference/query-language/statements/select.md):

```surql
CREATE person:1 SET role = "user", modified_at = d'1970-01-01';
CREATE person:2 SET role = "admin", modified_at = d'1990-01-01';
CREATE person:3 SET role = "user", modified_at = d'1999-01-01';
CREATE person:4 SET role = "admin", modified_at = d'2999-01-01';

(SELECT id, role FROM person GROUP BY role).map(|$o| {
    SELECT * FROM ONLY $o.id ORDER BY modified_at DESC LIMIT 1
});
```

```surql title="Output"
[
	{
		id: person:4,
		modified_at: d'2999-01-01T00:00:00Z',
		role: 'admin'
	},
	{
		id: person:3,
		modified_at: d'1999-01-01T00:00:00Z',
		role: 'user'
	}
]
```

How it works:

1. **`GROUP BY role`** collapses records into one result per role. Non-aggregated fields such as `id` become arrays of the grouped record ids.
2. **`.map(|$o| { … })`** runs the nested query once per grouped record.
3. **`SELECT * FROM ONLY $o.id ORDER BY modified_at DESC LIMIT 1`** fetches all of the records in that group, orders by `modified_at`, and returns the latest one.

To partition by more than one field, include every non-aggregated field in both the projection and the `GROUP BY` clause:

```surql
(SELECT id, role, status FROM person GROUP BY role, status).map(|$o| {
    SELECT * FROM ONLY $o.id ORDER BY modified_at DESC LIMIT 1
});
```

See also the [`GROUP` clause](/docs/reference/query-language/clauses/group.md#latest-record-per-group) reference for a shorter summary.

## Counts for search filters (facets)

A search page often shows, next to its results, how many of those results have each value of a field: so many products of each brand, so many with each tag. Store the search results in a parameter, and then count them with one `GROUP BY` query for each field. An object holds all of the counts, so one request returns them together:

```surql
INSERT INTO product [
	{ name: 'Trail shoe', brand: 'Alpine', price: 90, tags: ['outdoor', 'running'] },
	{ name: 'Road shoe', brand: 'Swift', price: 120, tags: ['running'] },
	{ name: 'Hiking boot', brand: 'Alpine', price: 150, tags: ['outdoor'] },
	{ name: 'Sandal', brand: 'Swift', price: 40, tags: ['summer'] }
];

LET $results = SELECT * FROM product WHERE price < 130;

RETURN {
	brand: (SELECT brand, count() FROM $results GROUP BY brand),
	tag: (SELECT tag, count() FROM array::flatten($results.tags).map(|$t| { tag: $t }) GROUP BY tag)
};
```

```surql title="Output"
{
	brand: [
		{ brand: 'Alpine', count: 1 },
		{ brand: 'Swift', count: 2 }
	],
	tag: [
		{ count: 1, tag: 'outdoor' },
		{ count: 2, tag: 'running' },
		{ count: 1, tag: 'summer' }
	]
}
```

`GROUP BY` on a field that holds an array groups records by the whole array, so `['outdoor', 'running']` would be one group. To count each tag on its own, the query collects the tags of every result into one array with [`array::flatten()`](/docs/reference/query-language/functions/database-functions/array.md#arrayflatten), and turns each tag into an object that `GROUP BY` can group.

The `WHERE` clause of `$results` can be any search, including a [full-text](/docs/reference/query-language/statements/define/indexes.md#full-text-search-fulltext-index) match, and the counts follow whatever it returns.

## Grouping an array of objects by a field

`GROUP BY` also works on an array, such as a field that holds objects, because `SELECT` can read `FROM` any SurrealQL value. Each group comes back as an object of its own:

```surql
LET $values = [
	{ name: 'green', variant: variant:colour },
	{ name: 'S', variant: variant:size },
	{ name: 'blue', variant: variant:colour }
];

SELECT variant, name AS names FROM $values GROUP BY variant;
```

```surql title="Output"
[
	{ names: ['green', 'blue'], variant: variant:colour },
	{ names: ['S'], variant: variant:size }
]
```

To get one object with a key for each group instead, build the entries and pass them to [`object::from_entries()`](/docs/reference/query-language/functions/database-functions/object.md#objectfrom_entries). A record ID used as a key becomes a string:

```surql
object::from_entries(
	($values.variant).distinct().map(|$k| [$k, $values.filter(|$v| $v.variant = $k).name])
);
```

```surql title="Output"
{
	"variant:colour": ['green', 'blue'],
	"variant:size": ['S']
}
```

The parentheses around `$values.variant` matter, because without them, `.distinct()` runs on each variant separately rather than on the array of all of them. See [Flattening](/docs/reference/query-language/language-primitives/idioms.md#flattening) for this rule.

## Graph paths and inner queries

Graph traversal (`->edge->table`) can include a **parenthesised inner query** on the edge or node, similar to filtering or projecting in a subquery. This allows you to restrict or shape the edges before the traversal continues.

```surql
SELECT ->(SELECT like_strength FROM likes
  WHERE like_strength > 10) AS likes FROM person;
```

Shorthand filters are often written without a nested `SELECT`, but the nested form is useful when you need full `SELECT` power (`ORDER BY`, `GROUP BY`, and so on):

```surql
SELECT ->(likes WHERE like_strength > 10) AS likes FROM person;
```

More examples: [Selecting inside graph queries](/docs/reference/query-language/statements/select.md#selecting-inside-graph-queries) and [graph clauses](/docs/reference/query-language/statements/relate.md#graph-clauses) on the `RELATE` page.

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/testing

# Testing

This page details a number of ways that SurrealDB can be tested.

This page covers the ways a SurrealDB database can be tested, starting with the SDKs, which is where most testing happens.

## SDKs

Database testing by users is most frequently done using an SDK, particularly for the languages in which SurrealDB can be used [in embedded mode](/docs/build/embedding.md). This allows testing to be conducted in just a few lines of code.

```rust
use surrealdb::{
    engine::any::connect,
    types::{ToSql, Value},
};

#[tokio::test]
async fn test_db() {
    let db = connect("memory").await.unwrap();
    db.use_ns("main").use_db("main").await.unwrap();

    let created = db
        .query("CREATE ONLY person:one RETURN VALUE id")
        .await
        .unwrap()
        .take::<Value>(0)
        .unwrap()
        .to_sql();
    assert_eq!(created, "person:one");
}
```

For a larger example of a test suite made by a SurrealDB user, see [this repo](https://github.com/kotolex/surrealist/tree/644a97f5d899f3ad7e011cb82d11ce7d1d482ebc/tests/unit_tests) which tests hundreds of assertions using the Python SDK.

## Testing via direct SurrealQL queries

Testing can also be done through direct SurrealQL queries to the database. While the CLI and SurrealDB Studio are the most convenient tools to use, sending in queries via the [HTTP endpoints](/docs/reference/rest-api/http-protocol.md) may be more convenient when needing to test output depending on certain environments such as queries as a record user vs. as a database-level system user or a root user.

While you are exploring behaviour in SurrealQL, [`.expect()`](/docs/reference/query-language/functions/database-functions/value.md#valueexpect) (from SurrealDB 3.1) on intermediate results or at the end of a method chain makes it obvious which step failed before you wrap the same logic in an SDK test or a transaction with `THROW`.

### Using manual transactions for testing

As failed transactions automatically roll back any changes made, a transaction with a final `THROW` statement can be used as a confirmation that no errors have taken place inside a group of queries.

Take the following example that creates a unique index and then inserts some records to make sure that the database logic is functioning as expected. However, as names are not necessarily unique, the index soon gives an error and cancels the transaction before `THROW` can be reached.

```surql
BEGIN TRANSACTION;
DEFINE INDEX unique_name ON TABLE person FIELDS name UNIQUE;

INSERT INTO person [
    { name: 'Agatha Christie', born: d'1890-09-15' },
    { name: 'Billy Billerson', born: d'1979-09-11' },
	-- Pretend there are is 10,000 more objects here
    { name: 'Agatha Christie', born: d'1955-05-15' },
];

THROW "Reached the end";
COMMIT TRANSACTION;
```

The output is not the expected 'An error occurred: Reached the end' message, showing that not all queries were successful.

```surql title="Output"
"Database index `unique_name` already contains
'Agatha Christie', with record `person:qs4bpvl96sf9x40b3567`"
```

If the index is redefined to be less strict, the statements will work and the expected output will be reached, confirming that no errors occurred during the test.

```surql
BEGIN TRANSACTION;
DEFINE INDEX OVERWRITE unique_person ON TABLE person FIELDS name, born UNIQUE;

INSERT INTO person [
    { name: 'Agatha Christie', born: d'1890-09-15' },
    { name: 'Billy Billerson', born: d'1979-09-11' },
    { name: 'Agatha Christie', born: d'1955-05-15' },
];

THROW "Reached the end";
COMMIT TRANSACTION;
```

```surql title="Expected output"
'An error occurred: Reached the end'
```

## Validating `.surql` files

One or more `.surql` files can be validated by the CLI using the [`surreal db validate`](/docs/reference/cli/db/commands/validate.md) command.

## SurrealDB internal testing

Each PR submitted to the SurrealDB repo runs a series of [language tests](https://github.com/surrealdb/surrealdb/tree/main/language-tests) that carries out thousands of assertions. As an internal tool, the language test suite is and will continue to be developed with only the needs of the SurrealDB developer team in mind. It is in no way meant to be a tool for community use.

That said, the code for the suite is publicly available and SurrealDB users sometimes gravitate towards it due to the convenience of being able to run tests over multiple `.surql` files in a variety of situations without needing to recompile any code. If you are considering doing the same, be sure to note that these tests are unit tests with output that may differ slightly from that received as a response from a running database instance.

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/transactions

# Transactions

How SurrealDB transactions work: the default per-statement scope, manual BEGIN, COMMIT and CANCEL, and the snapshot isolation guarantees that apply under concurrency.

SurrealDB is ACID-compliant, so the work inside a transaction either commits as a whole or rolls back with no partial effect. By default **each SurrealQL statement runs in its own transaction**, including side effects such as [defined events](/docs/reference/query-language/statements/define/event.md). A single `CREATE`, `UPDATE` or `SELECT` is therefore atomic on its own.

This page covers manual transactions, which group statements that must succeed or fail together, and the **isolation** guarantees that apply when many clients read and write at the same time.

## Implicit transactions

`BEGIN` is not the only thing that groups statements. Blocks and function bodies each run as one transaction, despite the lack of `BEGIN` or `COMMIT`, which is why they are described as implicit. Each behaves like any other transaction: it commits when it finishes, and rolls back if anything inside it fails.

Implicit transactions commit on normal completion, including an early `RETURN`. An early return is control flow rather than a failure, so the work already done is kept:

```surql
{
    CREATE shipment:shp_5501 SET order = order:ord_8412, carrier = 'DHL';
	-- the caller only wants the id
    RETURN shipment:shp_5501;                            
	-- never runs
    UPDATE order:ord_8412 SET status = 'shipped';        
};
```

Implicit transactions also roll back on errors, whether that is a [`THROW`](/docs/reference/query-language/statements/throw.md) or a statement that simply fails, such as a schema violation. Nothing inside survives:

```surql
{
    CREATE order:ord_8412 SET item = product:keyboard, quantity = 2;   -- rolled back
    THROW "Insufficient stock";
    UPDATE product:keyboard SET stock -= 2;                            -- never runs
};
```

Without the braces the order would persist while the stock it reserved never moved, which is the inconsistency a transaction exists to prevent.

In a bare sequence of statements, each one is its own transaction. Here the `THROW` reports an error, and both records still exist afterwards, because the statements either side of it committed independently:

```surql
CREATE product:keyboard SET stock = 40;   -- commits
THROW "Stop the import";                  -- errors, and stops nothing
CREATE product:mouse SET stock = 25;      -- commits
```

Both products exist afterwards. A bare `THROW` reports an error without halting the statements around it, so it cannot be used as a guard clause outside a block.

So wrapping statements in braces is enough to make them atomic.

A block does not, however, give a failure somewhere to be contained. There is one transaction per query rather than a stack of them, so a block inside a manual transaction is part of that transaction rather than a nested one. A `THROW` inside the block aborts the whole thing, including the statements that ran before it:

```surql
BEGIN;
UPDATE account:one SET balance -= 100;    -- rolled back as well
{
    CREATE ledger_entry:le_9001 SET amount = 100, account = account:one;
    THROW "Ledger rejected the entry";
};
UPDATE account:two SET balance += 100;    -- never runs
COMMIT;                                   -- fails: the transaction was already aborted
```

## Manual transactions

Three statements control a manual transaction:

```surql title="Transaction statements"
BEGIN [ TRANSACTION ];
COMMIT [ TRANSACTION ];
CANCEL [ TRANSACTION ];
```

[`BEGIN`](/docs/reference/query-language/statements/begin.md) opens the transaction. [`COMMIT`](/docs/reference/query-language/statements/commit.md) makes every change inside it a permanent part of the database. [`CANCEL`](/docs/reference/query-language/statements/cancel.md) rolls those changes back instead. If any statement inside the transaction fails, the whole transaction is rolled back and no change survives.

```surql title="A transfer between two accounts"
-- Create two accounts for bank customers
CREATE account:one SET balance = 135605.16;
CREATE account:two SET balance = 91031.31;

-- Start a manual database transaction
BEGIN TRANSACTION;

-- Update the balances of each customer involved in the wire transfer
UPDATE account:one SET balance += 300.00;
UPDATE account:two SET balance -= 300.00;

-- Apply both updates together. Had either statement failed, the database
-- would remain in its initial state.
COMMIT TRANSACTION;
```

Replacing `COMMIT TRANSACTION` with `CANCEL TRANSACTION` in the example above leaves both balances untouched.

Client SDKs expose the same model through transaction handles. See the transactions guide for your language under [SDKs](/docs/languages/javascript.md).

## THROW to conditionally cancel a transaction

A transaction rolls back on its own when a statement errors. [`THROW`](/docs/reference/query-language/statements/throw.md) breaks out of one deliberately, at any point. `THROW` can be followed by any value, which serves as the error message, and is usually a string.

```surql
CREATE account:one SET dollars =  100;
CREATE account:two SET dollars =  100;

LET $transfer_amount = 150;

BEGIN TRANSACTION;

UPDATE account:one SET dollars -= $transfer_amount;
UPDATE account:two SET dollars += $transfer_amount;
IF account:one.dollars < 0 {
    THROW "Insufficient funds, would have $" + <string>account:one.dollars + " after transfer"
};
COMMIT TRANSACTION;
SELECT * FROM account;
```

```surql title="Output when $transfer_amount set to 150"
'An error occurred: Insufficient funds, would have $-50 after transfer'
```

```surql title="Output when $transfer_amount set to 50"
[
	{
		dollars: 50,
		id: account:one
	},
	{
		dollars: 150,
		id: account:two
	}
]
```

## Snapshot isolation

Every SurrealDB transaction runs under **snapshot isolation**. When a transaction starts, it sees a consistent point-in-time view of the database. That snapshot stays stable for the lifetime of the transaction, so reads inside the transaction do not observe concurrent writes from other transactions until commit.

SurrealDB does not offer weaker isolation levels. You cannot downgrade to read committed or read uncommitted.

On commit, the engine checks for **write conflicts**. If two concurrent transactions modified the same key, the later commit fails with a transaction conflict error and must be retried. There is no silent last-writer-wins merge at the storage layer.

Records that a transaction only reads are outside that check by default. The [`FOR UPDATE`](#locked-reads-with-for-update) clause brings them into it.

These semantics apply across deployment models and storage backends: embedded and server, single-node and distributed, RocksDB, SurrealKV, SurrealMX, and browser IndexedDB. The [query layer](/docs/learn/data-models/architecture.md#query-layer) enforces the same isolation contract regardless of which engine persists the keys underneath.

### What you get in practice

Snapshot isolation with conflict detection on commit protects against the anomalies most application developers plan for:

| Anomaly | Protected? | Notes |
| --- | --- | --- |
| Dirty reads | Yes | A transaction never reads uncommitted data from another transaction. |
| Non-repeatable reads | Yes | Re-reading the same data inside a transaction returns the same values. |
| Lost updates (same key) | Yes | Concurrent writes to the same key cannot both commit; one transaction must retry. |
| Write skew | Opt in | Covered per record with [`FOR UPDATE`](#locked-reads-with-for-update); see [write skew](#write-skew) below. |

In database terms, snapshot isolation sits between read committed and serialisable. It matches the default isolation level in MySQL (InnoDB `REPEATABLE READ`) and PostgreSQL's optional `REPEATABLE READ` level, with the important caveat that SurrealDB does not provide serialisable isolation. For the specific records a transaction names, [`FOR UPDATE`](#locked-reads-with-for-update) closes the remaining gap.

### Compared with PostgreSQL `REPEATABLE READ`

PostgreSQL's `REPEATABLE READ` is snapshot isolation too, so the two protect against the same anomalies. The mechanisms differ in ways that decide where an application handles a conflict.

| | PostgreSQL `REPEATABLE READ` | SurrealDB |
| --- | --- | --- |
| Snapshot taken | At the first statement in the transaction | At the start of the transaction |
| Competing writer | Blocks on a row lock until the first transaction resolves | Never blocks; proceeds and commits |
| Conflict surfaces | At the conflicting statement, as `could not serialize access due to concurrent update` | At `COMMIT`, as a transaction conflict error |
| `SELECT ... FOR UPDATE` | Takes a row lock, and raises straight away if the row moved since the snapshot | Registers the record, and raises at `COMMIT` if it moved |
| Write skew | Needs explicit locking, or the `SERIALIZABLE` level | Needs [`FOR UPDATE`](#locked-reads-with-for-update) |
| Blanket serialisable level | `SERIALIZABLE` covers predicate-based anomalies as well | No equivalent; `FOR UPDATE` covers named records only |

Two consequences follow for application code. Conflicts arrive later in SurrealDB: a `FOR UPDATE` read never fails on account of a concurrent write, so retry logic wraps the whole transaction rather than guarding individual statements. And because a predicate cannot be registered, an invariant that depends on a set of records rather than on named ones has no direct equivalent of `SERIALIZABLE` to fall back on. Such an invariant needs a record that stands in for the set, such as a counter or a parent record that every participant reads for update.

## Write conflicts and retries

When a commit fails because another transaction wrote the same key first, SurrealDB returns a **transaction conflict** error. Your application, or your client's retry logic, should run the transaction again.

This is normal under concurrent load, not a sign of data corruption. At scale, rising conflict rates show up in metrics such as `surrealdb_transaction_conflicts_total` - see [Observability](/docs/manage/observability/metrics.md) for monitoring guidance.

Keep transactions short and touch the fewest keys necessary. Long-running transactions that overlap on hot keys see more conflicts.

## Unique indexes under concurrent writes

A `UNIQUE` index is the way to make an insert happen exactly once, for example so that two overlapping workers in a job queue cannot both enqueue the same notification. The index makes this safe because each index entry is a key, so two transactions that write the same indexed value write the same key, and the write conflict check on commit applies to it.

When two concurrent transactions insert records with the same value for a `UNIQUE` index, neither waits for the other. Each checks the index against its own snapshot, in which the other's uncommitted record does not exist, so both statements succeed. The first transaction to commit wins. The second fails at `COMMIT` with a transaction conflict error that says the transaction can be retried, and none of its writes are kept. When the losing transaction runs again, its snapshot now holds the committed record, and the insert fails straight away with the unique index error:

```surql
DEFINE INDEX unique_key ON notification FIELDS key UNIQUE;

-- Run by two workers at the same time; one commit fails with a
-- transaction conflict, and its retry fails with the index error
BEGIN;
CREATE notification SET key = 'invoice-2041-sent';
COMMIT;
```

```text title="Error output when the retry runs"
Database index `unique_key` already contains 'invoice-2041-sent', with record `notification:...`
```

`INSERT IGNORE` and `INSERT ... ON DUPLICATE KEY UPDATE` are safe in the same way. They never produce a duplicate under concurrency, but they decide against the transaction's snapshot. A concurrent insert of the same record id or indexed value that commits first makes the other transaction fail with the retryable conflict error, rather than skipping its record or updating the existing one. On retry, the statement sees the committed record and skips or updates it as usual.

An existence check written into the query gives no such guarantee. Without a unique index, both of the following transactions see no matching task in their snapshots, so both create one and both commit:

```surql
DEFINE TABLE task;

BEGIN;
IF !(SELECT * FROM task WHERE key = 'nightly-report') {
    CREATE task SET key = 'nightly-report';
};
COMMIT;
```

This is the same rule as in PostgreSQL, where `INSERT ... WHERE NOT EXISTS (...)` is not safe on its own and a unique index is the fix. The difference is in how the conflict arrives: PostgreSQL makes the second inserter wait for the first and then raises the conflict, while SurrealDB lets both proceed and raises it at `COMMIT`. Retry logic therefore wraps the whole transaction.

A unique index that applies to only some records, such as PostgreSQL's partial unique index, is covered under [conditional uniqueness](/docs/reference/query-language/statements/define/indexes.md#conditional-uniqueness).

## Write skew

Snapshot isolation does not prevent **write skew**. Write skew occurs when two transactions each read overlapping state, make independent decisions, and both commit even though their combined effect breaks an invariant.

An everyday example is one in which two doctors each check a schedule, see that only one shift is booked, and both book themselves. Each transaction read a consistent snapshot, but together they overbooked the day.

Where write skew matters for your workload, read the records the rule depends on with [`SELECT ... FOR UPDATE`](#locked-reads-with-for-update). Each doctor's transaction then registers the `schedule` record it based its decision on, so whichever transaction commits second fails and retries against the booking the first one made.

Writing every record the rule depends on has the same effect, because a write is conflict-checked already. That remains the natural choice where the transaction was going to update the record anyway, and conditional updates work on the same principle.

## Locked reads with `FOR UPDATE`

_(since v3.3.0)_

The [`FOR UPDATE`](/docs/reference/query-language/statements/select.md#the-for-update-clause) clause on `SELECT` enrols a record that the transaction only reads into the same commit-time conflict check that already covers writes. The transaction commits only if no other transaction wrote that record after the snapshot was taken. Otherwise `COMMIT` fails with a transaction conflict error and the transaction can be retried.

```surql
BEGIN;

-- Register the schedule for commit-time conflict detection
LET $schedule = SELECT * FROM ONLY schedule:monday FOR UPDATE;

-- The decision below rests on a value that is read but never written,
-- which is exactly the case FOR UPDATE covers
IF $schedule.shifts_booked = 0 {
    UPDATE booking SET doctor = $auth.id, day = "monday";
};

COMMIT;
```

Three further properties apply beyond the basic guarantee:

- **Concurrent writers are not blocked.** The clause takes no lock. Another transaction can write a registered record and commit normally, and the cost lands on the reading transaction, whose own `COMMIT` then fails and must be retried. Readers coming from a database where `SELECT ... FOR UPDATE` holds a row lock should plan for retries rather than for waiting.
- **An absent record is still covered.** Reading a record id that does not exist registers that id, so a concurrent transaction creating it also invalidates the commit. This is what makes `FOR UPDATE` safe for get-or-create paths.
- **The enclosing statement becomes write-classified.** A `FOR UPDATE` read nested inside an expression promotes its statement to a write transaction, because the registration can only be validated at commit time.

| Aspect | Behaviour |
| --- | --- |
| Targets | Record ids only, written literally or passed through a parameter. Tables, record ranges, subqueries and parameters holding a table are rejected. |
| Concurrency model | Optimistic. No lock is taken and no transaction waits; a conflict surfaces at `COMMIT` on the transaction that read the record. |
| Granularity | One record at a time. Tables, ranges and query predicates are not locked, so a concurrent transaction can still create a new record that matches a predicate. |
| Transaction type | Requires a transaction that commits. A read-only context rejects the clause, since the registration would never be validated. |
| Storage backends | Supported on every storage backend. Where an engine has no conflict-tracked read path the clause is refused outright, so the guarantee is never quietly downgraded. |
| Clause conflicts | Cannot be combined with `VERSION`, `GROUP BY` or `SPLIT`, or with `LIMIT` across multiple targets. |

> [!NOTE]
> `FOR UPDATE` is used for a record the transaction reads but never writes. A record the transaction also writes is covered already by the write conflict check, so adding the clause there changes nothing.

## ACID at a glance

| Property | In SurrealDB |
| --- | --- |
| **Atomicity** | A transaction's statements commit together or roll back together. |
| **Consistency** | Schema, permissions, and statement semantics apply on every commit; you define business invariants in SurrealQL and application code. |
| **Isolation** | Snapshot isolation on all storage backends; write conflicts abort on commit, and [`FOR UPDATE`](#locked-reads-with-for-update) extends that check to records the transaction only reads. |
| **Durability** | Committed data persists according to your storage engine and sync settings - see [File-backed storage](/docs/running/file-backed.md) and [Deployment models](/docs/manage/self-hosted/deployment-models.md). |

Some features deliberately step outside the triggering transaction's ACID boundary. [`ASYNC` events](/docs/reference/query-language/statements/define/event.md#async-events), for example, run after commit in a separate transaction. Use them only when that trade-off is acceptable.

## See also

* [The `FOR UPDATE` clause](/docs/reference/query-language/statements/select.md#the-for-update-clause)
* [Transaction statements in SurrealQL](/docs/reference/query-language/language-primitives/transactions.md)
* [Using transactions to test code for errors](/docs/learn/querying/concepts-and-guides/testing.md#using-manual-transactions-for-testing)
* [Architecture](/docs/learn/data-models/architecture.md)

---

Source: https://surrealdb.com/docs/learn/querying/concepts-and-guides/working-with-types

# Working with types

Tips for working with data types in SurrealDB, including arrays, type safety, typed LET statements, and chaining array functions.

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](/docs/reference/query-language/language-primitives/data-types.md) page in the query language documentation.

## Arrays

Working with arrays is one of the most important skills when working with SurrealDB, as [`SELECT`](/docs/reference/query-language/statements/select.md) statements return an array of values by default unless the `ONLY` keyword is used on an array that contains a single item.

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

```surql title="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.

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

```surql title="Output"
[
	4,
	76,
	9,
	8,
	5
]
```

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

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

## Type safety and type conversion

### Using typed `LET` statements

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.

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

```surql title="Output"
0
```

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

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

```surql title="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
```

Once the error is located, the fix is easier to implement.

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

```surql title="Output"
2
```

### Mapping items in an array

The [`array::map()`](/docs/reference/query-language/functions/database-functions/array.md#arraymap) function provides access to each item in an array, allowing an operation to be performed on it before being passed on. Other similar functions can also be used, such as [`array::filter()`](/docs/reference/query-language/functions/database-functions/array.md#arrayfilter) 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 the location data is a valid geometric point, and then returns the remaining items as objects with a different structure.

```surql
[
	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
    });
```

```surql title="Output"
[
	{
		coordinates: (98, 65.7),
		item: 0,
		name: 'Some city'
	},
	{
		coordinates: (0, 0.1),
		item: 1,
		name: 'Other city'
	}
]
```

While refining pipelines like this, [`.expect()`](/docs/reference/query-language/functions/database-functions/value.md#valueexpect) (from SurrealDB 3.1) on the result of `.filter()` or `.map()` can confirm the shape you expect before the query continues. For example, `.expect(|$records| $records.len() = 2, "Expected exactly two valid cities")`.

---

Source: https://surrealdb.com/docs/learn/querying/custom-apis/managing-apis

# Managing APIs

Defining custom HTTP-style endpoints on SurrealDB so clients hit a narrow surface instead of arbitrary queries.

Custom APIs let you expose a small, deliberate set of routes on top of SurrealDB. Instead of handing every caller a generic query channel, you can define named endpoints that return a familiar HTTP-shaped result.

That pattern pairs well with tightening access such as varying querying timeouts for free or entry-level users on an app. To accomplish this, you can combine [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) with [capabilities](/docs/learn/security/authorization/capabilities.md) or server [environment variables](/docs/reference/cli/db/environment-variables.md) so certain users only see the APIs you designed.

## Where endpoints live

Each definition maps to a path under `/api/:namespace/:database/...`, followed by the path you gave in the statement. As such, an endpoint path like `get_users` in namespace `my_namespace` and database `my_database` becomes something like `/api/my_namespace/my_database/get_users` over HTTP.

## Reading the incoming request

Inside the handler you can use the built-in [`$request`](/docs/reference/query-language/language-primitives/parameters.md#request) value. This parameter holds the `method`, `body`, `headers`, `query`, `params` (from your path pattern), and `context` you or middleware may have set.

## A minimal endpoint

Here is a small endpoint that echoes part of the body and sets a couple of headers. The precise clauses and permissions are spelled out in the [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) reference page.

```surql title="Defining an API endpoint"
DEFINE API "/test"
    FOR get, post
        MIDDLEWARE
            api::timeout(1s)
        THEN {
            {
                status: 200,
                body: {
                    request: $request.body,
                    response: "The server works"
                },
                headers: {
                    'last-modified': <string>time::now(),
                    'expires': <string>(time::now() + 4d)
                }
            };
        };
```

You can test the endpoint from SurrealQL without needing to deploy by calling the [`api::invoke`](/docs/reference/query-language/functions/database-functions/api.md) function. This function takes either the path alone or the path plus a body object.

```surql
api::invoke("/test");

api::invoke("/test", {
    body: {
        hi: "please",
        give: "me",
        the: "information"
    }
});
```

## Path patterns: one segment or the rest of the URL

The path string in `DEFINE API` can be static, or it can capture pieces of the URL.

A segment like `"/users/:id"` binds one path component - anything in that slot shows up on `$request.params`. A trailing pattern with `*` instead of `:` matches everything from that point - handy for nested paths or file-like routes.

```surql
DEFINE API OVERWRITE "/test/:anything_goes" FOR get THEN {
    RETURN {
        body: {
            some: "data"
        }
    }
};

api::invoke("/test/this_matches");
api::invoke("/test/same_here");
api::invoke("/test/but/this/wont/match");
```

Here the first two calls hit the handler; the third does not, because `:anything_goes` only covers a single segment - extra slashes mean “no matching route”, which surfaces as a 404-style result from `api::invoke`.

To accept multiple trailing segments, switch the capture to the `*` form:

```surql
DEFINE API OVERWRITE "/test/*anything_goes" FOR get THEN {
    RETURN {
        body: {
            some: "data"
        }
    }
};

api::invoke("/test/this_matches");
api::invoke("/test/same_here");
api::invoke("/test/works/with/multiple/paths/now");
```

All three calls succeed because the remainder of the path is treated as one captured piece.

## Middleware

Built-in helpers such as `api::timeout` sit in the `MIDDLEWARE` list before your `THEN` block. For more details on how to use middleware, see the [next page](/docs/learn/querying/custom-apis/middleware.md).

## Where to read more

* [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) - full statement reference, including `PERMISSIONS`.
* [API functions](/docs/reference/query-language/functions/database-functions/api.md) - `api::invoke` and related helpers.
* [Custom functions](/docs/learn/querying/concepts-and-guides/custom-functions.md) - how `fn::` functions fit into larger designs.

---

Source: https://surrealdb.com/docs/learn/querying/custom-apis/middleware

# Middleware

Chaining custom functions before your API handler so you can share logic on DEFINE API routes.

When you use [`DEFINE API`](/docs/reference/query-language/statements/define/api.md), the `THEN` block is the handler that decides the HTTP-shaped response. Middleware is everything you run before that handler, such as timeouts, logging, auth checks, or tweaks to the outgoing payload. SurrealDB runs built-in helpers from the [API functions](/docs/reference/query-language/functions/database-functions/api.md) package alongside your own `fn::` functions, in the order you list them.

Custom middleware functions are ordinary [user-defined functions](/docs/reference/query-language/statements/define/function.md). Each one receives:

* The current request object (conventionally `$req`).
* A `next` closure that continues the chain and eventually runs `THEN` when no middleware remains.

You can add extra parameters after those two. Their values are supplied when you attach the function to `DEFINE API` (for example passing in `time::now()` to return the time at which a request was processed).

The function must return an object - that is the response object passed to the next middleware or returned to the client. Names like `$req` and `$next` are only conventions; what matters is the order of arguments and that you actually call `$next($req)` when you want the pipeline to proceed.

## Starting from a handler with no middleware

```surql
DEFINE API "/custom_response"
    FOR get
        THEN {
            {
                status: 200,
                body: {
                    num: 1
                }
            };
        };
```

Calling `api::invoke("/custom_response")` returns the body you would expect:

```surql title="Sample output"
{
	body: {
		num: 1
	},
	headers: {
		"x-surreal-request-id": '90ecf16c-d3d5-4a06-825f-c54c5af5afbf'
	},
	request_id: '90ecf16c-d3d5-4a06-825f-c54c5af5afbf',
	status: 200
}
```

## Adding a function that adjusts the response

This middleware calls `$next($req)` to obtain the handler's result, then bumps `body.num` by one before returning:

```surql
DEFINE FUNCTION fn::increment_num($req: object, $next: function) -> object {
    LET $res = $next($req);
    $res + { body: { num: $res.body.num + 1 } }
};

DEFINE API "/custom_response"
    FOR get
        MIDDLEWARE
            fn::increment_num()
        THEN {
            {
                status: 200,
                body: {
                    num: 1
                }
            };
        };
```

Now `api::invoke("/custom_response")` yields `num: 2`, showing that the outer middleware has reshaped what the caller sees.

```surql title="Sample output"
{
	body: {
		num: 2
	},
	headers: {
		"x-surreal-request-id": 'a4fa9a57-f1ac-4eb1-8433-cce37a19a5e0'
	},
	request_id: 'a4fa9a57-f1ac-4eb1-8433-cce37a19a5e0',
	status: 200
}
```

## Stacking two pieces of middleware

Order matters when adding middleware, so be sure to list outer effects first if they should wrap everything that follows. Here a timer middleware adds a timestamp as an `x-called-at` header, and increment still adjusts the body:

```surql
DEFINE FUNCTION fn::start_timer($req: object, $next: function, $called_at: datetime) -> object {
    LET $res = $next($req);
    $res + { headers: { "x-called-at": <string>$called_at } }
};

DEFINE FUNCTION fn::increment_num($req: object, $next: function) -> object {
    LET $res = $next($req);
    $res + { body: { num: $res.body.num + 1 } }
};

DEFINE API "/custom_response"
    FOR get
        MIDDLEWARE
            fn::start_timer(time::now()),
            fn::increment_num()
        THEN {
            {
                status: 200,
                body: {
                    num: 1
                }
            };
        };

api::invoke("/custom_response");
```

```surql title="Sample output"
{
	body: {
		num: 2
	},
	headers: {
		"x-called-at": '2026-10-14T04:51:23.959554Z',
		"x-surreal-request-id": 'ba447d57-24e6-4cf4-9e8b-68076b040980'
	},
	request_id: 'ba447d57-24e6-4cf4-9e8b-68076b040980',
	status: 200
}
```

## See also

* [Managing APIs](/docs/learn/querying/custom-apis/managing-apis.md) - defining routes, path captures, and `$request`.
* [Custom functions](/docs/learn/querying/concepts-and-guides/custom-functions.md) - patterns for `fn::` definitions used in middleware.

---

Source: https://surrealdb.com/docs/learn/querying/gql/mutations

# Mutations

ISO GQL (Cypher Query Language) data-modifying statements - INSERT, SET, REMOVE, and DELETE - on the /gql endpoint.

_(since v3.2.0)_

ISO GQL (Cypher Query Language) on SurrealDB supports the four **data-modifying** statements from the standard - **`INSERT`**, **`SET`**, **`REMOVE`**, and **`DELETE`** - in addition to read-only `MATCH … RETURN` queries. Mutations run on the same [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql) endpoint and through [`eval::gql`](/docs/reference/query-language/functions/database-functions/eval.md#evalgql) (which still needs [`--allow-eval-query`](/docs/learn/security/authorization/capabilities.md#eval-queries)).

> [!NOTE]
> From **3.3.0**, GQL is enabled by default. On **3.2.x**, enable it with [`--allow-experimental gql`](/docs/reference/cli/db/commands/start.md#experimental-capabilities). Mutation-bearing queries open a **write transaction** - the same permissions, field validation, events, indexes, references, and live-query behaviour as native SurrealQL `CREATE` / `UPDATE` / `DELETE` / `RELATE` apply.

## Linear programs

A GQL query is a **linear program**: an ordered sequence of `MATCH` / `OPTIONAL` read clauses and data-modifying statements, in **any interleaving**, optionally ending in `RETURN`.

- The **binding table** threads through every step in textual order.
- A **`MATCH` or `OPTIONAL` after a mutation** re-scans **live** (post-write) state in the same transaction - a clause after `SET` or `DELETE` sees updated or removed records; a clause after `INSERT` sees created records and may bind variables the `INSERT` introduced.
- **`RETURN` is optional** when the query mutates. Read-only queries must still end with `RETURN`.
- A mutation-only query (no `RETURN`) returns an **empty** result.

The examples below assume that [the following seed data](/docs/learn/querying/gql/via-http.md#load-sample-data) has been first uploaded.

### Via `eval::gql`

The same mutation strings work in the REPL when both **`gql`** and **`eval`** are allowed - see [Try without HTTP](/docs/learn/querying/gql/via-http.md#try-without-http---evalgql):

```cypher
eval::gql("MATCH (n:person WHERE n.name = 'A') SET n.age = 99 RETURN n.age AS age");
-- { age: 99 }
```

## `SET`

Update properties on a bound node or edge.

```gql
MATCH (n:person WHERE n.name = 'A') SET n.age = 99 RETURN n.age AS age
```

- **`SET a.p = v`** - set one property.
- **`SET a = { … }`** - replace all **user** properties (a `CONTENT`-style replace). Properties absent from the map are dropped. The record `id`, and an edge's `in` / `out`, are preserved.

Setting reserved keys **`id`**, **`in`**, or **`out`** is rejected on both the per-property form (`SET a.id = …`) and the object form.

**`SET a:Label`** is rejected - a SurrealDB record belongs to exactly one table; labels are immutable.

## `REMOVE`

Unset a property without deleting the record.

```gql
MATCH (n:person WHERE n.name = 'A') REMOVE n.age RETURN n.name AS name
```

**`REMOVE a:Label`** is rejected for the same one-table-per-record rule as `SET` label mutations.

## `DELETE`

Delete a matched node or edge.

```gql
MATCH (n:person WHERE n.name = 'A') DETACH DELETE n
```

- **`NODETACH DELETE`** (ISO default) - errors if the node still has connected edges.
- **`DETACH DELETE`** - deletes the node and cascades connected edges. Bound edge variables for cascaded edges become `null` in a trailing `RETURN`.

A deleted binding becomes **`null`** in post-mutation projections.

## `INSERT`

Create nodes and edges. Each new node requires a **label** (table name).

**Leading insert** (no preceding `MATCH`) - runs once:

```gql
INSERT (p:person {name: 'Z', age: 1}) RETURN p.name AS name
```

**After `MATCH`** - runs **once per binding row**. Relate existing endpoints or create new nodes:

```gql
MATCH (a:person WHERE a.name = 'A')
MATCH (b:person WHERE b.name = 'C')
INSERT (a)-[:likes]->(b)
RETURN a.name AS src, b.name AS dst
```

Graph-pattern form: `INSERT (a:Label {…})-[:Edge {…}]->(b:Label {…})`. A node with no label and no properties references a variable already bound by a preceding `MATCH` (an existing endpoint).

## Read after write

Mutations and reads can interleave in one query:

```gql
MATCH (n:person WHERE n.name = 'C') SET n.age = 20
MATCH (m:person WHERE m.age = 20)
RETURN m.name AS name ORDER BY name
```

The second `MATCH` sees the updated age on `C` and returns both `B` and `C` (both age 20).

## `RETURN` after mutations

When present, `RETURN` projects the **post-mutation** binding table:

- **`SET` / `INSERT`** rebind the mutated or created record (the **after** image).
- Fan-out that binds the same record more than once applies writes **per row**, in order (last-write-wins for row-dependent values).
- **`DELETE`** nulls deleted bindings; **`DETACH DELETE`** also nulls cascaded edge bindings.

## Rejected forms

The lowering layer rejects several ISO forms that do not map cleanly to SurrealDB's one-table-per-record model, including:

- Label mutations (`SET a:Label`, `REMOVE a:Label`)
- Mutations on unbound variables, groups, or path patterns
- Reserved property keys on `SET` (`id`, `in`, `out`)
- Malformed `INSERT` graph patterns (e.g. undirected edges, anonymous nodes without labels where required)

Parse and semantic errors return the same error envelope as read queries.

## SurrealQL vs GQL for writes

| Task | Prefer |
| --- | --- |
| Schema changes, bulk load, `RELATE` with arbitrary SurrealQL | [SurrealQL](/docs/reference/query-language.md) on [`POST /sql`](/docs/reference/rest-api/http-protocol.md) |
| Graph-pattern read + write in one ISO GQL (Cypher Query Language) program | GQL mutations on [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql) |
| Nested GQL inside an open SurrealQL transaction | [`eval::gql`](/docs/reference/query-language/functions/database-functions/eval.md#evalgql) |

## Next steps

- [GQL overview](/docs/learn/querying/gql/overview.md) - capabilities, wire surfaces, and syntax differences from openCypher
- [Sample queries](/docs/learn/querying/gql/sample-queries.md) - read-only pattern examples
- [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql) - HTTP headers and response envelope

---

Source: https://surrealdb.com/docs/learn/querying/gql/overview

# GQL

Query graph data with ISO GQL (Cypher Query Language), a graph pattern language.

_(since v3.2.0)_

SurrealDB supports **[ISO/IEC 39075 GQL](https://www.iso.org/standard/76120.html)** for **graph pattern matching and data modification** over your existing tables and `RELATE` edges. The surface syntax is closer to **Cypher-style `MATCH … RETURN`** than to SurrealQL `SELECT`, but it runs on the same storage model: node labels map to tables, edge types map to relation tables, and properties map to record fields.

> [!NOTE]
> From **3.3.0**, ISO GQL (Cypher Query Language) is enabled by default on `POST /gql`, the `gql` RPC method, and MCP - no experimental capability is required. On **3.2.x**, enable it with `--allow-experimental gql` (or `SURREAL_CAPS_ALLOW_EXPERIMENTAL=gql`). The surface includes read queries (`MATCH … RETURN`) and data-modifying statements (`INSERT`, `SET`, `REMOVE`, `DELETE`) - see [GQL mutations](/docs/learn/querying/gql/mutations.md).

## GQL is not GraphQL

SurrealDB exposes two different graph query languages on **separate** endpoints. Do not abbreviate [GraphQL](/docs/learn/querying/graphql/overview.md) to `gql` in product identifiers - the short name **`gql`** refers to ISO GQL (Cypher Query Language) only.

| | **GQL** (this guide) | **[GraphQL](/docs/learn/querying/graphql/overview.md)** |
| --- | --- | --- |
| Standard | [ISO/IEC 39075 GQL](https://www.iso.org/standard/76120.html) | [GraphQL](https://graphql.org/) |
| Syntax | `MATCH (a)-[:knows]->(b) RETURN …` | `query { persons { name } }` |
| HTTP | [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql) | [`POST /graphql`](/docs/reference/rest-api/http-protocol.md#graphql) |
| WebSocket RPC | `method: "gql"` | `method: "graphql"` |
| Setup | On by default from 3.3.0 (experimental `gql` on 3.2.x) | [`DEFINE CONFIG GRAPHQL`](/docs/reference/query-language/statements/define/config.md) |
| Schema | Tables and `RELATE` edges you already have | Auto-generated GraphQL schema from your database |

## When to use GQL

- You already think in **graph patterns** such as `(a)-[:knows]->(b)`, `SHORTEST`, and `ALL SHORTEST`, and have yet to learn how to query such paths in SurrealQL.
- You are migrating a database from Neo4j to SurrealDB and want to test to ensure that existing Cypher queries map to the same output.
- You want a **stable graph query surface** aligned with the ISO GQL (Cypher Query Language) standard.

For general-purpose schema changes, bulk load, and full SurrealQL expressiveness, keep using [SurrealQL](/docs/reference/query-language.md) and the [`/sql`](/docs/reference/rest-api/http-protocol.md) endpoint. For ISO graph-pattern **writes** (`INSERT`, `SET`, `REMOVE`, `DELETE`), see [GQL mutations](/docs/learn/querying/gql/mutations.md).

## Wire surfaces

| Surface | How to call |
| --- | --- |
| **HTTP** | [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql) - raw GQL query in the request body |
| **WebSocket RPC** | `{ "method": "gql", "params": ["<query>", { "var": value }] }` - use for typed `$variables` |
| **Postgres wire** | [`SET dialect = 'gql'`](/docs/reference/rest-api/postgres-protocol.md) (or `options=-c dialect=gql` at connect) on a Postgres client connection |
| **MCP** | `gql` tool (when the server exposes MCP) |
| **SurrealQL** | [`eval::gql`](/docs/reference/query-language/functions/database-functions/eval.md#evalgql) - nested GQL in the caller's transaction (capability-gated) |

Session headers match `/sql`: `Surreal-NS`, `Surreal-DB`, and authentication. Responses use the same JSON envelope as `/sql` (`status`, `result`, `time`).

## Try from SurrealQL (`eval::gql`)

To experiment in the REPL without `curl` or `POST /gql`, wrap a GQL string in [`eval::gql`](/docs/reference/query-language/functions/database-functions/eval.md#evalgql). The function runs the same engine as the HTTP endpoint and participates in the caller's transaction (including [mutations](/docs/learn/querying/gql/mutations.md)).

`eval::gql` needs the **`eval`** capability that `--allow-all` does not enable: [`--allow-eval-query`](/docs/learn/security/authorization/capabilities.md#eval-queries) (denied for every subject by default). From 3.3.0 you do **not** also need `--allow-experimental gql` (that flag is still required on 3.2.x).

**Embedded REPL** (`surreal db sql` against `memory` or a file path - no separate `surreal db start`):

**Bash**

```bash
echo 'eval::gql("MATCH (n:person) RETURN n.name AS name ORDER BY name");' | surreal db sql --user root --pass secret --allow-eval-query --pretty --hide-welcome
```

**PowerShell**

```powershell
'eval::gql("MATCH (n:person) RETURN n.name AS name ORDER BY name");' | surreal db sql --user root --pass secret --allow-eval-query --pretty --hide-welcome
```

To explore interactively instead, open the same REPL without piping:

```bash
surreal db sql --user root --pass secret --allow-eval-query
```

**Remote server** (`surreal db start` + `surreal db sql -e ws://…`): pass `--allow-eval-query` on **`surreal db start`** only. The client REPL does not enable `eval` at runtime.

```bash
surreal db start --user root --pass secret --allow-eval-query
```

Optional bindings use an object as the second argument: `eval::gql("… WHERE n.age > $min …", { min: 18 })`. Full setup, seed data, and more examples can be found on the [Eval functions](/docs/reference/query-language/functions/database-functions/eval.md#evalgql) page.

## Data model mapping

| GQL | SurrealDB |
| --- | --- |
| `(:person)` | Records in table `person` |
| `-[k:knows]->` | Records in relation table `knows` (`in` / `out` record IDs) |
| `n.name` | Field `name` on the bound record |
| `$min` | Parameter - bind via RPC `params` object (typed JSON values) |

GQL is **not** lowered to SurrealQL text - it compiles to an internal match plan and runs on the streaming engine. The [sample queries](/docs/learn/querying/gql/sample-queries.md) page shows **SurrealQL that returns the same shape** where a close equivalent exists; some patterns (optional match blocks, path selectors) are much more concise in GQL.

## Notable syntax differences from openCypher

- `--` starts a **line comment**, not an undirected edge.
- Inequality is **`<>`**, not `!=`.
- Label conjunction uses **`&`** (`:person&employee`), not `:person:employee`.
- Variable-length quantifiers are **postfix on the edge**: `-[e:knows]->{1,3}(b)`, not `*1..3` inside the brackets.
- No `IN` list membership operator in this subset.

Design notes, the supported subset, and mutation semantics are documented in the SurrealDB source tree under `doc/opengql/` (`REFERENCE.md`, `LOWERING.md`). The parser grammar is vendored from the upstream [opengql/grammar](https://github.com/opengql/grammar) project; that name refers to the grammar repository, not the ISO standard SurrealDB implements.

## Next steps

- [GQL via HTTP](/docs/learn/querying/gql/via-http.md) - enable GQL, load data, and call `POST /gql` with cURL or `eval::gql` from the REPL
- [Postgres wire protocol](/docs/reference/rest-api/postgres-protocol.md) - run GQL from `psql` or Postgres drivers with `SET dialect = 'gql'`
- [GQL mutations](/docs/learn/querying/gql/mutations.md) - `INSERT`, `SET`, `REMOVE`, `DELETE`
- [`POST /gql` HTTP reference](/docs/reference/rest-api/http-protocol.md#gql) - headers, response envelope, and parameters
- [Sample GQL and SurrealQL queries](/docs/learn/querying/gql/sample-queries.md) - side-by-side examples on the seed graph
- [Eval functions](/docs/reference/query-language/functions/database-functions/eval.md) - run GQL from inside SurrealQL with `eval::gql`

---

Source: https://surrealdb.com/docs/learn/querying/gql/sample-queries

# Sample queries

Compare common GQL graph patterns against similar SurrealQL queries on the same seed graph.

_(since v3.2.0)_

The examples below use the same **person / knows / city** seed graph as the GQL language tests. SurrealQL snippets return the **same or equivalent row shape**, but are not a mechanical translation of the GQL compiler; they are idiomatic reads you can compare while learning.

## Prerequisites

Start a local instance with GQL enabled and load the seed graph - see [GQL via HTTP](/docs/learn/querying/gql/via-http.md#load-sample-data).

You can run the GQL examples in either of these ways:

| Approach | How |
| --- | --- |
| **HTTP** | cURL to `POST /gql` (shown in each GQL tab) - GQL is on by default from 3.3.0; on 3.2.x use `--allow-experimental gql` |
| **REPL** | [`eval::gql("…")`](/docs/reference/query-language/functions/database-functions/eval.md#evalgql) in `surreal db sql` or SurrealDB Studio - also needs [`--allow-eval-query`](/docs/learn/security/authorization/capabilities.md#eval-queries) on the process that runs the engine (embedded `surreal db sql`, or `surreal db start` for remote) |

Example REPL equivalent for the first query:

```cypher
eval::gql("MATCH (n:person) RETURN n.name AS name ORDER BY name");
-- [{ name: 'A' }, { name: 'B' }, { name: 'C' }]
```

## List nodes by label

**GQL**

```gql title="Query"
MATCH (n:person) RETURN n.name AS name ORDER BY name
```

```bash title="cURL"
curl -sS -X POST -u "root:secret" \
  -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" -H "Content-Type: text/plain" \
  -d 'MATCH (n:person) RETURN n.name AS name ORDER BY name' \
  http://localhost:8000/gql
```

```json title="Output"
[
	{ "name": "A" },
	{ "name": "B" },
	{ "name": "C" }
]
```

**SurrealQL**

```surql title="Query"
SELECT name FROM person ORDER BY name;
```

```surql title="Output"
[
	{ name: 'A' },
	{ name: 'B' },
	{ name: 'C' }
]
```

## Traverse an edge with a property filter

Only **person → person** `knows` edges with `since > 2020`.

**GQL**

```gql title="Query"
MATCH (a:person)-[k:knows]->(b:person)
WHERE k.since > 2020
RETURN a.name, b.name
ORDER BY a.name
```

```bash title="cURL"
curl -sS -X POST -u "root:secret" \
  -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" -H "Content-Type: text/plain" \
  -d 'MATCH (a:person)-[k:knows]->(b:person) WHERE k.since > 2020 RETURN a.name, b.name ORDER BY a.name' \
  http://localhost:8000/gql
```

```json title="Output"
[
	{ "a.name": "A", "b.name": "B" }
]
```

**SurrealQL**

```surql title="Query"
SELECT in.name AS `a.name`, out.name AS `b.name`
FROM knows
WHERE since > 2020 AND record::tb(out) = 'person'
ORDER BY in.name;
```

```surql title="Output"
[
	{ 'a.name': 'A', 'b.name': 'B' }
]
```

## Optional match

Every `person`, with an optional `knows` edge to a `city` when one exists.

**GQL**

```gql title="Query"
MATCH (a:person)
OPTIONAL MATCH (a)-[k:knows]->(b:city)
RETURN a.name AS name, b.name AS city
ORDER BY name
```

```bash title="cURL"
curl -sS -X POST -u "root:secret" \
  -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" -H "Content-Type: text/plain" \
  -d 'MATCH (a:person) OPTIONAL MATCH (a)-[k:knows]->(b:city) RETURN a.name AS name, b.name AS city ORDER BY name' \
  http://localhost:8000/gql
```

```json title="Output"
[
	{ "name": "A", "city": "London" },
	{ "name": "B", "city": null },
	{ "name": "C", "city": null }
]
```

**SurrealQL**

```surql title="Query"
SELECT name, array::first(->knows->city.name) AS city
FROM person
ORDER BY name;
```

```surql title="Output"
[
	{ name: 'A', city: 'London' },
	{ name: 'B', city: NONE },
	{ name: 'C', city: NONE }
]
```

## Aggregation with GROUP BY

Count outgoing **person → person** `knows` edges per person.

**GQL**

```gql title="Query"
MATCH (a:person)-[:knows]->(b:person)
RETURN a.name AS name, count(*) AS c
GROUP BY a.name
ORDER BY name
```

```bash title="cURL"
curl -sS -X POST -u "root:secret" \
  -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" -H "Content-Type: text/plain" \
  -d 'MATCH (a:person)-[:knows]->(b:person) RETURN a.name AS name, count(*) AS c GROUP BY a.name ORDER BY name' \
  http://localhost:8000/gql
```

```json title="Output"
[
	{ "name": "A", "c": 1 },
	{ "name": "B", "c": 2 },
	{ "name": "C", "c": 1 }
]
```

**SurrealQL**

```surql title="Query"
SELECT in.name AS name, count() AS c
FROM knows
WHERE record::tb(out) = 'person'
GROUP BY in.name
ORDER BY name;
```

```surql title="Output"
[
	{ name: 'A', c: 1 },
	{ name: 'B', c: 2 },
	{ name: 'C', c: 1 }
]
```

## What to try next

The language-test corpus under `language-tests/tests/opengql/` in the SurrealDB repository covers variable-length quantifiers (`->{1,3}`, `->*`), path search (`ALL SHORTEST`, `SHORTEST k`), multi-pattern comma joins, and [mutations](/docs/learn/querying/gql/mutations.md). Variable-length and path patterns have no single-line SurrealQL equivalent - they are the main reason to use GQL for reads; mutations are the ISO-native way to change graph data in the same program as `MATCH`.

---

Source: https://surrealdb.com/docs/learn/querying/gql/via-http

# Via HTTP

Enable ISO GQL (Cypher Query Language) on a SurrealDB instance and run queries through POST /gql.

_(since v3.2.0)_

The **`POST /gql`** endpoint accepts a **raw GQL query** in the request body (not JSON-wrapped). Authentication and namespace selection use the same headers as [`POST /sql`](/docs/reference/rest-api/http-protocol.md). For the full HTTP reference (headers, response envelope, limits), see [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql).

## Start SurrealDB with GQL

From **3.3.0**, start a normal instance - ISO GQL (Cypher Query Language) is on by default:

```bash
surreal db start --log info --user root --pass secret memory
```

On **3.2.x**, pass the experimental capability (storage path after the flag, or use `memory`):

```bash
surreal db start --log info --user root --pass secret \
  --allow-experimental gql memory
```

**Bash**

```bash
export SURREAL_CAPS_ALLOW_EXPERIMENTAL=gql
surreal db start --log info --user root --pass secret memory
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "gql"
surreal db start --log info --user root --pass secret memory
```

> [!NOTE]
> This is **not** [GraphQL](/docs/learn/querying/graphql/overview.md) - use [`POST /graphql`](/docs/reference/rest-api/http-protocol.md#graphql) for GraphQL queries.
## Load sample data

Use [`POST /sql`](/docs/reference/rest-api/http-protocol.md) with namespace **`main`** and database **`main`** (set via headers below):

```bash
curl -sS -X POST -u "root:secret" \
  -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" -H "Content-Type: text/plain" \
  -d "CREATE person:1 SET name = 'A', age = 30, active = true, city = 'London';
CREATE person:2 SET name = 'B', age = 20, active = false, city = 'Paris';
CREATE person:3 SET name = 'C', city = 'London';
CREATE city:1 SET name = 'London';
INSERT RELATION INTO knows [
	{ id: knows:k12, in: person:1, out: person:2, since: 2021 },
	{ id: knows:k21, in: person:2, out: person:1, since: 2018 },
	{ id: knows:k23, in: person:2, out: person:3, since: 2020 },
	{ id: knows:k1c, in: person:1, out: city:1, since: 2019 },
	{ id: knows:k31, in: person:3, out: person:1 }
];" \
  http://localhost:8000/sql
```

## `POST /gql`

Send the GQL query as the **raw body** with `Content-Type: text/plain` (or omit; UTF-8 text is expected).

```bash
curl -sS -X POST -u "root:secret" \
  -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" -H "Content-Type: text/plain" \
  -d 'MATCH (n:person) RETURN n.name AS name ORDER BY name' \
  http://localhost:8000/gql
```

Example response (same envelope as `/sql`):

```json
[
	{
		"status": "OK",
		"result": [
			{ "name": "A" },
			{ "name": "B" },
			{ "name": "C" }
		],
		"time": "1.5ms"
	}
]
```

### Response formats

Set `Accept` to:

- `application/json` (default)
- `application/cbor` for CBOR-encoded results

Parse errors return **HTTP 400** with an error payload.

## Mutations

The same endpoint accepts **data-modifying** GQL - `INSERT`, `SET`, `REMOVE`, and `DELETE` - interleaved with `MATCH` / `OPTIONAL` in one query. Mutation-bearing requests run in a **write transaction** and enforce the same permissions as SurrealQL writes.

Example - update a property and return the new value:

```bash
curl -sS -X POST -u "root:secret" \
  -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" -H "Content-Type: text/plain" \
  -d "MATCH (n:person WHERE n.name = 'A') SET n.age = 99 RETURN n.age AS age" \
  http://localhost:8000/gql
```

See [GQL mutations](/docs/learn/querying/gql/mutations.md) for `INSERT`, `REMOVE`, `DELETE`, read-after-write interleaving, and rejected forms.

## WebSocket RPC

On an authenticated WebSocket session, send the query as the first parameter. Pass **typed** variables as an optional second object:

```json
{
	"id": 1,
	"method": "gql",
	"params": [
		"MATCH (n:person) RETURN n.name AS name ORDER BY name"
	]
}
```

With parameters:

```json
{
	"id": 2,
	"method": "gql",
	"params": [
		"MATCH (n:person) WHERE n.age > $min RETURN n.name AS name",
		{ "min": 18 }
	]
}
```

## Try without HTTP - `eval::gql`

If you prefer the CLI or SurrealDB Studio over cURL, run GQL through [`eval::gql`](/docs/reference/query-language/functions/database-functions/eval.md#evalgql) inside SurrealQL. You still need [`--allow-eval-query`](/docs/learn/security/authorization/capabilities.md#eval-queries), as `eval::*` is denied by default even under `--allow-all`. From 3.3.0 you do not need `--allow-experimental gql` as well (required on 3.2.x only).

One process (embedded): pass `--allow-eval-query` on `surreal db sql`, load the [seed data](#load-sample-data) with ordinary SurrealQL, then:

```cypher
eval::gql("MATCH (n:person) RETURN n.name AS name ORDER BY name");
```

```bash
surreal db sql --user root --pass secret --allow-eval-query
```

**Two processes (remote):** enable eval on **`surreal db start`**, then connect with `surreal db sql` as usual - capability flags on the client do not turn on `eval` for a remote engine.

```bash
surreal db start --user root --pass secret --allow-eval-query
```

Mutations work the same way: `eval::gql("MATCH (n:person WHERE n.name = 'A') SET n.age = 99 RETURN n.age AS age")` runs in the open SurrealQL transaction. See [GQL mutations](/docs/learn/querying/gql/mutations.md) and [Eval functions](/docs/reference/query-language/functions/database-functions/eval.md#evalgql).

---

Source: https://surrealdb.com/docs/learn/querying/graphql/overview

# GraphQL

Query SurrealDB with GraphQL over HTTP or RPC.

SurrealDB supports [GraphQL](https://graphql.org/) through the [`/graphql`](/docs/reference/rest-api/http-protocol.md#graphql) endpoint, which can be accessed via [SurrealDB Studio](https://studio.surrealdb.com/), GraphiQL, Postman, or any other GraphQL client.

> [!NOTE]
> **GraphQL is not GQL.** [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md) (`MATCH … RETURN …`) is a separate language on [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql). Do not abbreviate GraphQL to `gql` - in SurrealDB, `gql` always means ISO GQL (Cypher Query Language).

## Key features

The main features of GraphQL are:

- **Declarative Data Fetching**: GraphQL allows you to request exactly the data you need, no more and no less. This reduces over-fetching and under-fetching of data, leading to more efficient queries.

- **Strongly Typed Schema**: GraphQL uses a strong type system to define the capabilities of an API. This schema serves as a contract between the client and the server, ensuring that queries are valid before execution.

- **Hierarchical Structure**: GraphQL queries mirror the shape of the data they return, making it intuitive to work with nested data structures.

- **Single Endpoint**: When using GraphQL over HTTP, it typically uses a single endpoint, simplifying API architecture and reducing network overhead.

- **Ecosystem and Tools**: GraphQL has a rich ecosystem of tools for development, testing, and monitoring, including GraphiQL for query exploration and Apollo Client for state management.

## Getting started

Enabling GraphQL queries for SurrealDB can be done through a single statement: [`DEFINE CONFIG GRAPHQL`](/docs/reference/query-language/statements/define/config.md).

The simplest clause to follow this with is `AUTO`, which will automatically include all tables in the GraphQL schema.

```surql
DEFINE CONFIG GRAPHQL AUTO;
```

A query for `(INFO FOR DB).configs` will show that the above statement ends up having the clauses `TABLES AUTO FUNCTIONS AUTO`.

```surql
{ GraphQL: 'GRAPHQL TABLES AUTO FUNCTIONS AUTO' }
```

For more fine tuning of the configuration, the `TABLES` and `FUNCTIONS` clauses can be followed by [other clauses](/docs/reference/query-language/statements/define/config.md#tables-configuration).

## Schema naming

_(since v3.1.0)_

From SurrealDB 3.1.0, the auto-generated GraphQL schema follows a single **Apollo-style** naming convention (there is no `NAMING` switch):

| Operation | Example for table `person` |
| --- | --- |
| Fetch one record | `person(id: ID!)` |
| List records | `persons(filter, where, order, limit, start, version)` - pluralised table name |
| Aggregate | `persons_aggregate(filter, groupBy, …)` |
| Create / update / delete | `createPerson`, `updatePerson`, `deletePerson` (bulk: `createPersons`, …) |

Field names on types mirror SurrealQL unless you set [`GRAPHQL_ALIAS`](/docs/reference/query-language/statements/define/field.md) or [`GRAPHQL_DEPRECATED`](/docs/reference/query-language/statements/define/field.md) on [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) / [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) / [`DEFINE FUNCTION`](/docs/reference/query-language/statements/define/function.md). List queries also support Relay-style **`personsConnection`** cursor pagination alongside offset `limit` / `start`.

If you upgrade from 3.0.x, regenerate client stubs and saved queries - names such as `_get_person` and `createManyPerson` are no longer generated for typical tables.

## Next steps

The next page introduces some common GraphQL patterns and their SurrealQL equivalents or near equivalents.

This is followed by the following pages that detail the various tools by which GraphQL queries can be run on a SurrealDB database:

- Using the [`/graphql`](/docs/reference/rest-api/http-protocol.md#graphql) endpoint [via HTTP (cURL)](/docs/learn/querying/graphql/via-http.md)
- Using the [`/graphql`](/docs/reference/rest-api/http-protocol.md#graphql) endpoint [via Bruno](/docs/learn/querying/graphql/via-bruno.md)
- Using [SurrealDB Studio](https://studio.surrealdb.com/), SurrealDB's interactive environment for experimenting with GraphQL queries and seeing results immediately in the UI.

Other tools such as Postman can also be used against the `/graphql` endpoint.

## Examples

- [Sample queries](/docs/learn/querying/graphql/sample-queries.md) - queries to run against a GraphQL-enabled database

---

Source: https://surrealdb.com/docs/learn/querying/graphql/sample-queries

# Sample queries

Compare common GraphQL queries against similar SurrealQL SELECT patterns

_(since v3.1.0)_

From SurrealDB 3.1.0, SurrealDB’s GraphQL layer uses **Apollo-style** names: a **pluralised list** field (for example `persons` for table `person`), a **singular fetch** field `person(id: …)`, and `persons_aggregate` for aggregates. Internally these map to SurrealQL-style reads (typically `SELECT`). The SurrealQL here is a rough equivalent for the same or similar data shape. See [GraphQL overview](/docs/learn/querying/graphql/overview.md#schema-naming) for the full naming table.

Before trying the examples, enable GraphQL and define data in the current namespace and database (see [GraphQL overview](/docs/learn/querying/graphql/overview.md) using [`DEFINE CONFIG GRAPHQL AUTO`](/docs/reference/query-language/statements/define/config.md)). The snippets below assume:

- Namespace **`main`**, database **`main`**
- Root authentication **`root`** / **`secret`**
- A `person` table with `name` and `age`, and records `person:simon` and `person:marcus` as in [GraphQL via HTTP](/docs/learn/querying/graphql/via-http.md)

```surql title="Schema and sample data"
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name ON TABLE person TYPE string;
DEFINE FIELD age ON TABLE person TYPE number;
CREATE person:simon SET name = "Simon", age = 23;
CREATE person:marcus SET name = "Marcus", age = 28;
DEFINE CONFIG GRAPHQL AUTO;
```

## List records and choose fields

GraphQL here returns a list of objects, similar to `SELECT` without `ONLY`.

**GraphQL**

```graphql title="Query"
query {
	persons {
		name
		age
	}
}
```

```graphql title="Output"
{
	"data": {
		"persons": [
			{
				"age": 28,
				"name": "Marcus"
			},
			{
				"age": 23,
				"name": "Simon"
			}
		]
	}
}
```

**SurrealQL**

```surql title="Query"
SELECT name, age FROM person;
```

```surql title="Output"
[
	{
		age: 28,
		name: 'Marcus'
	},
	{
		age: 23,
		name: 'Simon'
	}
]
```

**cURL (HTTP)**

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json"
  -d '{ "query": "query { persons { name age } }" }' http://localhost:8000/graphql
```

## Fetch a single record by id

Use **`person(id: …)`** with the **record key** (`simon`, not `person:simon` in the argument). The GraphQL `id` field on the result is the full record id.

**GraphQL**

```graphql title="Query"
query {
	person(id: "simon") {
		id
		name
		age
	}
}
```

```graphql title="Output"
{
	"data": {
		"person": {
			"id": "person:simon",
			"name": "Simon",
			"age": 23
		}
	}
}
```

**SurrealQL**

```surql title="Query"
SELECT * FROM ONLY person:simon;
```

```surql title="Output"
{
	age: 23,
	id: person:simon,
	name: 'Simon'
}
```

**cURL (HTTP)**

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json"
  -d '{ "query": "query { person(id: \"simon\") { id name age } }" }' http://localhost:8000/graphql
```

## Limit how many records are returned

GraphQL uses **`limit`** (and optional **`start`** for offset). SurrealQL uses `LIMIT` / `START` and `ONLY` to return a single record as opposed to an array containing a single record.

**GraphQL**

```graphql title="Query"
query {
	persons(limit: 1) {
		name
		age
	}
}
```

```graphql title="Output"
{
	"data": {
		"persons": [
			{
				"age": 28,
				"name": "Marcus"
			}
		]
	}
}
```

**SurrealQL**

```surql title="Query"
SELECT name, age FROM ONLY person LIMIT 1;
```

```surql title="Output"
{
	age: 28,
	name: 'Marcus'
}
```

**cURL (HTTP)**

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json"
  -d '{ "query": "query { persons(limit: 1) { name age } }" }' http://localhost:8000/graphql
```

## Filter records

GraphQL accepts **`filter`** or **`where`** with the generated input type for the table. For a scalar field, use comparison keys such as **`eq`**, **`ne`**, **`gt`**, and **`lt`** where the schema allows them.

The `id` field can be filtered too, but it takes the full record id, so `persons(where: { id: { eq: "person:simon" } })` returns Simon, while `"simon"` fails with `Error converting value: "simon" to type: record`. This is the opposite of [`person(id: …)`](#fetch-a-single-record-by-id), which takes only the record key and returns `null` when given `person:simon`.

**GraphQL**

```graphql title="Query"
query {
	persons(where: { age: { eq: 23 } }) {
		name
	}
}
```

```graphql title="Output"
{
	"data": {
		"persons": [
			{
				"name": "Simon"
			}
		]
	}
}
```

**SurrealQL**

```surql title="Query"
SELECT name FROM person WHERE age = 23;
```

```surql title="Output"
[
	{
		name: 'Simon'
	}
]
```

**cURL (HTTP)**

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json"
  -d '{ "query": "query { persons(where: { age: { eq: 23 } }) { name } }" }' http://localhost:8000/graphql
```

## Order results

Use an **`order`** argument with **`asc`** or **`desc`** and a field name.

**GraphQL**

```graphql title="Query"
query {
	persons(order: { asc: age }) {
		name
		age
	}
}
```

```graphql title="Output"
{
	"data": {
		"persons": [
			{
				"age": 23,
				"name": "Simon"
			},
			{
				"age": 28,
				"name": "Marcus"
			}
		]
	}
}
```

**SurrealQL**

```surql title="Query"
SELECT name, age FROM person ORDER BY name ASC;
```

```surql title="Output"
[
	{
		age: 23,
		name: 'Simon'
	},
	{
		age: 28,
		name: 'Marcus'
	}
]
```

**cURL (HTTP)**

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json"
  -d '{ "query": "query { persons(order: { asc: age }) { name age } }" }' http://localhost:8000/graphql
```

## Next steps

- Query the same endpoint from HTTP clients: [GraphQL via HTTP](/docs/learn/querying/graphql/via-http.md)
- Optional: [Bruno](/docs/learn/querying/graphql/via-bruno.md) or [SurrealDB Studio](/docs/learn/querying/graphql/via-studio.md)

For the full configuration surface (tables, functions, limits), see [`DEFINE CONFIG GRAPHQL`](/docs/reference/query-language/statements/define/config.md).

---

Source: https://surrealdb.com/docs/learn/querying/graphql/via-bruno

# Via Bruno

In this section, you will explore querying SurrealDB using Bruno.

Bruno is an API client that can send GraphQL queries to SurrealDB over HTTP. This page covers starting a server and querying it from Bruno.

## Getting started

Before you can start making queries, you need to start SurrealDB. You can do this by starting a new instance of SurrealDB with the [`surreal db start`](/docs/reference/cli/db/commands/start.md) command, Docker, or SurrealDB Studio.

**CLI**

```bash
surreal db start --log debug --user root --password secret
```

**Docker**

```bash
docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest start -u root -p secret
```

## Create a new Bruno collection

Create a new collection by hand, or copy the following files into a new folder, for example `graphql-test`.

If you are creating your collection copying the following files, make sure to create `bruno.json` with the following content:

```json
{
  "version": "1",
  "name": "surrealdb graphql",
  "type": "collection",
  "ignore": [
    "node_modules",
    ".git"
  ]
}
```

Set authentication to basic, using your credentials from above (username: `root`, password: `secret`). Or save the following in a new file as `collection.bru`:

```text
auth {
  mode: basic
}

auth:basic {
  username: root
  password: secret
}
```

## Create a script to populate the DB

Save this file as `import.bru`. We are going to import this script using Bruno:

```text
meta {
  name: import
  type: http
  seq: 2
}

post {
  url: http://localhost:8000/import
  body: text
  auth: inherit
}

headers {
  Surreal-NS: test
  Surreal-DB: graphql
  Accept: application/json
}

body:text {
  DEFINE TABLE item SCHEMAFULL;
  DEFINE TABLE tag SCHEMAFULL;
  DEFINE TABLE container SCHEMAFULL;
  DEFINE TABLE space SCHEMAFULL;
  
  -- Enable GraphQL for the table
  DEFINE CONFIG GRAPHQL AUTO;
  
  -- Define some fields
  DEFINE FIELD name ON TABLE item TYPE string;
  DEFINE FIELD description ON TABLE item TYPE string;
  DEFINE FIELD space ON TABLE item TYPE record;
  DEFINE FIELD time ON TABLE item TYPE object;
  DEFINE FIELD time.createdAt ON TABLE item TYPE datetime;
  
  DEFINE FIELD tags ON TABLE item TYPE array<object>;
  DEFINE FIELD tags.*.name ON TABLE item TYPE string;
  DEFINE FIELD tags.*.color ON TABLE item TYPE string;
  
  DEFINE FIELD name ON TABLE tag TYPE string;
  DEFINE FIELD name ON TABLE container TYPE string;
  DEFINE FIELD name ON TABLE space TYPE string;
  
  DEFINE TABLE is_in TYPE RELATION IN item | container OUT container | space;
  DEFINE TABLE tagged TYPE RELATION IN item OUT tag;
  
  -- Create tags
  CREATE tag:electronics CONTENT { name: 'Electronics' };
  CREATE tag:tools CONTENT { name: 'Tools' };
  CREATE tag:cleaning CONTENT { name: 'Cleaning' };
  
  -- Create spaces
  CREATE space:home CONTENT { name: 'Home' };
  
  -- Create containers
  LET $c_living = CREATE container CONTENT { name: 'Living room' } RETURN id;
  LET $c_desk = CREATE container CONTENT { name: 'Work desk' } RETURN id;
  LET $c_box = CREATE container CONTENT { name: 'Box 1' } RETURN id;
  LET $c_box_2 = CREATE container CONTENT { name: 'Small box 2' } RETURN id;
  
  RELATE $c_box_2->is_in->$c_box SET time = { updatedAt: time::now() };
  RELATE $c_box->is_in->$c_desk SET time = { updatedAt: time::now() };
  RELATE $c_desk->is_in->$c_living SET time = { updatedAt: time::now() };
  RELATE $c_living->is_in->space:home SET time = { updatedAt: time::now() };
  
  -- Create an item
  LET $item = CREATE item CONTENT {
      name: 'Lens wipes',
      description: 'box of lens wipes',
      space: space:home,
      tags: [{name: "comsumable", color: "#FF0000"}, {name: "cleaning", color: "#0000FF"}],
      time: {
          createdAt: time::now()
      }
  } RETURN id;
  
  RELATE $item->is_in->$c_box SET time = { updatedAt: time::now() };
  RELATE $item->tagged->tag:cleaning;
  
  -- Create an item
  LET $item2 = CREATE item CONTENT {
      name: 'HDD',
      description: 'external hard drive samsung white',
      space: space:home,
      tags: [{name: "electronics", color: "#00FFFF"}],
      time: {
          createdAt: time::now()
      }
  } RETURN id;
  
  RELATE $item2->is_in->$c_box_2 SET time = { updatedAt: time::now() };
  RELATE $item2->tagged->tag:electronics;
  
}

settings {
  encodeUrl: true
  timeout: 0
}
```

## Create a script to query using GraphQL

Save this one as `query.graphql`

```text
meta {
  name: test
  type: graphql
  seq: 1
}

post {
  url: http://localhost:8000/graphql
  body: graphql
  auth: inherit
}

headers {
  Surreal-NS: test
  Surreal-DB: graphql
  Accept: application/json
}

body:graphql {
  {
    items(filter: { name: { ne: "HDD" } }) {  # -- example "not equals" filter
      name
      space {
        id
      }
    }
  }
}

settings {
  encodeUrl: true
  timeout: 0
}
```

## Now open and run in Bruno

Your collection folder should contain the following files:

- `bruno.json`
- `collection.bru`
- `import.bru`
- `query.bru`

Open the collection in Bruno, run the "import" request, and then "query".

You should then see a result like this:

```json
{
  "data": {
    "items": [
      {
        "name": "Lens wipes",
        "space": {
          "id": "space:home"
        }
      }
    ]
  }
}
```

## Troubleshooting

- if you see this error: `InvalidRequest(NotConfigured)`, make sure you have included this line in the import `DEFINE CONFIG GRAPHQL AUTO`.

---

Source: https://surrealdb.com/docs/learn/querying/graphql/via-http

# Via HTTP

In this section, you will explore querying SurrealDB using the GraphQL HTTP endpoint. The HTTP API is designed to be simple and intuitive, with any interface that provides a consistent way to interact with the database.

SurrealDB provides an HTTP API for interacting with the database programmatically. Its RESTful interface can be used for a wide range of database operations, from querying data to modifying records and managing database structures.

## Starting a new connection

Before you can start making queries, you need to start SurrealDB with the GraphQL module enabled. You can do this by starting a new instance of SurrealDB with the [`surreal db start` command](/docs/reference/cli/db/commands/start.md).

```bash
surreal db start --log debug --user root --password secret
```

In order to allow querying the created table using GraphQL, you will need to explicitly enable GraphQL using the [`DEFINE CONFIG`](/docs/reference/query-language/statements/define/config.md) statement. This will allow you to query the table using GraphQL on a per-database basis.

## `POST /sql`

To use the GraphQL API, you first need to enable it using the `DEFINE CONFIG` statement. This will allow you to query the table using GraphQL on a per-database basis.

To do this, you can send a `POST` request to the `/sql` endpoint with a RAW body containing the `DEFINE CONFIG` statement. For example:

```surrealql title="Enabling GraphQL"
DEFINE CONFIG GRAPHQL AUTO;
```

Here are three commands to define this configuration, along with some table and field definitions and sample data. They also assume a root user with the name `root` and the password `secret`.

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" \
  -d 'DEFINE TABLE person SCHEMAFULL; DEFINE FIELD name ON TABLE person TYPE string; DEFINE FIELD age ON TABLE person TYPE number;' \
  http://localhost:8000/sql

curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" \
  -d 'CREATE person:simon SET name = "Simon", age = 23; CREATE person:marcus SET name = "Marcus", age = 28;' \
  http://localhost:8000/sql

curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json" \
  -d 'DEFINE CONFIG GRAPHQL AUTO' http://localhost:8000/sql
```

## `POST /graphql`

To use the GraphQL API, you can send a `POST` request to the `/graphql` endpoint with a JSON body containing the GraphQL query via Postman or any other HTTP client. For example, to query the `person` table for all records, you can send the following request:

```json
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" \
  -d '{ "query": "query { persons { name } }" }' http://localhost:8000/graphql
```

```json title="Response"
{"data":{"persons":[{"name":"Marcus"},{"name":"Simon"}]}}
```

The GraphQL endpoint enables use of GraphQL queries to interact with your data.

### Headers
<table>
    <thead>
        <tr>
            <th colspan="2">Header</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>Authorization</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            Sets the root, namespace, database, or record authentication data
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>Accept</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the desired content-type of the response
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>surreal-ns</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the selected Namespace for queries
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>surreal-db</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the selected Database for queries
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```bash title="Request"
curl -X POST \
  -u "root:secret" \
  -H "Surreal-NS: main" \
  -H "Surreal-DB: main" \
  -H "Accept: application/json" \
  -d '{"query": "query { person(where: { age: { gt: 18 } }) { id name age } }"}' \
  http://localhost:8000/graphql
```

```json title="Response"
[
	{
		"time": "14.357166ms",
		"status": "OK",
		"result": [
			{
				"age": "23",
				"id": "person:simon"
				"name": "Simon",
			},
			{
				"age": "28",
				"id": "person:marcus"
				"name": "Marcus",
			},
		]
	}
]
```

---

Source: https://surrealdb.com/docs/learn/querying/graphql/via-studio

# Via SurrealDB Studio

In this section, you will explore querying SurrealDB using SurrealDB Studio.

The GraphQL query view in [SurrealDB Studio](https://studio.surrealdb.com/query) provides syntax highlighting, query validation, and real-time execution, with results displayed as the JSON structure returned by GraphQL.

## Getting started

Before you can start making queries, you need to start SurrealDB with the GraphQL module enabled. You can do this by starting a new instance of SurrealDB with the [`surreal db start`](/docs/reference/cli/db/commands/start.md) command.

```bash
surreal db start --log debug --user root --password secret
```

After starting the SurrealDB instance, you can navigate to SurrealDB Studio to start a new connection.

### Start a new connection

In the top left corner of the SurrealDB Studio, start a new connection. Ensure that the connection information is the same as the one you used to start the SurrealDB instance. In the example above we have set the user to `root` and the password to `secret`.

> [!IMPORTANT]
> Querying via GraphQL is not supported in the SurrealDB Studio sandbox.

Learn more about starting a connection in the [SurrealDB Studio documentation](/docs/explore/studio.md).

### Setting a namespace and database

Before you can start writing queries, you need to set the [namespace and database](/docs/learn/data-models/architecture.md#namespaces-and-databases) you want to use. For example, you can set the namespace to `test` and the database to `test`. This will set the namespace and database for the current connection.

Additionally, you can [start serving from SurrealDB Studio](/docs/explore/studio.md), which also enables GraphQL automatically, starting a server on `http://localhost:8000` by default for a root user with username and password `root`.

<img src="~/assets/img/image/surrealist/connection.png" alt="SurrealDB Studio connection settings" />

### Preparing your database

Next, use the [SurrealQL query editor](/docs/explore/studio.md) to create some data. For example, you can create a new `user` table with fields for `firstName`, `lastName`, and `email` and add a new user to the database.

In order to allow querying the created table using GraphQL, you will need to explicitly enable GraphQL using the [`DEFINE CONFIG`](/docs/reference/query-language/statements/define/config.md) statement. This will allow you to query the table using GraphQL on a per-database basis.

This must be followed by statements to explicitly define the resources to query. That is, you must use the [`DEFINE TABLE` statement](/docs/reference/query-language/statements/define/table.md) to define the table, and [`DEFINE FIELD` statement](/docs/reference/query-language/statements/define/field.md) to define the fields for the table. This is because GraphQL differs from SurrealDB itself in requiring resources to be defined before they can be used.

```surql title="Creating a user table"
DEFINE TABLE user SCHEMAFULL;

-- Enable GraphQL for the user table.
DEFINE CONFIG GRAPHQL AUTO;

-- Define some fields. Not strictly necessary for
-- SurrealDB itself, but required for GraphQL
DEFINE FIELD firstName ON TABLE user TYPE string;
DEFINE FIELD lastName ON TABLE user TYPE string;
DEFINE FIELD email ON TABLE user TYPE string
  ASSERT string::is_email($value);
DEFINE INDEX userEmailIndex ON TABLE user FIELDS email UNIQUE;

-- Create a new User
CREATE user CONTENT {
    firstName: 'Jon',
    lastName: 'Doe',
    email: 'Jon.Doe@surrealdb.com',
};
```

## Write your first GraphQL query

After you have created some data, you can start writing GraphQL queries. You can use the [SurrealDB Studio GraphQL editor](/docs/explore/studio.md) to write your GraphQL queries.

For example, to query the `user` table for all records, you can write the following GraphQL query:

```graphql
{
    users {
        firstName
        lastName
        email
    }
}
```

<img src="~/assets/img/image/surrealist/graphql-querying-fields.png" alt="SurrealDB Studio GraphQL query" />

And to get the user with the email "Jon.Doe@surrealdb.com", you can write the following GraphQL query:

```graphql
{
    users(filter: {email: {eq: "Jon.Doe@surrealdb.com"}}) {
        firstName
        lastName
    }
}
```

SurrealDB Studio will automatically validate the query and provide you with the results.

## Introspection

SurrealDB Studio also supports introspection with GraphQL. This means that you can query the database and SurrealDB Studio will automatically infer the type of the data you are querying. For example, if you query the `user` table for all records, SurrealDB Studio will automatically infer the type of the data to be `user`.

<img src="~/assets/img/image/surrealist/graphql-type-inference.png" alt="SurrealDB Studio GraphQL type inference" />

## Learn more

To learn more about the GraphQL view in SurrealDB Studio, check out the [SurrealDB Studio documentation](/docs/explore/studio.md).

---

Source: https://surrealdb.com/docs/learn/querying/performance/performance-best-practices

# Performance best practices

This guide outlines some key performance best practices for using SurrealDB. While SurrealDB offers powerful and flexible features to support you in meeting your desired performance standards, the use that you make of those features will ultimately determine whether or not you meet them.

To achieve the best performance from SurrealDB, there are a number of configuration options and runtime design choices to consider. The following is a non-exhaustive list of best practices to help you address common performance challenges and avoid frequent pitfalls.

For published results comparing storage engines and deployment modes, see the [SurrealDB benchmarks](https://surrealdb.com/benchmarks).

## SurrealDB architecture

While SurrealDB is a [multi-model database](/blog/what-are-multi-model-databases), at its core, SurrealDB stores data in documents on transactional key-value stores.

This means that SurrealDB is a general-purpose database optimised for a combination of various workloads such as operational, AI and real-time workloads.

While SurrealDB can perform well with real-time and advanced analytical workloads, its architecture is not a columnar one. As such, it is not optimised for large ad-hoc analytical queries in the same way as specialised columnar data warehouses.

SurrealDB is built using a layered approach, with compute separated from the storage. This allows you, if necessary, to scale up the compute and storage layers independently from each other.

Read more about [SurrealDB architecture](/docs/learn/data-models/architecture.md) and [deployment models](/docs/manage/self-hosted/deployment-models.md).

## Performing queries

### Selecting single records

Certain queries in SurrealDB can be more efficiently written in certain ways which ensure that full table scans or indexes are not necessary when executing the query.

In traditional SQL, the following query can be used to query for a particular row from a table:

```surql
SELECT *
FROM user
WHERE id = 19374837491;
```

In SurrealDB 3.0 and above, the query planner is able to identify this as a record ID scan if the `id` field after the `WHERE` clause is a record ID.

```surql
SELECT *
FROM user
WHERE id = user:19374837491;
```

However, using a `WHERE` clause will always perform a table scan in versions of SurrealDB before 3.0. If this is the case, just remove the `WHERE` clause and select directly from the record ID itself.

```surql
SELECT *
FROM user:19374837491;
```

### Selecting multiple records

In traditional SQL, the following queries can be used to query for getting particular rows from a table:

```surql
-- Selecting individual IDs
SELECT *
FROM user
WHERE id = 19374837491
   OR id = 12647931632;
```

```surql
-- Selecting a range of IDs
SELECT *
FROM user
WHERE id >= 12647931632
   AND id <= 19374837491;
```

However, currently in SurrealDB this query will perform a scan to find the record, although this is not necessary and you don't need to index the id field when using SurrealDB. Instead the following query can be used to select the specific record without needing to perform any scan:

```surql
-- Selecing indiviudal IDs
SELECT *
FROM user:19374837491, user:12647931632;
```

```surql
-- Selecting a range of IDs
SELECT *
FROM user:12647931632..=19374837491;
```

### Simplifying logic in `WHERE` clauses

If a `WHERE` clause cannot be avoided, performance can still be improved by optimising the portion after the `WHERE` clause. As a boolean check is the simplest possible operation, having a boolean field that can be used in a `WHERE` clause can significantly improve performance.

```surql
DEFINE FIELD data_length ON person VALUE random_data.len();
DEFINE FIELD is_short ON person VALUE random_data.len() < 10;

-- Fill up the database a bit with 10,000 records
CREATE |person:10000|
  SET random_data = rand::string(1000) RETURN NONE;
-- Add one outlier with short random_data
CREATE person:one SET random_data = "HI!" RETURN NONE;

-- Function call + compare operation: slowest
SELECT * FROM person WHERE random_data.len() < 10;
-- Compare operation: much faster
SELECT * FROM person WHERE data_length < 10;
-- Boolean check: even faster
SELECT * FROM person WHERE is_short;
-- Direct record access: almost instantaneous
SELECT * FROM person:one;
```

## Using indexes

SurrealDB has native built-in support for a number of different index types, without relying on external libraries or implementations.

With native support for indexes in the core database engine, SurrealDB uses indexes where possible within the SurrealQL query language, without pushing queries down to a separate indexing engine.

In addition, data is indexed in the same way for embedded systems, single-node database servers, and multi-node highly-available clusters, ensuring that the same indexing functionality is available regardless of the SurrealDB deployment model.

`UPDATE`, `UPSERT` and `DELETE` statements with a `WHERE` clause use indexes in the same way as `SELECT`, so the condition can go directly on the statement. Adding `EXPLAIN` shows whether an index was chosen:

```surql
DEFINE INDEX age ON user FIELDS age;
UPDATE user SET adult = false WHERE age < 18 EXPLAIN;
```

```surql title="Output"
[
	{
		detail: {
			plan: {
				direction: 'forward',
				from: { inclusive: false, value: NONE },
				index: 'age',
				to: { inclusive: false, value: 18 }
			},
			table: 'user'
		},
		operation: 'Iterate Index'
	},
	{
		detail: { type: 'Memory' },
		operation: 'Collector'
	}
]
```

> [!NOTE]
> Before SurrealDB 2.3.0, only `SELECT` statements used indexes. On those versions, wrap the condition in a subquery that selects only the `id` field, such as `UPDATE (SELECT id FROM user WHERE age < 18) SET adult = false`, so that the `SELECT` can use the index.

### Index strategies explained

When using `SELECT`, `UPDATE`, `UPSERT` or `DELETE`, SurrealDB uses a query planner whose role is to identify if it can use the index to speed the execution of the query.

Without indexes, SurrealDB will operate a `SELECT` query on a table by using the table iterator. It mainly scans every record of a given table. If there is a condition (`WHERE ...`), an ordering (`ORDER BY ...`), or an aggregation (`GROUP BY ...`), it will load the value in memory and execute the operation. This process is commonly called a "table full scan".

```surql
SELECT *
FROM user
WHERE age < 18
EXPLAIN;
```

```surql title="Output"
[
	{
		detail: {
			table: 'user'
		},
		operation: 'Iterate Table'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	}
]
```

Under certain conditions, if an index exists, and the condition or ordering involves exclusively fields that are indexed, the query planner will suggest an execution plan that involves one or multiple indexes to achieve these potential optimisations:

- Only collect records that match the condition(s), as opposed to performing a table full scan.
- As the index already stores the records in order, the scanning collects the records pre-ordered, sparing an additional ordering phase.

```surql
DEFINE INDEX idx_user_age ON user FIELDS age;

SELECT age
FROM user
WHERE age > 18
EXPLAIN;
```

```surql title="Output"
[
	{
		detail: {
			plan: {
				from: {
					inclusive: false,
					value: 18
				},
				index: 'idx_user_age',
				to: {
					inclusive: false,
					value: NONE
				}
			},
			table: 'user'
		},
		operation: 'Iterate Index'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	}
]
```

If there are several clauses separated with `OR` operators, the query planner may do several index-based iterations:

```surql
SELECT age
FROM user
WHERE age < 7
   OR age > 77
EXPLAIN;
```

```surql title="Output"
[
	{
		detail: {
			plan: {
				from: {
					inclusive: false,
					value: NONE
				},
				index: 'idx_user_age',
				to: {
					inclusive: false,
					value: 7
				}
			},
			table: 'user'
		},
		operation: 'Iterate Index'
	},
	{
		detail: {
			plan: {
				from: {
					inclusive: false,
					value: 77
				},
				index: 'idx_user_age',
				to: {
					inclusive: false,
					value: NONE
				}
			},
			table: 'user'
		},
		operation: 'Iterate Index'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	}
]
```

### Use `UPSERT` to take advantage of unique indexes

`UPSERT` statements have a unique performance advantage when paired with a unique index.

A unique index on its own is used to prevent more than one record from containing the same data, such as a name or email address.

```surql
DEFINE INDEX email_index ON user FIELDS email UNIQUE;

CREATE user SET email = "bob@bob.com";
CREATE user SET email = "bob@bob.com";
```

```surql title="Output"
"Database index `email_index` already contains 'bob@bob.com',
  with record `user:g7s070gqvh3lj7fdp26w`"
```

An `UPSERT` statement works like a `CREATE` statement in this case as well, except that if the value for `email` is already present, it will modify the existing record instead of creating a new one. An `UPSERT` will only fail in this case if a user attempts to upsert to a certain record ID (like `user:bob` instead of just the `user` table) when another record holds this value.

The key point here is that in either case, `UPSERT` is using the index to find the record instead of a table scan.

```surql
DEFINE INDEX email_index ON user FIELDS email UNIQUE;

CREATE user SET email = "bob@bob.com";

-- Checks index, finds existing user via email "bob@bob.com", modifies it
UPSERT user SET email = "bob@bob.com", name = "Bob Bobson";

-- Checks index, fails as a new `user:bob` cannot be created with the same email
UPSERT user:bob SET email = "bob@bob.com", name = "Bob Bobson";
```

An `UPDATE` with a `WHERE` clause on the indexed field also uses the unique index, so both statements below go directly to Bob's record without a table scan. The difference is what happens when no record matches: `UPDATE` changes nothing, while `UPSERT` creates a new record.

```surql
DEFINE INDEX email_index ON user FIELDS email UNIQUE;

-- Create 50,000 users to fill up the database
CREATE |user:50000| RETURN NONE;

-- Create Bob
CREATE user SET email = "bob@bob.com";

-- Uses email_index to find the record
UPDATE user SET name = "Bob Bobson" WHERE email = "bob@bob.com";

-- Also uses email_index, and creates the record if no user has this email
UPSERT user SET name = "Bob Bobson", email = "bob@bob.com";
```

> [!NOTE]
> Before SurrealDB 2.3.0, `UPDATE ... WHERE` did not use indexes and performed a full table scan. On those versions, `UPSERT` is the faster way to modify a single record identified by a unique field.

### Index lookup on remote fields

SurrealDB document record IDs store both the table name and the record identifier. This design provides a straightforward and consistent way to reference records across the database. It also makes it possible to filter a table based on conditions that relate to a referenced table.

Here is a concrete example, where the statement `SELECT * FROM access WHERE user.role = 'admin'` will retrieve records from the `access` table for which the referenced record in the `user` table has the `role` field set to 'admin'.

Consider the following example:

```surql
DEFINE FIELD user ON TABLE access TYPE record<user>;

CREATE user:1 SET name = 'foo', role = 'admin';
CREATE user:2 SET name = 'bar', role = 'admin';

CREATE access:A SET user = user:1;
CREATE access:B SET user = user:2;

SELECT *
FROM access
WHERE user.role = 'admin'
```

The query retrieves records from the `access` table whose associated record in the `user` table has the `role` field set to 'admin'.

```surql title="Output"
[
	{
		id: access:A,
		user: user:1
	},
	{
		id: access:B,
		user: user:2
	}
]
```

To optimise this query, you can create indexes on both the `user.role` field and the `access.user` field. With these indexes, the query planner can use an index-based join strategy:

```surql
DEFINE INDEX idx_user_role ON TABLE user FIELDS role;
DEFINE INDEX idx_access_user ON TABLE access FIELDS user;

SELECT *
FROM access
WHERE user.role = 'admin' 
EXPLAIN;
```

```surql title="Output"
[
	{
		detail: {
			plan: {
				index: 'idx_access_user',
				joins: [
					{
						index: 'idx_user_role',
						operator: '=',
						value: 'admin'
					}
				],
				operator: 'join'
			},
			table: 'access'
		},
		operation: 'Iterate Index'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	}
]
```

## Running SurrealDB

### Using SurrealDB Cloud

The easiest way to deploy and scale up SurrealDB is by using SurrealDB Cloud, which runs, maintains and scales the database for you.

Read more about running SurrealDB [using SurrealDB Cloud ](/cloud).

### Running SurrealDB as a server

When starting the SurrealDB server, use the correct configuration options and settings. For production environments or for performance benchmarking, the `--log` command-line argument or the `SURREAL_LOG` environment variable should be set to `info` (the default option when not specified), `warn`, or `error`.

Other log verbosity levels (such as `debug`, `trace`, or `full`) are only recommended for use in debugging, testing, or development scenarios. This is because verbosity of the log level impacts the performance by increasing the amount of information being logged for each single operation.

The same applies for the SurrealDB Docker container, in which the `--log` argument should be omitted or specifically set to a log verbosity level that does not impact performance.

```sh
docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest start --log info rocksdb://path/to/mydatabase
```

Read more about running SurrealDB as a [single-node server](/docs/running/file-backed.md) or [multi-node cluster](/docs/running/multi-node.md).

For performance comparisons between storage engines and deployment modes, see the [SurrealDB benchmarks](https://surrealdb.com/benchmarks).

To measure performance on your own hardware and data sizes, use [crud-bench](https://github.com/surrealdb/crud-bench), the open-source tool that SurrealDB uses to test its own performance. It runs create, read, update, scan and delete workloads with a configurable number of records and concurrent clients, against an embedded datastore, a local server in Docker, or a remote deployment, and can run the same workloads against other databases for comparison.

Additionally, ensure that the `rocksdb` storage engine is used to store data.

```sh
surreal db start --log info rocksdb://path/to/mydatabase
```

#### CPU cores and concurrent queries

The server runs queries on a pool of worker threads. By default the pool has one thread for each CPU core, with a minimum of four, and the [`SURREAL_RUNTIME_WORKER_THREADS`](/docs/reference/cli/db/environment-variables.md) environment variable sets a different number.

Most of the work of one query runs on one thread, so one query at a time uses about one CPU core, however many cores the machine has. The server uses more cores when it receives more queries at the same time, from one connection or from many.

Some consequences of this:

- **Low CPU use under light load is expected.** A server that receives one query at a time does not use the other cores. Sending independent queries concurrently, for example from several connections or several tasks in the same client, is what uses them.
- **Queries beyond the number of cores wait.** When more queries arrive together than there are cores to run them, each one takes longer, because they share the same cores. A client that sends 20 large queries at once to a 4-core server receives the answers over a longer period than it takes to run one of them.
- **A container limit caps the server.** In Docker or Kubernetes, a CPU limit on the container applies whatever the number of worker threads is. A pod limited to one CPU never uses more than one core.

### Running SurrealDB embedded in Rust

In Rust, the `--release` flag is used to ensure that a build is optimised for performance as opposed to compilation speed. However, the SurrealDB source code also contains a few additional flags when the `--release` flag is passed in as seen below. As this configuration will not be present by default inside the `Cargo.toml` for your own project when adding the `surrealdb` dependency, be sure to add it if performance is a priority.

```toml
[profile.release]
codegen-units = 1
lto = true
opt-level = 3
panic = 'abort'
strip = true
```

In addition, using the correct memory allocator can greatly improve the performance of the database core engine when running SurrealDB as an embedded database within Rust. Using an optimised asynchronous runtime configuration can also help speed up concurrent queries and increase database throughput.

In your project's `Cargo.toml` file, ensure that the `allocator` feature is enabled on the `surrealdb` dependency:

```toml
surrealdb = { version = "3", features = ["allocator", "kv-mem", "kv-surrealkv", "kv-rocksdb", "protocol-http", "protocol-ws", "rustls"] }
```

When running SurrealDB within your Rust code, ensure that the asynchronous runtime is configured correctly, making use of multiple threads, an increased stack size, and an optimised number of threads:

```toml
tokio = { version = "1.49.0", features = ["sync", "rt-multi-thread"] }
```

```rs
fn main() {
	tokio::runtime::Builder::new_multi_thread()
    .enable_all()
    .thread_stack_size(10 * 1024 * 1024) // 10MiB
    .build()
    .unwrap()
    .block_on(async {
      // Your application code
    })
}
```

Read more about [running SurrealDB embedded in Rust.](/docs/build/embedding/by-language/rust.md)

### Running SurrealDB embedded in Tauri

When running SurrealDB as an embedded database within Rust, default options of Tauri can make SurrealDB run slower, as it processes and outputs the database information logs. Configuring Tauri correctly can result in much improved performance with the core database engine and any queries which are run on SurrealDB.

When building a desktop application with Tauri, ensure that the Tauri plugin log is disabled by configuring the `tauri.conf.json` file:

```json
{
	"plugins": {
		"logger": {
			"enabled": false
			}
	}
}
```

Alternatively you can disable logs at compile time when building your Tauri app:

**Bash**

```bash
TAURI_LOG_LEVEL=off cargo tauri build
```

**PowerShell**

```powershell
$env:TAURI_LOG_LEVEL = "off"
cargo tauri build
```

---

Source: https://surrealdb.com/docs/learn/querying/real-time/changefeeds

# Changefeeds

Replaying table changes over time with changefeeds and SHOW CHANGES - useful for sync, pipelines, and external systems.

Changefeeds are how you ask SurrealDB for a history of writes to a table (or database) so you can replay them elsewhere: search indexes, warehouses, cache warmers, or any system that needs to catch up after downtime.

You define how long changes are retained when you set up the feed by adding a duration like `3d` or `100h`. Changes are then read with the [`SHOW`](/docs/reference/query-language/statements/show.md) statement.

## What you need first

Before `SHOW CHANGES` returns anything useful, the table or database must opt in with a `CHANGEFEED` definition (duration, scope). Changefeeds are usually defined using a `DEFINE TABLE` statement, but can be defined on the entire database if desired via `DEFINE DATABASE`.

## Replaying from a point in time or a versionstamp

A typical flow is:

1. Note when you last processed changes (a datetime) or where you stopped (a versionstamp).
2. Ask for everything since that marker, optionally with a limit so you page through large backlogs.

The [`SHOW` statement reference](/docs/reference/query-language/statements/show.md) spells out the full syntax. In practice you are choosing: “give me the next batch of mutations after this cursor.”

```surql
-- Define the changefeed and its duration
DEFINE TABLE reading CHANGEFEED 3d;

-- Create some records in the reading table
CREATE reading SET story = "Once upon a time";
CREATE reading SET story = "there was a database";

-- Replay changes to the reading table since a date
SHOW CHANGES FOR TABLE reading SINCE d"2025-09-07T01:23:52Z" LIMIT 10;
-- Replay changes to the reading table since a versionstamp
SHOW CHANGES FOR TABLE reading SINCE 1 LIMIT 10;
```

If the datetime is after the changefeed was created and lines up with your retention window, you get a list of entries, each with `changes` and a `versionstamp` (where you are in the log). How each mutation is encoded depends on your [`DEFINE TABLE ... CHANGEFEED`](/docs/reference/query-language/statements/define/table.md#example-usage) options; when differences are stored (`INCLUDE ORIGINAL`), an update to an existing record carries a **reverse diff** from the current state to the state immediately before that write. The [`SHOW`](/docs/reference/query-language/statements/show.md) reference covers this in full. The following shape is representative:

```surql title="Output"
[
	{
		changes: [
			{
				define_table: {
					changefeed: {
						expiry: 3d,
						original: false
					},
					drop: false,
					id: 0,
					kind: {
						kind: 'ANY'
					},
					name: 'reading',
					permissions: {
						create: false,
						delete: false,
						select: false,
						update: false
					},
					schemafull: false
				}
			}
		],
		versionstamp: 116372747574247424
	},
	{
		changes: [
			{
				update: {
					id: reading:1up24usipwi7wjs4nbgk,
					story: 'Once upon a time'
				}
			}
		],
		versionstamp: 116372747574247425
	},
	{
		changes: [
			{
				update: {
					id: reading:6q3845opi3nikxr7jvgg,
					story: 'there was a database'
				}
			}
		],
		versionstamp: 116372747574312960
	}
]
```
## Copying changes to another instance

A change feed can keep a second SurrealDB instance up to date with the first. Each round reads the changes from the source with `SHOW CHANGES`, and applies them to the target in one transaction, together with the versionstamp to continue from next time. An `update` entry holds the whole record after the write, so `UPSERT` with that record recreates it on the target, and a `delete` entry holds the ID of the removed record.

Keeping the versionstamp in a record on the target, here `sync_state:reading`, means it changes in the same transaction as the data. If a round fails partway, neither moves, and the next round starts from the same place.

A program has to run the rounds, because each one connects to two instances, and `SINCE` only accepts a literal value. The program reads the versionstamp from the target and writes it into the text of the `SHOW CHANGES` query, rather than passing it as a parameter.

**SurrealQL**

```surql
-- 1. On the target: where to continue from
sync_state:reading.since ?? 0;

-- 2. On the source: the changes from that versionstamp on.
-- SINCE includes the entry with this versionstamp itself.
SHOW CHANGES FOR TABLE reading SINCE 117368726935830530 LIMIT 1000;

-- 3. On the target: apply the changes and store the next
-- versionstamp, which is the last one returned plus 1
BEGIN;
FOR $change IN $changes {
	IF $change.update {
		UPSERT $change.update.id CONTENT $change.update;
	} ELSE IF $change.delete {
		DELETE $change.delete.id;
	};
};
UPSERT sync_state:reading SET since = $next;
COMMIT;
```

**Python**

```python
from surrealdb import AsyncSurreal

APPLY = """
BEGIN;
FOR $change IN $changes {
	IF $change.update {
		UPSERT $change.update.id CONTENT $change.update;
	} ELSE IF $change.delete {
		DELETE $change.delete.id;
	};
};
UPSERT sync_state:reading SET since = $next;
COMMIT;
"""

async def sync(source, target):
    since = await target.query("sync_state:reading.since ?? 0")
    batch = await source.query(
        f"SHOW CHANGES FOR TABLE reading SINCE {since} LIMIT 1000"
    )
    if not batch:
        return
    # Keep only record changes, and leave out changes to the table definition
    changes = [
        change
        for entry in batch
        for change in entry["changes"]
        if "update" in change or "delete" in change
    ]
    await target.query(
        APPLY, {"changes": changes, "next": batch[-1]["versionstamp"] + 1}
    )
```

Call `sync()` in a loop, with one connection to each instance.

Entries that change the table definition, such as `define_table`, are not applied, so define the table on the target separately.

The source keeps changes only for the duration given in its `CHANGEFEED` clause. If the program stops for longer than that, the changes it missed are gone, and the target has to be copied again with an [export](/docs/reference/cli/db/commands/export.md) before the program starts again.

---

Source: https://surrealdb.com/docs/learn/querying/real-time/live-queries

# Live queries

Subscribing to inserts, updates, and deletes with LIVE SELECT so clients stay in sync over a persistent connection.

Live queries are SurrealDB's way to push changes to a client. Running a `LIVE SELECT` opens a subscription that sends notifications whenever matching records appear, change, or disappear, without needing to directly poll the database to do so.

That fits interactive UIs, dashboards, and small collaboration features. For replaying history or catching up a batch pipeline, use [changefeeds](/docs/learn/querying/real-time/changefeeds.md) and `SHOW CHANGES` instead.

The full grammar and edge cases live under [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md) in the reference. Below is how to think about subscriptions and what to watch for in production.

## Starting a subscription

With `LIVE SELECT *`, every notification carries the full record: the new state for a create or an update, and the record as it was before deletion for a delete. The table must already exist, for example through a `DEFINE TABLE` statement or an earlier write, or the statement fails with `The table 'person' does not exist`. You receive a UUID as soon as the live query is registered. This UUID can later be passed in to a [`KILL`](/docs/reference/query-language/statements/kill.md) statement when you want to stop.

```surql
LIVE SELECT * FROM person;
```

```surql title="Output"
u'b1f1d115-ad0f-460d-8cbf-dbc7ce48851c'
```

Message layout on the wire is described in the [live query / WebSocket protocol](/docs/reference/rest-api/rpc-protocol.md#live) section.

## DIFF mode

If you prefer patches instead of whole documents on update, use `DIFF`. Updates arrive as [JSON Patch](https://jsonpatch.com/)-style arrays, which can be smaller and easier to merge on the client when records are large.

```surql
LIVE SELECT DIFF FROM person;
```

```surql title="Output"
u'b87cbb0d-ca15-4f0a-8f86-caa680672aa5'
```

## Filtering with WHERE

You can narrow the subscription the same way you would a normal `SELECT`: only records that match your predicate participate.

```surql
LIVE SELECT * FROM person WHERE age > 18;
```

The filter is not a query that runs again over the table. Each time a write changes a record, SurrealDB checks that one record against the live query: the `WHERE` clause decides whether the subscriber is notified, and the projection shapes what it receives. While it does so, these parameters describe the change:

| Parameter | Value |
| --- | --- |
| `$event` | `'CREATE'`, `'UPDATE'` or `'DELETE'` |
| `$before` | The record before the write, or `NONE` for a created record |
| `$after`, `$value` | The record after the write, or `NONE` for a deleted record |
| `$auth`, `$session`, `$token`, `$access` | The subscriber's own session, not the session that made the write |

A filter on `$event` subscribes to some kinds of change only, such as new records:

```surql
LIVE SELECT * FROM person WHERE $event = 'CREATE';
```

A filter on `$before` also notifies when a record stops matching. The following live query notifies when a task becomes open, and again when an open task changes to another status:

```surql
LIVE SELECT * FROM task WHERE status = 'open' OR $before.status = 'open';
```

A deleted record is checked as it was before the delete, so a filter on its fields still matches it. These are the same parameters that a [`DEFINE EVENT`](/docs/reference/query-language/statements/define/event.md) statement reads, so a condition written for an event usually works as a live query filter as well. The [`LIVE SELECT` reference](/docs/reference/query-language/statements/live-select.md#filter-by-event) has more examples.

## Consistency and security

Notifications reflect committed work, so a live event will not take place if a transaction is rolled back.

Under heavy concurrency, the system makes a best effort to preserve sensible ordering, but you should not assume a total order across all writers that matches commit order in every edge case.

### Field permissions

A notification carries the record that changed, and each subscriber gets its own copy of it, reduced to the fields that subscriber may select. `$value`, `$after` and `$before` are bound to that same reduced copy, so none of them holds a field the notification itself would not carry.

This matters because a live query's projection and `WHERE` clause are both written by the subscriber, and either can read one of those parameters. A field the subscriber cannot select reads as `NONE` in both:

```surql
-- Reads a restricted field into the notification, under a name of the caller's choosing
LIVE SELECT $after.hidden AS leaked FROM test;

-- Reads nothing into the notification, but decides whether one is sent at all
LIVE SELECT * FROM test WHERE $after.hidden = 1;
```

The two would leak differently, which is why the parameters are bound to the reduced copy rather than the projection being restricted. A projection carries the value out, and renaming it with `AS` would take it past the check that drops restricted fields by name. A `WHERE` clause carries nothing out, so the record stays clean, and the subscriber would instead learn a value it cannot select from which changes produced a notification and which did not.

On the delete side `$before` covers both, since it is the only parameter still holding the record as it was.

## Parameters in live queries

From v3.0.0 onwards you can use session parameters in live queries, such as by binding names with `LET` before you open the subscription.

```surql
LET $table = 'measurement';
LET $location = 'Tallinn';
LIVE SELECT * FROM type::table($table) WHERE location == $location;
```

## Where to read more

* [`LIVE SELECT` reference](/docs/reference/query-language/statements/live-select.md)
* [Changefeeds](/docs/learn/querying/real-time/changefeeds.md) - replay and `SHOW CHANGES`
* [Real-time best practices](/docs/learn/querying/real-time/real-time-best-practices.md) - wider event-driven patterns

---

Source: https://surrealdb.com/docs/learn/querying/real-time/real-time-best-practices

# Real-time best practices

This guide outlines some best practices for using SurrealDB in an event-driven and real-time manner.

This page covers patterns for using SurrealDB when query time matters, when the database needs to react to changes on its own, or when you want to query aggregated data that is updated automatically over time.

## Temporal querying and record ranges

Temporal querying is generally done to answer two questions: what/where, and when? For example:

* What is the weather like in London at the timestamp `2026-01-28T01:02:56Z`?
* What is the weather like in London between the timestamps `2026-01-28T00:00:00Z` (28 January 2026) and `2026-01-29T00:00:00Z` (the next day)?

Databases that store this sort of data tend to be fairly massive. Data on weather, traffic, user activity, and anything else that holds one record per event at a certain point in time very quickly builds up.

SurrealQL has a built-in method to keep querying time down in cases like these: a complex record ID made out of an array.

```surql
CREATE weather:["London", time::now()]      SET temperature = 9.0;
CREATE weather:["London", time::now() + 1d] SET temperature = 8.0;
```

To see what makes an array special in a case like this, let's compare it with a more general approach that holds the time and location inside regular fields instead of the ID itself. We'll call this table `weather2` to tell the two apart.

```surql
CREATE weather2 SET 
    location = "London",
    at = time::now(),
    temperature = 9.0;

CREATE weather2 SET 
    location = "London",
    at = time::now() + 1d,
    temperature = 8.0;
```

To see all the data for London between yesterday and tomorrow, you would add a number of `WHERE` clauses.

```surql
SELECT * FROM weather2
WHERE 
    location = "London" AND 
    at > time::now() - 1d AND 
    at < time::now() + 1d;
```

The query works just fine, but let's now add the `EXPLAIN` clause to the end of it to see how the operation was performed.

```surql
[
	{
		detail: {
			direction: 'forward',
			table: 'weather2'
		},
		operation: 'Iterate Table'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	}
]
```

The 'Iterate Table' part here means that the operation iterated through the entire table to find matching records. This is fine to a certain extent, but becomes less efficient as other data for times and locations begins to build up.

To speed this up, an index can be added.

```surql
DEFINE INDEX weather ON weather2 FIELDS location, at;
```

With the index added, a query followed by `EXPLAIN` will show that we now iterated over the index, which is much more efficient.

```surql
[
	{
		detail: {
			plan: {
				index: 'weather',
				prefix: [
					'London'
				],
				ranges: [
					{
						operator: '>',
						value: d'2026-01-27T02:02:18.367Z'
					},
					{
						operator: '<',
						value: d'2026-01-29T02:02:18.367Z'
					}
				]
			},
			table: 'weather2'
		},
		operation: 'Iterate Index'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	}
]
```

As you can see, there is nothing wrong with using an index - and this may very well fit your own use case.

But let's now compare it to the approach that uses a complex record ID.

```surql
CREATE weather:["London", time::now()] SET temperature = 9.0;
CREATE weather:["London", time::now() + 1d] SET temperature = 8.0;
```

Technically, you could use the same query as before with a `WHERE` clause on each of the parts of the array-based ID. But that would result in another full table iteration.

```surql
SELECT * FROM weather
WHERE 
    id[0] = "London" AND 
    id[1] > time::now() - 1d AND 
    id[1] < time::now() + 1d;
```

Instead, we can query over a range of IDs: all the IDs that fall in between the starting point `["London", time::now() - 1d]` and the end point `["London", time::now() + 1d]`.

```surql
SELECT * FROM weather:["London", time::now() - 1d]..=["London", time::now() + 1d];
```

This is what is known as a record range query, or a table partition scan. It works by querying over just the fraction of record IDs that fall within this range, instead of every record in the table. And you don't need to define a separate index for it because a record ID is a direct pointer to the data itself.

If you imagine a database that holds thousands of weather observations over time for thousands of cities, the space between `weather:["London", time::now() - 1d]` and `["London", time::now() + 1d]` is just a very tiny slice! Querying on a partition this small is like going into a large bookstore knowing that the books you want are all in section C7 as opposed to walking through the entire place looking at each book along the way.

We can demonstrate the difference in performance by adding 100,000 random records using both formats over a period of time between now and one year ago.

To make the output easier to read, we'll do the following. First we'll put the two queries creating the records inside their own scope, and use the [value::chain()](/docs/reference/query-language/functions/database-functions/value.md#chain) function to grab the output, ignore it, and turn it into a string showing what operation has just been performed. Then we'll do the same for a regular `SELECT` query over the `weather2` data, compared to the record range query for the `weather` data. If you need the query to stop when a step does not look right, use [`.expect()`](/docs/reference/query-language/functions/database-functions/value.md#valueexpect) on that value instead of `.chain()` to return the original value when your closure returns `true`, otherwise failing with an error.

```surql
{
    -- 100,000 weather records
    FOR $_ IN 0..100000 {
    CREATE weather:["London", time::now() - rand::duration(0ns, 1y)] SET temperature = 9.0;
    };

    -- 100,000 weather2 records
    FOR $_ IN 0..100000 {
        CREATE weather2 SET 
        location = "London",
        at = time::now() - rand::duration(0ns, 1y),
        temperature = 9.0;
    };
}.chain(|$_| "Sample data added!");

"Regular select" + (SELECT * FROM weather2
WHERE 
    location = "London" AND 
    at > time::now() - 1d AND 
    at < time::now() + 1d).chain(|$_| "");

"Record range select" + (SELECT * FROM weather:["London", time::now() - 1d]..=["London", time::now() + 1d]).chain(|$_| "");
```

You should see a result showing that the record range is over a hundred times faster, which makes sense as it is only iterating over a surface area about 1/365th the size of the one that uses a full table scan.

## ULIDs and UUIDs

The standard record ID for SurrealDB is twenty characters long and composed of lowercase letters and numbers.

```surql
qrfz62eovom9f6p9j0fk
```

If you want a record ID with temporal information you might want to try using a datetime, but datetimes can't be used as IDs in SurrealQL.

```surql
CREATE weather SET id = time::now(); -- Won't work
```

Technically you can work around this by setting the ID to a stringified datetime, or an array with a single datetime.

```surql
CREATE weather SET id = <string>time::now();
CREATE weather:[time::now()];
```

However, when a record ID is composed of only a datetime, there is a very slight chance that the ID won't be unique. Though datetimes have nanosecond precision, you might be using SurrealDB on a system that rounds its datetimes to the millisecond or microsecond and creating multiple records at the same time may result in an error from two records having the same ID.

Another option is to set the ID of the record to be a ULID or a UUID. Both of these contain the datetime at which they were created, but are always unique and have no chance of collision.

```surql
LET $now = time::now();
CREATE weather:uuid() SET location = "London";
CREATE weather:ulid() SET location = "London";
```

A ULID and UUID can be created from a datetime, and both ULIDs and UUIDs can be turned back into one as well.

```surql
LET $uuid = rand::uuid(d'1997-08-29');
time::from_uuid($uuid);
//- d'1997-08-29T00:00:00Z'

LET $ulid = rand::ulid(d'1997-08-29');
time::from_ulid($ulid);
//- d'1997-08-29T00:00:00Z'
```

However, because datetimes have nanosecond precision but ULIDs and UUIDs have millisecond precision, a roundtrip from datetime to ULID/UUID and back will not be exactly the same as the original datetime.

To remove the precision from the original datetime, you can use the [`time::floor()`](/docs/reference/query-language/functions/database-functions/time.md#timefloor) function.

```surql
LET $now = time::now();
LET $ulid = rand::ulid($now);
LET $now_again = time::from_ulid($ulid);
$now == $now_again;                  -- false
time::floor($now, 1ms) = $now_again; -- true
```

## Live queries

If you don't want to make a query every time you want to see the latest updates to a table, you can use a [live query](/docs/reference/query-language/statements/live-select.md) instead.

Since results for live queries show up the moment a record is created or updated, you don't necessarily need a complex ID - though either way works.

```surql
-- Just used for live queries? WHERE location = "London" is fine
LIVE SELECT * FROM weather WHERE location = "London";
CREATE weather SET location = "London", temperature = 9.0;

-- Querying 'weather' manually too? Probably opt for an array-based ID
LIVE SELECT * FROM weather WHERE id[0] = "London";
CREATE weather:["London", time::now()] SET temperature = 9.0;
```

A `LIVE SELECT` will always return a UUID, like this one.

```surql
u'20372f84-714c-412b-b259-c8703574d4f1'
```

If making a live query inside SurrealDB Studio, you'll also see a notification asking you if you would like to move to live mode to see the changes to a table as they come in. After clicking on this, you can open up another window to make queries (or use the CLI, an SDK, or anything else) and watch the events as they come in.

Live query results can be listened for via SDKs as well. Here is one example showing how to listen for results on the `weather` table using the Rust SDK.

```rust
use futures::StreamExt;
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::{RecordId, SurrealValue, ToSql};

#[derive(Debug, SurrealValue)]
struct Weather {
    id: RecordId,
    temperature: f64,
    location: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    let mut weather = db.select("weather").live().await?;

    while let Some(result) = weather.next().await {
        println!("Got something! {result:?}");
        match result {
            Ok(data) => {
                let weather: Weather = data.data;
                println!("{}", weather.into_value().to_sql());
            }
            Err(error) => eprintln!("{error}"),
        }
    }

    Ok(())
}
```

A live query can also be limited to some kinds of change, such as `LIVE SELECT * FROM reading WHERE $event = 'CREATE'`, which filters on the server instead of in every client. [Filtering with WHERE](/docs/learn/querying/real-time/live-queries.md#filtering-with-where) lists the parameters a live query can filter on.

## Defining events

Defining events is a bit similar to live queries, except that this time we are able to have the database automatically respond on its own instead of requiring an external listener to take care of it. If everything that you need to respond to an event can be done at the database level, then defining an event is the best way to handle it.

Here is one example of an event that creates an `alert` whenever a weather condition is set to critical, and also uses an [http function](/docs/reference/query-language/functions/database-functions/http.md) to let an external service know about it.

```surql
DEFINE EVENT alert ON weather WHEN severity = "critical" THEN {
    LET $alert = CREATE ONLY alert SET 
		at = time::now(), 
		body = "Alert! " + $input.conditions + " in " + $input.location
	RETURN VALUE body;
    http::post('https://jsonplaceholder.typicode.com/posts/', {
        body: $alert
    });
};

CREATE weather:uuid() SET location = "London", severity = "critical", conditions = "Too many tourists";

SELECT * FROM alert;
```

Note that you will need to use the [--allow-net](/docs/learn/security/authorization/capabilities.md#network) flag when starting up a SurrealDB instance to allow functions like `http::post()` to work, as they are disabled by default.

### When and how not to use an event

Generally it is best to keep event logic within a single event that does not itself lead to another event. For an extreme example, take the following event that creates a record that triggers another event, which itself triggers the first event.

```surql
DEFINE EVENT goes_forever ON weather THEN {
    CREATE other;
};

DEFINE EVENT also_goes_forever ON other THEN {
    CREATE weather;
};

CREATE weather;
```

Doing so will not cause the database to freeze, but instead will quickly lead to a maximum computation depth after which the query will fail. This is thanks to the fact that every operation in SurrealDB is done inside its own transaction. Since each event triggered by the event before is part of the original transaction, the entire operation is cancelled and rolled back.

Note that the error output will show the chain of events at each depth before the limit was reached.

> 'Error while processing event goes_forever: Error while processing event also_goes_forever: Error while processing event goes_forever: Error while processing event also_goes_forever: Error while processing event goes_forever: Error while processing event also_goes_forever: Error while processing event goes_forever: Error while processing event also_goes_forever: Error while processing event goes_forever: Error while processing event also_goes_forever: Error while processing event goes_forever: Error while processing event also_goes_forever: Error while processing event goes_forever: Error while processing event also_goes_forever: Error while processing event goes_forever: Error while processing event also_goes_forever: Error while processing event goes_forever: Error while processing event also_goes_forever: Error while processing event goes_forever: Error while processing event also_goes_forever: Reached excessive computation depth due to functions, subqueries, or computed values'

If you find yourself using an event to update an aggregate like in the example below, a table view - introduced in the next section - might be what you are looking for.

```surql
CREATE sum SET date = "2026-01-29", amount = 0;

DEFINE EVENT update_sum ON purchase WHEN $event = "CREATE" THEN {
    UPSERT sum SET date = $after.date, amount += $after.amount WHERE date = $after.date;
};

CREATE purchase SET amount = 100, date = "2026-01-29";
CREATE purchase SET amount = 200, date = "2026-01-29";
CREATE purchase SET amount = 200, date = "2026-01-30";

SELECT * FROM sum;

-- Output:
[
	{
		amount: 300,
		date: '2026-01-29',
		id: sum:m4qv5iqpr9lfmpe0sgqh
	},
	{
		amount: 200,
		date: '2026-01-30',
		id: sum:vc7dxvrj5mdmm75bbqs5
	}
]
```

## Table views

Sometimes you might not want either a real-time query or an instantaneous reaction to an event, but prefer instead to have an aggregate of all the data in a certain table whenever you need to know it.

This can be done by defining a separate table as a `SELECT` expression preceded by the `AS` clause. The table that it draws from can be a regular table, like in the `purchase` example above. Alternatively, it can be followed by the `DROP` keyword if you only want to use it as a table view and don't need to query it directly.

```surql
DEFINE TABLE purchase;
-- DEFINE TABLE purchase DROP <- Use DROP if you never want to query 'purchase'
DEFINE TABLE sum AS SELECT math::sum(amount) AS amount, date FROM purchase GROUP BY date;

CREATE purchase SET amount = 100, date = "2026-01-29";
CREATE purchase SET amount = 200, date = "2026-01-29";
CREATE purchase SET amount = 200, date = "2026-01-30";

SELECT * FROM sum;
```

With the table view set up, you don't need to do anything but select from `sum` to see the aggregated results.

```surql
[
	{
		amount: 300,
		date: '2026-01-29',
		id: sum:[
			'2026-01-29'
		]
	},
	{
		amount: 200,
		date: '2026-01-30',
		id: sum:[
			'2026-01-30'
		]
	}
]
```

The function `math::sum()` is one of a number of functions that can be used both on its own and as an aggregate function.

For a full list of aggregate functions, see [this page](/docs/reference/query-language/functions/database-functions.md#aggregate-functions).

### Using predictable IDs for table views

One nice thing about table views is how predictable the IDs of the grouped values can be. Take the following example which has a `traffic_snapshot` table that holds a timestamp along with the number of cars and trucks at a certain location. On top of it we have a table called `traffic` that groups the results according to hourly intervals by using the `time::format()` function to turn a timestamp into a string output like `'2026-01-28:06:00:00'`.

```surql
DEFINE TABLE traffic_snapshot;
DEFINE TABLE traffic AS 
    SELECT 
        location, 
        time::format(at, "%Y-%m-%d:%H:00:00") AS at, 
        math::sum(cars) AS cars, math::sum(trucks) AS trucks 
    FROM traffic_snapshot
    GROUP BY location, at;

CREATE traffic_snapshot SET location = "53rd St", at = time::now(),         cars = 10, trucks = 3;
CREATE traffic_snapshot SET location = "53rd St", at = time::now() + 30m,   cars = 20, trucks = 5;
CREATE traffic_snapshot SET location = "53rd St", at = time::now() + 1h,    cars = 50, trucks = 10;
CREATE traffic_snapshot SET location = "53rd St", at = time::now() + 1h30m, cars = 34, trucks = 6;
```

Because the table view groups by `location` and `at`, we know that that will be the format of the `traffic` record IDs. As such, if you want to know what the traffic was like for a certain day, you can just pull it directly from the record ID - an operation which is close to instantaneous.

```surql
SELECT * FROM traffic:['53rd St', '2026-01-28:06:00:00'];
SELECT * FROM traffic:['53rd St', '2026-01-28:07:00:00'];
```

```surql title="Output"
-------- Query --------

[
	{
		at: '2026-01-28:06:00:00',
		cars: 70,
		id: traffic:[
			'53rd St',
			'2026-01-28:06:00:00'
		],
		location: '53rd St',
		trucks: 15
	}
]

-------- Query --------

[
	{
		at: '2026-01-28:07:00:00',
		cars: 34,
		id: traffic:[
			'53rd St',
			'2026-01-28:07:00:00'
		],
		location: '53rd St',
		trucks: 6
	}
]
```

## Combining real-time and event-driven functionality

In practice, you will probably want to combine many or even all of the approaches mentioned above. For example, you could have a schema that uses an array-based ID for quick record range queries, a defined event to respond to high volumes of traffic, and a table view to see aggregate traffic for certain units of time.

```surql
-- Define an event
DEFINE EVENT alert ON traffic_snapshot WHEN $after.cars + $after.trucks > 50 THEN {
    LET $alert = CREATE ONLY alert SET 
		message = "Heavy traffic at " + $after.id[0] + " with " + <string>($after.cars + $after.trucks) + " vehicles reported at " + <string>time::now();
    http::post('https://jsonplaceholder.typicode.com/posts/', {
        body: {
            message: $alert.message
        }
    });
};

-- Define a table view
DEFINE TABLE traffic AS 
    SELECT 
        id[0] AS location, 
        time::format(id[1], "%Y-%m-%d:%H:00:00") AS at, 
        math::sum(cars) AS cars, math::sum(trucks) AS trucks 
    FROM traffic_snapshot
    GROUP BY location, at;

-- Use an ID ideal for record range queries
CREATE traffic_snapshot:["53rd St", time::now() - 1h30m] SET cars = 34, trucks = 6;
CREATE traffic_snapshot:["53rd St", time::now() - 1h]    SET cars = 50, trucks = 10;
CREATE traffic_snapshot:["53rd St", time::now() - 30m]   SET cars = 20, trucks = 5;
CREATE traffic_snapshot:["53rd St", time::now()]         SET cars = 10, trucks = 3;

SELECT * FROM traffic_snapshot:["53d St", time::now() - 1h]..;
SELECT * FROM alert;
SELECT * FROM traffic;
```

---

Source: https://surrealdb.com/docs/learn/querying/surrealql/executing-queries

# Executing queries

In this section, you will explore methods to query data in SurrealDB using SurrealQL, GraphQL or any of the available SDKs. This allows you to retrieve, filter, and manipulate data efficiently and in the best way for your use case.

SurrealDB supports multiple query interfaces: [SurrealQL](/docs/learn/querying/surrealql/what-is-surrealql.md) for full-featured database queries, [GraphQL](/docs/learn/querying/graphql/overview.md) for schema-driven access, and any of the [available SDKs](/docs/languages.md) for language-native integration.

## SurrealQL

[SurrealQL](/docs/reference/query-language.md) is the query language for SurrealDB. It closely resembles traditional SQL, with some differences and improvements.

Designed to provide developers with an intuitive way to interact with SurrealDB, SurrealQL offers a familiar syntax and supports various statement types, allowing you to perform complex database operations efficiently.

To get started with SurrealQL, explore the [SurrealQL documentation](/docs/reference/query-language.md) to learn about the [statements](/docs/reference/query-language/statements/overview.md) available. These statements enable you to perform a wide range of database operations, from querying data to modifying records and managing database structures.

Additionally, SurrealQL provides a rich set of [database functions](/docs/reference/query-language/functions/database-functions.md) for data retrieval, manipulation and more complex computations.

### Getting started

The easiest way to get started with SurrealQL is to use the [SurrealDB Studio](https://studio.surrealdb.com/) UI. This interactive environment allows you to experiment with SurrealQL statements and see the results in a tabular format. You can begin with the built-in Sandbox, or connect to a remote or local instance. The Sandbox is non-persistent; to persist your data while still experimenting, click on **Deploy to Cloud** in SurrealDB Studio to create a free SurrealDB Cloud instance.

You can use SurrealDB Studio to learn about the syntax and features of SurrealQL and to develop your queries and scripts.

To familiarise yourself with SurrealQL, explore the various [statements](/docs/reference/query-language/statements/overview.md) and their syntax. The statement pages provide examples and explanations for each statement type, helping you understand how to construct queries and interact with SurrealDB effectively.

### Querying options

When using SurrealQL, there are several options available to interact with your database instance depending on your environment and needs.

- **SurrealDB Studio**: [SurrealDB Studio](https://studio.surrealdb.com/) is an interactive environment that allows you to experiment with SurrealQL statements and see the results in a tabular format. It is a great tool for learning about the syntax and features of SurrealQL and for developing your queries and scripts.

- **CLI**: The SurrealDB Command Line Interface (CLI) lets you [interact with the database directly from your terminal](/docs/reference/cli/db.md). You can execute SurrealQL queries, manage database structures, and perform administrative tasks using the CLI.

- **WebSocket**: SurrealDB supports WebSocket connections, allowing you to execute SurrealQL queries in real-time. This option is ideal for applications that require low-latency communication and real-time updates.

- **HTTP**: You can send HTTP requests to the [`/sql` endpoint](/docs/reference/rest-api/http-protocol.md#sql) to execute SurrealQL queries. This method is useful for integrating SurrealDB with web applications and services that communicate over HTTP.

- **Postman**: Using [Postman](https://www.postman.com/) or any other HTTP client, you can send a `POST` request to the `/sql` endpoint with your SurrealQL query in the body. This method provides flexibility and can be useful for testing and automation purposes.

- **RPC**: SurrealDB also supports RPC, allowing you to interact with the database programmatically over a network.

#### Using RPC

SurrealDB also supports RPC, allowing you to interact with the database programmatically over a network.

1. **Set up an RPC client**: Depending on your programming language, you can use various libraries to create an RPC client. Here is an example using JavaScript with the `fetch` function built into Node.js 18 and later:

   ```javascript
   async function querySurrealDB() {
     // Create a new person record
     const response = await fetch('http://localhost:8000/rpc', {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
         'Accept': 'application/json',
         'Authorization': 'Basic ' + btoa('root:secret'),
         'surreal-ns': 'main',
         'surreal-db': 'main',
       },
       body: JSON.stringify({
         id: 1,
         method: 'query',
         params: ['CREATE person SET name = "John Doe"; SELECT * FROM person;'],
       }),
     });

     const data = await response.json();
   }

   querySurrealDB();
   ```

2. **Execute the RPC call**: Run your script to send the RPC request and receive the response from SurrealDB.

## GraphQL

SurrealDB also supports GraphQL, allowing you to interact with the database using the familiar syntax. Currently, you can use the [GraphQL integration via SurrealDB Studio](/docs/learn/querying/graphql/via-studio.md), SurrealDB's own user interface, or [over HTTP](/docs/learn/querying/graphql/via-http.md) via a GraphQL Client such as [GraphiQL](https://github.com/graphql/graphiql) or [Postman](/docs/explore/tutorials/tutorials/http-via-postman.md) at the `localhost:8000/graphql` endpoint.

With SurrealDB Studio, you can easily connect to any SurrealDB instance, execute queries in real time, explore your tables, and design your schemas - all in one place.

The GraphQL view allows you to define and retrieve only the data you need, giving you more control and efficiency in how you interact with your database.

### Getting started

To get started with GraphQL, you can use the GraphQL integration in SurrealDB Studio. Once you have connected to your SurrealDB instance, you can explore your tables, execute queries, and manage your data in a user-friendly interface.

### Querying options

When using GraphQL, there are several options available to interact with your database instance depending on your environment and needs.

- **SurrealDB Studio**: [SurrealDB Studio](https://studio.surrealdb.com/) is an interactive environment that allows you to experiment with SurrealQL statements and see the results in a tabular format. It is a great tool for learning about the syntax and features of SurrealQL and for developing your queries and scripts.

- **HTTP**: You can [send HTTP requests to the `/graphql` endpoint](/docs/learn/querying/graphql/via-http.md) to execute GraphQL queries. This method is useful for integrating SurrealDB with web applications and services that communicate over HTTP.

## SDKs

Another way to interact with SurrealDB is to use one of the available SDKs. The SDKs let you query data, insert records, and manage database operations programmatically in your preferred programming language.

### Available SDKs

SurrealDB supports SDKs for several popular programming languages, including:

- **JavaScript/TypeScript**: The [JavaScript/TypeScript SDK](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) allows you to interact with SurrealDB from web applications, Node.js environments, and other JavaScript-based platforms. It provides a comprehensive set of methods to perform queries, manage records, and handle database transactions.

- **Python**: The [Python SDK](/docs/reference/python.md) integrates SurrealDB with Python applications such as web applications, data analysis tools and automation scripts.

- **.NET**: The [.NET SDK](/docs/reference/dotnet.md) allows you to interact with SurrealDB using the .NET framework. It provides a comprehensive set of methods to perform queries, manage records, and handle database transactions.

- **Rust**: The [Rust SDK](/docs/reference/rust.md) is built on the safety and performance features of the Rust programming language, and lets you interact with SurrealDB in a type-safe manner.

### Getting started with SDKs

To get started with any of the SDKs, you need to install the appropriate package for your programming language. Once installed, you can initialise the SDK and connect to your SurrealDB instance. Here is an example of how to get started with the JavaScript SDK:

```javascript
import { Surreal } from 'surrealdb';

const db = new Surreal('http://localhost:8000');

await db.connect('root', 'root');

type Person = {
	id: string;
	name: string;
};

// Assign the variable on the connection
const result = await db.query<[Person[], Person[]]>(
	'CREATE person SET name = "John"; SELECT * FROM type::table($tb);',
	{ tb: 'person' }
);

// Get all of the results from the second query
const people = result[1].result;
```

## Learn more

To learn more about the available options for querying data in SurrealDB, explore the following resources:

- [SurrealQL documentation](/docs/reference/query-language.md)
- [SurrealDB Studio documentation](/docs/explore/studio.md)

---

Source: https://surrealdb.com/docs/learn/querying/surrealql/executing-queries/via-cli

# Via CLI

Run SurrealQL from the terminal with surreal db sql.

To get started, [install the SurrealDB CLI](/docs/reference/cli/db.md) on your local machine.

## Getting started

After installing the SurrealDB CLI, you can start writing SurrealQL queries by running the [`surreal db start`](/docs/reference/cli/db/commands/start.md) command in your terminal. You can also add the `--help` flag to view the available options and commands.

To start a SurrealDB server, run the surreal db start command, using the options below. This example serves the database at the default location (http://localhost:8000), with a username and password.

```bash
surreal db start --user root --pass secret
```

The server is actively running, and can be left alone until you want to stop hosting the SurrealDB server.

<img src="~/assets/img/terminal-start.png" alt="Terminal start" />

## Running queries

Open a second terminal and connect with [`surreal db sql`](/docs/reference/cli/db/commands/sql.md). You can stay in the interactive REPL, or pipe a one-shot query.

To open a REPL:

```bash title="Start a SurrealDB shell (local endpoint)"
surreal db sql --endpoint http://localhost:8000 --ns main --db main --user root --pass secret --pretty
```

```bash title="Start a SurrealDB shell (in-memory)"
surreal db sql --endpoint memory --ns main --db main --user root --pass secret --pretty
```

To run a simple `SELECT` without staying in the REPL:

**Bash**

```bash
echo 'SELECT * FROM person;' | surreal db sql --endpoint http://localhost:8000 --ns main --db main --user root --pass secret --pretty --hide-welcome
```

**PowerShell**

```powershell
'SELECT * FROM person;' | surreal db sql --endpoint http://localhost:8000 --ns main --db main --user root --pass secret --pretty --hide-welcome
```

<img src="~/assets/img/terminal-sql.png" alt="Terminal SQL" />

## Learn more

Learn more about the available commands and options in the [SurrealDB CLI documentation](/docs/reference/cli/db.md).

---

Source: https://surrealdb.com/docs/learn/querying/surrealql/executing-queries/via-http

# Via HTTP

In this section, you will explore querying SurrealDB using HTTP. The HTTP API is designed to be simple and intuitive, with a RESTful interface that provides a consistent way to interact with the database.

SurrealDB provides a [RESTful HTTP API](/docs/reference/rest-api/http-protocol.md) for interacting with the database programmatically. This is useful when you need to integrate SurrealDB into existing HTTP-based workflows, scripts, or environments where an SDK is not available.

## Using curl `POST /sql`

The `/sql` endpoint enables use of SurrealQL queries.

> [!IMPORTANT]
> This HTTP endpoint expects the HTTP body to be a set of SurrealQL statements.

### Headers

<table>
    <thead>
        <tr>
            <th colspan="2">Header</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>Authorization</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            Sets the root, namespace, database, or record authentication data
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>Accept</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the desired content-type of the response
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>surreal-ns</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the selected Namespace for queries.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>surreal-db</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the selected Database for queries.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

> [!IMPORTANT]
> The `-u` in the example below is a shorthand used by curl to send an Authorization header.

```bash title="Request"
curl -X POST -u "root:secret" -H "surreal-ns: main" -H "surreal-db: main" -H "Accept: application/json" -d "SELECT * FROM person WHERE age > 18" http://localhost:8000/sql
```

```json title="Response"
[
	{
		"time": "14.357166ms",
		"status": "OK",
		"result": [
			{
				"age": "23",
				"id": "person:6r7wif0uufrp22h0jr0o"
				"name": "Simon",
			},
			{
				"age": "28",
				"id": "person:6r7wif0uufrp22h0jr0o"
				"name": "Marcus",
			},
		]
	}
]
```

## Using Postman

Postman is a popular tool for testing APIs. You can use it to send HTTP requests to your SurrealDB instance and perform various database operations.

1. **Set up Postman**: Download and install Postman from the [official website](https://www.postman.com/).

2. **Create a new request**: Open Postman and create a new HTTP request.

3. **Configure the request**:
   - Set the request method to `POST`.
   - Enter the URL of your SurrealDB instance, e.g., `http://localhost:8000/sql`.
   - In the [headers section](#headers), add a `Content-Type` header with the value `application/json`.
   - In the Body section, select `raw` and set the type to `Text`. Enter your SQL query, for example:

```surql
INFO FOR DB;
```

4. **Send the request**: Click the `Send` button to execute the query. You will see the response from SurrealDB in the lower section of the Postman interface.

## Learn more

Learn more about other [HTTP Endpoints](/docs/reference/rest-api/http-protocol.md) available in SurrealDB. For a more detailed tutorial on using Postman with SurrealDB, refer to the [working with SurrealDB over HTTP via Postman tutorial](/docs/explore/tutorials/tutorials/http-via-postman.md).

---

Source: https://surrealdb.com/docs/learn/querying/surrealql/executing-queries/via-sdks

# Via SDKs

Query SurrealDB programmatically using one of the official SDKs for Rust, JavaScript, Python, Go, Java, Kotlin, .NET, PHP, Swift and Mojo, or a community SDK.

Each SurrealDB SDK has its own set of methods for connecting to the database and performing data queries.

In each SDK, you can connect to the database using a local or remote connection. Once you are connected, you can start performing data queries. The supported SDKs are:

- [Rust](/docs/reference/rust.md) (available)

- [JavaScript](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) (available)

- [TypeScript](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) (available)

- [Python](/docs/reference/python.md) (available)

- [Node.js](/docs/reference/javascript/engines/node.md) (available)

- [.NET](/docs/reference/dotnet.md) (available)

- [Golang](/docs/reference/golang.md) (available)

- [Java](/docs/reference/java.md) (available)

- [PHP](/docs/reference/php.md) (available)

- [Kotlin](/docs/reference/kotlin.md) (available)

- [Swift](/docs/reference/swift.md) (available)

- [Mojo](/docs/reference/mojo.md) (available)

For other languages and runtimes, see the [community SDKs](/docs/languages/community.md), which are maintained outside SurrealDB.

## Writing SurrealQL queries in SDKs

In addition to the variety of methods provided by the SDKs to perform data queries, the `query` method works as a catch-all way to run [SurrealQL statements](/docs/reference/query-language.md) against the database.

**Javascript**

```ts title="Method Syntax"
async db.query<T>(query, vars)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>query</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>vars</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```ts
type Person = {
	id: string;
	name: string;
};

// Assign the variable on the connection
const result = await db.query<[Person[], Person[]]>(
	'CREATE person SET name = "John"; SELECT * FROM type::table($tb);',
	{ tb: 'person' }
);

// Get the first result from the first query
const created = result[0].result[0];

// Get all of the results from the second query
const people = result[1].result;
```

`.query_raw()`

With `.query_raw()`, you will get back the raw RPC response. In contrast to the `.query()` method, this will not throw errors that occur in individual queries, but will rather give those back as a string, and this will include the time it took to execute the individual queries.

**PHP**

```php title="Method Syntax"
$db->query($query, $vars)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>$query</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>$vars</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```php
// Assign the variable on the connection
$result = db->query(
	'CREATE person SET name = "John"; SELECT * FROM type::table($tb);',
	[ "tb" => "person" ]
);

// Get the first result from the first query
$created = $result[0]->result[0];

// Get all of the results from the second query
$people = $result[1]->result;
```

**Python**

```python title="Method Syntax"
db.query(sql, vars)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>vars</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```python
# Assign the variable on the connection
result = await db.query('CREATE person; SELECT * FROM type::table($tb)', {
	'tb': 'person',
})
# Get the first result from the first query
result[0]['result'][0]
# Get all of the results from the second query
result[1]['result']
```

**.NET**

```csharp title="Method Syntax"
await db.Query(sql)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" data-label="Arguments">
                <code>cancellationToken</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="col" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>
<br/>

### Example usage

```csharp
// Execute query with params
const string table = "person";
var result = await db.Query($"CREATE person; SELECT * FROM type::table({table});");

// Get the first result from the first query
var created = result.GetValue<Person>(0);

// Get all of the results from the second query
var people = result.GetValue<List<Person>>(1);
```

<br />

`.RawQuery()` : Runs a set of SurrealQL statements against the database, based on a raw SurrealQL query.

```csharp title="Method Syntax"
await db.RawQuery(sql, params)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" data-label="Arguments">
                <code>params</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="col" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" data-label="Arguments">
                <code>cancellationToken</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="col" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```csharp
// Assign the variable on the connection
var @params = new Dictionary<string, object> { { "table", "person" } };
var result = await db.RawQuery("CREATE person; SELECT * FROM type::table($table);", @params);

// Get the first result from the first query
var created = result.GetValue<Person>(0);

// Get all of the results from the second query
var people = result.GetValue<List<Person>>(1);
```

**Golang**

```go title="Method Syntax"
db.Query(sql, vars)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>vars</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```go
// Assign the variable on the connection
result, err := db.Query("CREATE person; SELECT * FROM type::table($tb);", map[string]string{
	"tb": "person"
});
```

**Rust**

```rust title="Method Syntax"
db.query(sql).bind(vars)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>vars</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
// Run some queries
let sql = "
    CREATE person;
    SELECT * FROM type::table($table);
";
let mut result = db
    .query(sql)
    .bind(("table", "person"))
    .await?;
// Get the first result from the first query
let created: Option<Person> = result.take(0)?;
// Get all of the results from the second query
let people: Vec<Person> = result.take(1)?;
```

## Learn more

Learn more about the [SurrealQL query language](/docs/reference/query-language.md).

---

Source: https://surrealdb.com/docs/learn/querying/surrealql/executing-queries/via-studio

# Via SurrealDB Studio

In this section, you will explore SurrealQL queries using SurrealDB Studio, the official query editor for SurrealDB. SurrealDB Studio is a powerful tool that allows you to write, execute, and visualise SurrealQL queries in real-time.

The [SurrealDB Studio](https://studio.surrealdb.com/query) query editor provides syntax highlighting, query validation, and real-time execution. Query results are displayed in a tabular format, making it easy to analyse and visualise data.

## Getting started

To get started with SurrealQL in SurrealDB Studio, go to the [SurrealDB Studio Query Editor](https://studio.surrealdb.com/query) and start writing your SurrealQL queries. You can use the query editor to write queries, execute them, and view the results in real-time.

### Setting up a connection

Within the connection list you will find a special connection called Sandbox, which is always available and allows you to test and experiment without storing data persistently. The Sandbox is useful for learning SurrealQL, testing queries, and more. This connection is designed for simple testing and not for evaluating performance, as it is limited to a single thread within the browser's WebAssembly engine. To persist your data while still experimenting, click on **Deploy to Cloud** in SurrealDB Studio to create a free SurrealDB Cloud instance.

In order to interact with a SurrealDB database by any other means you must first create a connection. Connections store the details required to connect to a database, such as the endpoint, namespace, database, and authentication details. When you select an active connection in SurrealDB Studio, you will connect to the database and be able to interact with it using the available interface views.

After opening a connection, you can switch to another connection at any time by pressing the connection name in the top left of the interface. This will open the connection list allowing you to switch to another connection, or create a new one.

You can also create a new custom connection which will allow you to connect to a remote or local SurrealDB instance.

### Setting a namespace and database for connections.

If you are using a connection (local or remote), you need to set the [namespace and database](/docs/learn/data-models/architecture.md#namespaces-and-databases) for the connection before you can start writing queries.

For example, you can set both the namespace to `main` and the database to `main`. This will set the namespace and database for the current connection.

### Writing a query

Once you have a connection open, you can use the SurrealQL query editor to create some data. For example, you can create a new record in the person table.

```surql
CREATE person SET name = "John", age = 30;
```

In the query editor, you can use syntax highlighting, code completion, and validation to help you write your queries more efficiently. To execute a query, press the run query button at the bottom of the query editor.

## Learn more

To learn more about SurrealQL and how to write queries using SurrealDB Studio, check out the [SurrealDB Studio documentation](/docs/explore/studio.md).

---

Source: https://surrealdb.com/docs/learn/querying/surrealql/sample-queries

# Sample queries

Runnable SurrealQL examples for CREATE, SELECT, UPDATE and DELETE, each editable in an embedded SurrealDB Studio.

This page walks through the four statements that do most of the work in [SurrealQL](/docs/reference/query-language.md): `CREATE`, `SELECT`, `UPDATE` and `DELETE`. Every example runs against a live database in an embedded [SurrealDB Studio](/docs/explore/studio.md), so you can edit a query and run it again to see what changes.

SurrealQL's basic shape is largely equivalent to SQL, but includes additional syntax to allow conveniences such as record links, subqueries and nested field access. The examples below introduce each of those in turn.

### Creating data with CREATE

Before we can start querying data, we need to create some records. This can be done using the [CREATE statement](/docs/reference/query-language/statements/create.md), which is used to add new records to the database.

The following example demonstrates how to create a record in the `category` table, initialised with a `name` field and a `created_at` field. Press the *"Run query"* button to execute the query and view the response.

<br />

```surql
CREATE category SET
	name = 'Technology',
	created_at = time::now();
```

After executing this statement, the `category` record is created in the database, and a randomly generated unique id known as a [Record ID](/docs/reference/query-language/language-primitives/data-types/record-ids.md) is assigned to it. This ID represents the primary key of our record, and can be used to reference the record in future queries.

When creating records, you can also explicitly set the record ID. This can be useful when you are able to use predictable unique record IDs such as `company:surrealdb` or `planet:earth`. In the following example, we create a person record with the ID `john`, and set the `first`, `last`, `age`, `admin`, and `signup_at` fields.

<br />

```surql
CREATE person:john SET
	first = 'John',
	last = 'Adams',
	age = 29,
	admin = true,
	signup_at = time::now();
```

SurrealDB also supports subqueries, used in the following example to populate the `category` field of the `article` record with the ID of the `Technology` category.

<br />

```surql
CREATE article SET
	created_at = time::now(),
	author = person:john,
	title = 'Lorem ipsum dolor',
	text = 'Donec eleifend, nunc vitae commodo accumsan, mauris est fringilla.',
	category = SELECT VALUE id FROM ONLY category WHERE name = 'Technology' LIMIT 1;
```

### Querying data with SELECT

After inserting records into your database, you can now use the [SELECT statement](/docs/reference/query-language/statements/select.md) to retrieve data. While this statement will be familiar to anyone who has used traditional SQL before, SurrealDB's SELECT statement adds features drawn from NoSQL databases.

For example, in addition to selecting records from a single table, you can also select records from multiple tables, or select specific records by their Record ID.

<br />

```surql
-- Select all records from a table
SELECT * FROM article;
-- Select records from multiple tables
SELECT * FROM category, person;
-- Selecting specific records
SELECT * FROM person:john;
```

The [SELECT statement](/docs/reference/query-language/statements/select.md) can filter on fields, resolve the contents of a record link, and reach data through a Record ID directly, with no JOIN planning or indexes needed.

The following query combines a number of such features:
- **Filtering**: Use the `WHERE` clause to only include records where the author's age is less than 30.
- **Fetching**: Use the `.*` idiom to replace record ids with their actual field values.
- **Specific fields**: Retrieve only the title and author fields from the article table.
- **Record links**: Structure the field data from the author in a preferred format, including an alias for the field `name.full`.

```surql
SELECT
	title,
	category.*,
	author.{
		age,
		name: name.full,
	}
FROM article
WHERE author.age < 30;
```

### Modifying data with UPDATE

Records can be updated using the [UPDATE](/docs/reference/query-language/statements/update.md) statement, which allows you to modify the contents of existing records.

Much like the `SELECT` statement, you can pass both table names and individual record IDs to the `UPDATE` statement. This allows you to update specific records, or update multiple records at once.

```surql
UPDATE person:john SET
	age += 1,
	admin = false;
```

The `UPDATE` statement offers a variety of features to further filter down records, and apply different update strategies. The following example demonstrates how we can merge new data into records matching a specific condition.

<br />

```surql
UPDATE person MERGE {
	age: 30,
	admin: false
}
```

In addition to the `UPDATE` statement, SurrealDB also offers an [UPSERT statement](/docs/reference/query-language/statements/upsert.md), which has the added functionality of creating a record if it does not already exist. This can be useful when you want to update a record if it exists, or create it if it does not.

### Deleting data with DELETE

You can also delete records from your database using the [DELETE statement](/docs/reference/query-language/statements/delete.md). This statement allows you to remove records from your database, either by specifying the record ID, or by using specific conditions.

The following example demonstrates the use of the `RETURN` clause, which instructs SurrealDB to return the records before they are deleted.

<br />

```surql
DELETE article WHERE author.name.first = 'David' RETURN BEFORE;
```

## Where next

- [Writing SurrealQL](/docs/learn/querying/surrealql/writing-surrealql.md) for the syntax rules behind these examples.
- [Statements and values](/docs/learn/querying/surrealql/statements-and-values.md) for choosing between `CREATE`, `INSERT`, `UPDATE`, `UPSERT` and `RELATE`.
- [Executing queries](/docs/learn/querying/surrealql/executing-queries.md) for running the same statements from the CLI, an SDK or over HTTP.
- [Transactions](/docs/learn/querying/concepts-and-guides/transactions.md) for grouping statements that must succeed or fail together.

---

Source: https://surrealdb.com/docs/learn/querying/surrealql/statements-and-values

# Statements and values

SurrealQL statements grouped as resource definitions, control flow with transactions, and CRUD-style query operations.

SurrealDB has a variety of statements that let you configure and query a database. This page covers the different types of statements that are available.

## Types of statements

SurrealDB has a large variety of statements. They can be divided into three types:

* Statements that define and access database resources,
* Statements used for control flow and handling manual transactions,
* Statements used in the context of queries, usually in CRUD (create, read, update, delete) operations.

### Database resource statements

These statements pertain to defining, removing, altering, and rebuilding database resources. Some examples are:

* [`DEFINE`](/docs/reference/query-language/statements/define/overview.md) statements to define database resources,
* [`ALTER`](/docs/reference/query-language/statements/alter/overview.md) statements to alter certain resources,
* [`REBUILD`](/docs/reference/query-language/statements/rebuild.md) to rebuild an index.

Some other statements pertain to using defined resources. They are:

* [`USE`](/docs/reference/query-language/statements/use.md) to move from one namespace or database to another,
* [`INFO`](/docs/reference/query-language/statements/info.md) statements to see the definitions for resources.

### Control flow statements

These statements are used to describe how query execution should progress.

Some control flow statements only pertain to manual transactions. While all statements in SurrealDB are conducted inside their own transaction, these statements can be used to manually set up a larger transaction composed of multiple statements. They are:

* [`BEGIN`](/docs/reference/query-language/statements/begin.md) to begin a manual transaction,
* [`COMMIT`](/docs/reference/query-language/statements/commit.md) to commit a transaction,
* [`CANCEL`](/docs/reference/query-language/statements/cancel.md) to cancel a transaction.

Other control flow statements are used in the same manner as in other programming languages. Some examples are:

* [`FOR`](/docs/reference/query-language/statements/for.md) to begin a for loop,
* [`CONTINUE`](/docs/reference/query-language/statements/continue.md) to continue to the next iteration of a loop,
* [`BREAK`](/docs/reference/query-language/statements/break.md) to break out of a for loop, internal scope, function, etc.,
* [`THROW`](/docs/reference/query-language/statements/throw.md) to cancel execution and return an error.

### Query statements

These statements are used to execute queries, most often but not always in the context of a CRUD operation.

Some examples of query statements are:

* [`CREATE`](/docs/reference/query-language/statements/create.md) to create one or more records of one or more types of tables,
* [`INSERT`](/docs/reference/query-language/statements/insert.md) to create one or more regular records or graph edges,
* [`RELATE`](/docs/reference/query-language/statements/relate.md) to create a single graph edge between two records,
* [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md) to stream all the changes to a table,
* [`DELETE`](/docs/reference/query-language/statements/delete.md) to delete one or more records.

The following flowchart can be used to get a sense of when it makes sense to use `CREATE`, `INSERT`, `UPDATE`, `UPSERT`, and `RELATE`.

<img src="~/assets/img/surrealql/statements/statement_flowchart-light.png" darkSrc="~/assets/img/surrealql/statements/statement_flowchart.png" alt="A flowchart that explains in which cases to use the statements CREATE, INSERT, UPDATE, UPSERT, and RELATE." />

## Statement parameters

A number of parameters prefixed with `$` are automatically available within a statement that provide access to relevant context inside the statement. These are known as reserved variable names. For example:

* [$before](/docs/reference/query-language/language-primitives/parameters.md#before-after) and [$after](/docs/reference/query-language/language-primitives/parameters.md#before-after) can be accessed in statements that mutate values to see the values before and after an update,
* [$session](/docs/reference/query-language/language-primitives/parameters.md#session) provides context on the current session,
* [$parent](/docs/reference/query-language/language-primitives/parameters.md#parent-this) provides access to the value in a primary query while inside a subquery.

For a full list of these automatically generated parameters, see the [parameters](/docs/reference/query-language/language-primitives/parameters.md#reserved-variable-names) page.

## Values

Each of the types mentioned in the data model is a subset of an all-encompassing type called a value.

## Comparing and ordering values

As every data type is a subset of value, any value can be compared with another one.

```surql
9 > 1;            // Returns true
null > none;      // Also returns true
```

Being able to compare a value with any other value is what makes SurrealDB's record range syntax possible.

```surql
CREATE time_data:[d'2024-07-23T00:00:00.000Z'];
CREATE time_data:[d'2024-07-24T00:00:00.000Z'];
CREATE time_data:[d'2024-07-25T00:00:00.000Z'];
-- Records from the 24th to the 25th
SELECT * FROM time_data:[d'2024-07-24']..[d'2024-07-25'];
-- Records from the 24th
SELECT * FROM time_data:[d'2024-07-24']..;
-- All records
SELECT * FROM time_data:[NONE]..;
```

The `..` open-range syntax also represents an infinite value inside a record range query, making it the greatest possible value and the inverse of `NONE`, the lowest possible value. A part of a record range query that begins with `NONE` and ends with `..` will thus filter out nothing.

```surql
CREATE temperature:['London', d'2025-02-19T00:00:00.000Z'] SET val = 5.5;
CREATE temperature:['London', d'2025-02-20T00:00:00.000Z'] SET val = 5.7;

-- Return all records as long as index 0 = 'London'
SELECT * FROM temperature:['London', NONE]..=['London', ..];
```

```surql title="Output"
[
	{
		id: temperature:[
			'London',
			d'2025-02-19T00:00:00Z'
		],
		val: 5.5f
	},
	{
		id: temperature:[
			'London',
			d'2025-02-20T00:00:00Z'
		],
		val: 5.7f
	}
]
```

Inside a schema, the keyword `any` is used to denote any possible value.

```surql
DEFINE FIELD anything ON TABLE person TYPE any;
```

## Values and truthiness

Any value is considered to be truthy if it is not NONE, NULL, or a default value for the data type. A data type at its default value is one that is empty, such as an empty string or array or object, or a number set to 0.

The following example shows the result of the `array::all()` method, which checks to see if all of the items inside an array are truthy or not.

```surql
array::all(["", 1, 2, 3]); // false because of ""
array::all([{}, 1, 2, 3]); // false because of {}
array::all(["SurrealDB", { is_nice_database: true }, 1, 2, 3]);  // true
```

As [the ! operator](/docs/reference/query-language/language-primitives/operators.md) reverses the truthiness of a value, a doubling of this operator can also be used to check for truthiness.

```surql
[
    !!"Has a value", !!"",             // true, false
    !!true, !!false,                   // true, false
    !!{ is_nice_database: true }, !!{} // true, false
    ];
```

The following example shows how `!!` can be conveniently used along with the [`object::entries()`](/docs/reference/query-language/functions/database-functions/object.md#objectentries) and [`object::from_entries()`](/docs/reference/query-language/functions/database-functions/object.md#objectfrom_entries) function to set fields from one table in another as long as they are not empty, `NULL`, or `NONE`. The filtering itself is done using the [`array::filter()`](/docs/reference/query-language/functions/database-functions/array.md#arrayfilter) function which returns any values that match a pattern such as the `!!` operator.

```surql
CREATE person:one CONTENT {
    this: NULL,
    that: "that",
    and: "and",
    the: NONE,
    other: ""
};

CREATE person:two CONTENT {
    this: "this",
    that: NONE,
    and: NULL,
    the: "",
    other: "other"
};

FOR $person IN SELECT * FROM person {
    LET $filtered = $person.*.entries().filter(|$entry| !!$entry[1]);
    CREATE new_table CONTENT object::from_entries($filtered);
    
    -- Or all in one line:
    -- CREATE new_table CONTENT object::from_entries($person.*.entries().filter(|$n| !!$n[1]))
};

SELECT * FROM new_table;
```

The output shows two new records that only contain fields that were truthy in the original `person` records.

```surql title="Output"
[
	{
		and: 'and',
		id: new_table:one,
		that: 'that'
	},
	{
		id: new_table:two,
		other: 'other',
		this: 'this'
	}
]
```

---

Source: https://surrealdb.com/docs/learn/querying/surrealql/what-is-surrealql

# What is SurrealQL?

SurrealQL is SurrealDB’s SQL-like language for queries, schemas, graph relationships and optimised execution.

[SurrealQL](/docs/reference/query-language.md) is the query language for SurrealDB. It closely resembles traditional SQL, with some differences and improvements.

SurrealQL is designed to provide developers with an intuitive way to interact with SurrealDB. It offers a familiar syntax and supports various statement types, allowing you to perform complex database operations efficiently.

While SurrealQL shares similarities with traditional SQL, it introduces enhancements and optimisations that make it well-suited for working with SurrealDB's advanced features. The same language is used to query data, modify records and manage database structures.

## Key features

The main features of SurrealQL are:

- **Familiar syntax**: SurrealQL adopts a syntax similar to traditional SQL, so developers who know SQL can start using SurrealDB with little relearning.

- **Advanced querying**: SurrealQL supports a wide range of querying capabilities, including filtering, sorting, aggregating, and joining data from multiple tables.

- **Data manipulation**: SurrealQL has statements to insert, update, and delete records in your SurrealDB database.

- **Graph relationships**: SurrealQL supports graph relationships, allowing you to define and query relationships between records in your database.

- **Schema management**: SurrealQL provides features for creating and modifying database schemas, allowing you to define the structure of your data and enforce data integrity.

- **Performance optimisation**: SurrealQL incorporates optimisations specific to SurrealDB, ensuring efficient execution of queries and minimising resource usage.

<img src="~/assets/img/image/surrealist/query-new.png" alt="SurrealDB Studio query view" />

---

Source: https://surrealdb.com/docs/learn/querying/surrealql/writing-surrealql

# Writing SurrealQL

Ways of executing SurrealQL queries such as SurrealDB Studio, CLI, HTTP, SDKs, and GraphQL.

[SurrealQL](/docs/reference/query-language.md) is the query language for SurrealDB. How queries reach the database depends on your tool or integration. The sections below describe each path in more detail.

## SurrealQL execution paths

The following four approaches are the most commonly used ways to execute SurrealQL queries directly or indirectly via SDKs in one of many programming languages.

| Approach | Typical use |
| -------- | ----------- |
| [SurrealDB Studio](/docs/learn/querying/surrealql/executing-queries/via-studio.md) | Interactive editing, Sandbox, and visual results in the browser. |
| [CLI](/docs/learn/querying/surrealql/executing-queries/via-cli.md) | Local development, scripts, and [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) against a running server. |
| [HTTP](/docs/learn/querying/surrealql/executing-queries/via-http.md) | Services and integrations that call the [`/sql`](/docs/reference/rest-api/http-protocol.md#sql) endpoint. |
| [SDKs](/docs/learn/querying/surrealql/executing-queries/via-sdks.md) | Application code using the official clients (WebSocket or HTTP internally, depending on SDK and configuration). |

In SurrealDB Studio, the built-in Sandbox does not persist data. To keep experimental work, use **Deploy to Cloud** in the app to create a free SurrealDB Cloud instance.

For a single entry point into these guides, see **[Executing queries](/docs/learn/querying/surrealql/executing-queries.md)**.

Interactive clients (including the SDKs) usually speak to the database over **WebSocket** or **HTTP**. The [RPC protocol](/docs/reference/rest-api/rpc-protocol.md) describes how queries and responses are framed on the wire.

Language syntax, statements, and functions are documented in the **[SurrealQL reference](/docs/reference/query-language.md)**.

## GraphQL

[GraphQL](/docs/learn/querying/graphql/overview.md) is a separate query interface on top of SurrealDB: you describe fields and shapes in GraphQL, not in SurrealQL. You can use it from [SurrealDB Studio](/docs/learn/querying/graphql/via-studio.md), over [HTTP](/docs/learn/querying/graphql/via-http.md), or from tools such as Postman or Bruno.

---

Source: https://surrealdb.com/docs/learn/schema-management

# Schema management

Tables, fields, indexes and events: how to shape data in SurrealDB. Tenancy and related patterns too.

Schema management is where you describe what lives in the database: tables and fields, indexes, events, optional files and buckets, and the namespace / database layout for multi-tenant setups.

What makes SurrealDB convenient is that schema management can be almost entirely dispensed with at the start, because standard CRUD (Create, Read, Update, Delete) operations do not require a schema to be defined first. This allows you to experiment at your leisure until it is time to firm up your database's expected record and data types, at which point schema definition comes to the fore.

Schema covers behaviour as well as data. For example, statements like [`DEFINE EVENT`](/docs/reference/query-language/statements/define/event.md) set up effects that take place after a statement is executed.

The learn pages here explain how and why to use these pieces together. For exact statement grammar, defaults, and edge cases, use the SurrealQL reference ([`DEFINE`](/docs/reference/query-language/statements/define/overview.md) and related pages).

If you are new to SurrealDB, it helps to read [tables](/docs/learn/schema-management/tables-and-fields/tables.md) and [fields](/docs/learn/schema-management/tables-and-fields/fields-and-validation.md) first, then [indexes](/docs/learn/schema-management/indexes/index-types-and-strategies.md) and [events](/docs/learn/schema-management/events-and-triggers/defining-events.md) when you need behaviour beyond simple storage.

For managing schema files across environments, synchronising locally and running phased rollouts in production, see [SurrealKit schema migration](/docs/manage/schema-migration.md).

## Record IDs

- [Record IDs and addressing](/docs/learn/schema-management/tables-and-fields/record-ids-and-addressing.md) - how a record id is built, and what it can address
- [Record ID best practices](/docs/learn/schema-management/tables-and-fields/record-id-best-practices.md) - choosing ids that stay useful as data grows

## Design and architecture

- [Schema design](/docs/learn/schema-management/schema-design.md) - choosing between schemafull and schemaless, and what each costs
- [Namespace & database architecture](/docs/learn/schema-management/multi-tenancy/namespace-and-database-architecture.md) - how to lay out namespaces and databases for multiple tenants

---

Source: https://surrealdb.com/docs/learn/schema-management/computed-data/closures

# Closures

Anonymous functions in SurrealQL, colloquially known as closures.

Closures are small inline functions. They are generally used in two ways:

* By binding them with `LET` as a parameter for reuse,
* Passed directly into methods like `.map()`, `.filter()`, or `.chain()`.

Parameters passed into closures are contained within `||`, after which the body of the function is written. They are not a replacement for [`DEFINE FUNCTION`](/docs/reference/query-language/statements/define/function.md) when you need a named, reusable routine with permissions.

## Basic examples

## Basic function definitions

```surql
-- Define an anonymous function that doubles a number
LET $double = |$n: number| $n * 2;
RETURN $double(2);  -- Returns 4

-- Define a function that concatenates two strings
LET $concat = |$a: string, $b: string| $a + $b;
RETURN $concat("Hello, ", "World!");  -- Returns "Hello, World!"
```

## Error handling and type enforcement

Like regular functions, you can also enforce type constraints within your functions to prevent type mismatches.

```surql
-- Define a function that greets a person, returning a string
LET $greet = |$name: string| -> string { "Hello, " + $name + "!" };
RETURN $greet("Alice");   -- Returns "Hello, Alice!"

-- Define a function with a return type
LET $to_upper = |$text: string| -> string { string::uppercase($text)
  };
RETURN $to_upper("hello");  -- Returns "HELLO"
RETURN $to_upper(123);      -- Error: type mismatch

-- Define a function that accepts only numbers
LET $square = |$num: number| $num * $num;
RETURN $square(4);    -- Returns 16
RETURN $square("4");  -- Error: type mismatch
```

## Closures in functions

Many of SurrealDB's functions require a closure to be passed in, making it easy to use complex logic on a value or the elements of an array.

The `chain` function which performs an operation on a value before passing it on:

```surql
"Two"
    .replace("Two", "2")
    .chain(|$num| <number>$num * 1000);
```

```surql title="Output"
2000
```

We can see that the input to the `.chain()` method is indeed a closure by creating our own that is assigned to a parameter. This closure can be passed into `.chain()`, returning the same output as above.

```surql
LET $my_func = |$num| <number>$num * 1000;

"Two"
    .replace("Two", "2")
    .chain($my_func);
```

The following example shows a chain of array functions used to remove useless data, followed by a check to see if all items in the array match a certain condition, and then a cast into another type. The [`array::filter`](/docs/reference/query-language/functions/database-functions/array.md#arrayfilter) call in the middle ensures that the [`string::len`](/docs/reference/query-language/functions/database-functions/string.md#stringlen) function that follows is being called on string values.

```surql
[NONE, NONE, "good data", "Also good", "important", NULL]
    .filter(|$v| $v.is_string())
    .all(|$s| $s.len() > 5)
    .chain(|$v| <string>$v);
```

```surql title="Output"
'true'
```

To fail the query when the pipeline does not meet your assumptions (for example while debugging), end the chain with [`.expect()`](/docs/reference/query-language/functions/database-functions/value.md#valueexpect) instead of casting to a string:

```surql
[NONE, NONE, "good data", "Also good", "important", NULL]
    .filter(|$v| $v.is_string())
    .all(|$s| $s.len() > 5)
    .expect(|$ok| $ok,
      "Expected at least one string longer than 5 characters");
```

## Capturing parameters

Another aspect of closures is that they are able to capture parameters in their environment. This is in fact where the name "closure" comes from, as they are anonymous functions that are able to "enclose" such parameters without needing to pass them through the `||` parallel bars that hold its input arguments.

```surql
LET $okay_nums = [1,2,3];

[1,5,6,7,0].filter(|$n| $n IN $okay_nums);
```

## Closures and writes

Whether a closure can modify database resources depends on your version.

**SurrealDB 3.3 and later**

A closure that writes makes the expression holding it a write, so it can modify database resources like any other statement. This applies wherever the closure is invoked, including closure-taking functions such as `map`, `filter` and `fold`, and it follows a write reached through a function the closure calls.

```surql
-- 1. Create a test table and function
DEFINE TABLE test_table SCHEMAFULL;
DEFINE FIELD name ON test_table TYPE string;

DEFINE FUNCTION fn::test_create($name: string) -> object {
    CREATE test_table CONTENT { name: $name };
    { created: true, name: $name };
};

-- 2. Call the function directly
fn::test_create("direct_call");

-- 3. Call the function inside .map()
LET $names = ["Alice", "Bob", "Charlie"];
$names.map(|$n| fn::test_create($n));
```

```surql title="Output"
[
	{ created: true, name: 'Alice' },
	{ created: true, name: 'Bob' },
	{ created: true, name: 'Charlie' }
]
```

A closure whose body only reads still resolves as a read, so `map`, `filter` and `fold` over a pure closure keep their read-only path.

A `FOR` loop or a `SELECT` over a list remains a good choice where it reads more clearly:

```surql
-- Function called once for each name
SELECT VALUE fn::test_create($this) FROM ["Alice", "Bob", "Charlie"];
```

**Before SurrealDB 3.3**

Closures work inside a read-only context, and cannot be used to modify database resources. This holds even when the write sits inside a function the closure calls.

```surql
-- 1. Create a test table and function
DEFINE TABLE test_table SCHEMAFULL;
DEFINE FIELD name ON test_table TYPE string;

DEFINE FUNCTION fn::test_create($name: string) -> object {
    CREATE test_table CONTENT { name: $name };
    { created: true, name: $name };
};

-- 2. Call the function directly - works
fn::test_create("direct_call");

-- 3. Call the function inside .map() - fails
LET $names = ["Alice", "Bob", "Charlie"];
$names.map(|$n| fn::test_create($n));
```

```surql title="Output"
Error: "Couldn't write to a read only transaction"
```

In many cases, a closure can be substituted by another operation such as a `FOR` loop or a regular `SELECT` statement.

```surql
DEFINE TABLE test_table SCHEMAFULL;
DEFINE FIELD name ON test_table TYPE string;

DEFINE FUNCTION fn::test_create($name: string) -> object {
    CREATE test_table CONTENT { name: $name };
    { created: true, name: $name };
};

-- Function to create a record called for each name
SELECT VALUE fn::test_create($this) FROM ["Alice", "Bob", "Charlie"];
```

## Conclusion

These anonymous functions provide a flexible way to define small, reusable pieces of logic that can be used throughout your queries. Using them keeps SurrealQL code modular and easier to maintain.

---

Source: https://surrealdb.com/docs/learn/schema-management/computed-data/computed-fields

# Computed fields

Fields that are derived on read with COMPUTED, traversing record links, and how they differ from VALUE and stored data.

Most fields are stored: you write a value and it stays on the record until you change it. A computed field differs from a normal field in that it is recalculated whenever the record is read, using an expression you attach in the schema. That makes it ideal for derived data that should always reflect a certain state instead of needing a separate update job.

## Compared to `VALUE` and normal storage

* `VALUE` on a field sets what gets stored (often normalising input); it runs on write, not on every read in the same way a computed field does.
* `COMPUTED` stores an expression, as opposed to set data.
* You cannot mark `id` or nested paths (for example `meta.score`) as computed - only top-level field names.

For everything else about field definitions - types, `ASSERT`, `DEFAULT`, permissions - see [Fields and validation](/docs/learn/schema-management/tables-and-fields/fields-and-validation.md) and the [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) reference.

## Example: always-fresh timestamp on read

```surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON user TYPE string;
DEFINE FIELD accessed_at ON user COMPUTED time::now();

CREATE user:one SET name = "Ada";
SELECT * FROM ONLY user:one;
SLEEP 1s;
SELECT * FROM ONLY user:one;
```

The second `SELECT` should show a new `accessed_at` each time, because the expression is evaluated when the record is projected.

## Record references

A stored field can hold a [record link](/docs/reference/query-language/language-primitives/record-links.md) - a record ID that points at another record - often declared with `record<table>` or `option<record<table>>`, and optionally with [`REFERENCE`](/docs/reference/query-language/statements/define/field.md) so deletes on the target record are handled predictably. A computed field does not store a link itself (the `REFERENCE` clause applies to stored fields only), but the expression may traverse link fields already on the record and read fields from the related record.

In the example below, `book.author` stores `person:ada`, while `author_name` is derived from that link. If you change the person’s `name`, the next read of the book shows the updated label without writing back to `book`.

```surql
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name ON person TYPE string;

DEFINE TABLE book SCHEMAFULL;
DEFINE FIELD title ON book TYPE string;
DEFINE FIELD author ON book TYPE record<person>;
DEFINE FIELD author_name ON book COMPUTED author.name;

CREATE person:ada SET name = "Ada Lovelace";
CREATE book:one SET title = "Notes", author = person:ada;

SELECT title, author_name FROM book:one;

UPDATE person:ada SET name = "Augusta Ada King";
SELECT title, author_name FROM book:one;
```

## When to use computed fields

Use them when:

* The result is cheap enough to evaluate on read and you want zero staleness.
* The logic is purely relative to the record and its graph.

Computed fields may not always be the best option. They should not be used when:

* You need the value indexed or searched like a normal field (compute into a stored field via events or application writes instead).
* The expression depends on external systems.

## See also

* [Closures](/docs/learn/schema-management/computed-data/closures.md) - small anonymous functions in expressions.
* [Record links](/docs/reference/query-language/language-primitives/record-links.md) - storing and traversing record IDs.
* [Reactive patterns](/docs/learn/schema-management/events-and-triggers/reactive-patterns.md) - when updates should flow from writes instead of reads.

---

Source: https://surrealdb.com/docs/learn/schema-management/events-and-triggers/defining-events

# Defining events

Running logic after creates, updates, and deletes with DEFINE EVENT.

Events let the database react to record changes: write an audit log, normalise related records, or enqueue follow-up work. They run after the record change but inside the same transaction (unless you opt into async), and they see [$before and $after](/docs/reference/query-language/language-primitives/parameters.md#before-after) snapshots of the record.

> [!NOTE]
> Events are a side effect of normal writes. They are not fired during a bulk [import](/docs/reference/cli/db/commands/import.md).

## Key ideas

* `$event`: `"CREATE"`, `"UPDATE"`, or `"DELETE"`.
* `$before` / `$after`: state immediately before and after the change.
* `$value`: the record as seen by the event (after create/update, before delete).
* `WHEN`: optional filter so the body runs only when something meaningful changed.
* `THEN`: the SurrealQL block to execute.

You need the usual database privileges and [`USE`](/docs/reference/query-language/statements/use.md) scope. Full syntax and async options are in [`DEFINE EVENT`](/docs/reference/query-language/statements/define/event.md); [Reactive patterns](/docs/learn/schema-management/events-and-triggers/reactive-patterns.md) covers ASYNC, RETRY, and MAXDEPTH.

## Example usage

-  Email Change Detection: Create an event that logs whenever a user's email is updated.

In this example:
- The `WHEN` clause checks if the email has changed.
- The `THEN` clause records this change in a `log` table.

```surql
-- Create a new event whenever a user changes their email address
-- One-statement event
DEFINE EVENT OVERWRITE test
  ON TABLE user WHEN $before.email != $after.email THEN (
    CREATE log SET 
        user       = $value.id,
        -- Turn events like "CREATE" into string "email created"
        action     = 'email' + ' ' + $event.lowercase() + 'd',
        -- `email` field may be NONE, log as '' if so
        old_email  = $before.email ?? '',
        new_email  = $after.email  ?? '',
        at         = time::now()
);
UPSERT user:test SET email = 'old_email@test.com';
UPSERT user:test SET email = 'new_email@test.com';
DELETE user:test;
SELECT * FROM log ORDER BY at ASC;
```

```surql title="Output"
[
	{
		action: 'email created',
		at: d'2025-11-25T02:59:41.003Z',
		id: log:e3thw1l0q7xiapznar1f,
		new_email: 'old_email@test.com',
		old_email: '',
		user: user:test
	},
	{
		action: 'email updated',
		at: d'2025-11-25T02:59:41.003Z',
		id: log:uaarfyk191jgod06xobm,
		new_email: 'new_email@test.com',
		old_email: 'old_email@test.com',
		user: user:test
	},
	{
		action: 'email deleted',
		at: d'2025-11-25T02:59:41.003Z',
		id: log:mlkag8h1xotglpz9wt2i,
		new_email: '',
		old_email: 'new_email@test.com',
		user: user:test
	}
]
```

### More complex logic:

The following event executes multiple actions to both log a purchase and establish relationships between the customer and product.

```surql
DEFINE EVENT purchase_made ON TABLE purchase
    WHEN $before == NONE
    THEN {
        LET $customer = (SELECT * FROM customer
          WHERE id = $after.customer);
        LET $product = (SELECT * FROM product
          WHERE id = $after.product);

        RELATE $customer->bought->$product CONTENT {
            quantity: $after.quantity,
            total: $after.total,
            status: 'Pending',
        };

        CREATE log SET
            customer_id = $after.customer,
            product_id = $after.product,
            action = 'purchase_created',
            timestamp = time::now();
    };
```

## Specific events

You can trigger events based on specific events. You can use the variable $event to detect what type of event is triggered on the table.

```surql
-- UPDATE event
-- Here we are creating a notification when a user is updated.
DEFINE EVENT user_updated ON TABLE user
    WHEN $event = "UPDATE"
    THEN (
        CREATE notification SET message = "User updated",
          user_id = $after.id,
          created_at = time::now()
    );

-- DELETE event is triggered when a record is deleted from the table.
-- Here we are creating a notification when a user is deleted.
DEFINE EVENT user_deleted ON TABLE user
    WHEN $event = "DELETE"
    THEN (
        CREATE notification SET message = "User deleted",
          user_id = $before.id,
          created_at = time::now()
    );

-- You can combine multiple events based on your use cases.
-- Here we are creating a log when a user is created, updated or deleted.
DEFINE EVENT user_event ON TABLE user
    WHEN $event = "CREATE" OR $event = "UPDATE" OR $event = "DELETE"
    THEN (
        CREATE log SET
            table = "user",
            event = $event,
            happened_at = time::now()
    );
```

This longer example shows an event that updates all posts for a publication to "published" status once a publication containing them is created.

```surql
-- Define an event
DEFINE FIELD status
  ON post TYPE "submitted" | "published" DEFAULT "submitted";
DEFINE EVENT publish_post ON TABLE publication
    WHEN $event = "CREATE"
    THEN (
        FOR $post IN $after.posts {
            UPDATE $post SET status = "published";
        }        
    );

CREATE post:one SET content = "I read the news today, oh boy...";
CREATE post:two
  SET content = "On the banks of Tuonela Bleach the skeletons of kings";
CREATE post:three SET content = "뭐 화끈한 일 뭐 신나는 일 없을까";
CREATE publication SET posts = [post:one, post:two, post:three];

SELECT * FROM post;
```

```surql title="Output"
[
	{
		content: 'I read the news today, oh boy...',
		id: post:one,
		status: 'submitted'
	},
	{
		content: '뭐 화끈한 일 뭐 신나는 일 없을까',
		id: post:three,
		status: 'submitted'
	},
	{
		content: 'On the banks of Tuonela Bleach the skeletons of kings',
		id: post:two,
		status: 'submitted'
	}
]
```

## Events and permissions

Queries inside the event always execute without any permission checks, even when triggered by changes made by the currently authenticated user.

Consider a CREATE query sent by a record user that has CREATE access to the `comment` table only:

```surql
CREATE comment SET
    post = post:tomatosoup,
    content = "So delicious!",
    author = $auth.id;
```

Logic can be added to the event itself to modify the behaviour depending on a user's permissions or any other condition.

```surql
DEFINE EVENT on_comment_created ON TABLE comment
    WHEN $event = "CREATE"
    THEN {
        -- Check if the post allows for adding comments.
        -- User record doesn't have access to the `post` table.
        IF $after.post.disable_comments {
            THROW "Can 't create a comment - Comments are disabled for
              this post";
        };

        -- Set the `approved` field on the new comment - automatically approve
        -- comments made by the author of the post.
        -- For security reasons, record users don't have any permissions for the `approved` field.
        UPDATE $after.id SET
            approved = $after.post.author == $after.author;
    };
```

## Accessing `$input` in events

The behaviour of events can be further refined via the `$input` parameter, which represents the record in question for the event.

```surql
-- Set CREATE in event to only trigger when record has `true` for `log_event`
DEFINE EVENT something ON person WHEN $input.log_event = true THEN {
    CREATE log SET at = time::now(), of = $input;
};

-- Set to `false`, does not trigger CREATE
CREATE person:debug SET name = "Billy", log_event = false;
-- Triggers CREATE
CREATE person:real SET name = "Bobby", log_event = true;

SELECT * FROM log;
```

Output:

```surql
[
	{
		at: d'2025-10-14T06:15:21.141Z',
		id: log:svbr2qhjywml20mufb0o,
		of: {
			log_event: true,
			name: 'Bobby'
		}
	}
]
```

## Async events

Events in SurrealDB are executed synchronously within the same transaction that triggers them. While this ensures consistency, it can lead to increased latency for write operations if the event logic is complex or resource intensive.

To allow events to execute independently of the transaction that triggers them, the `ASYNC` clause can be used.

### How async events are processed

Async events are processed in an interval dependent on the environment variable `SURREAL_ASYNC_EVENT_PROCESSING_INTERVAL` (or `--async-event-interval` when starting the server) which is set to 5 seconds as the default. Lowering this will reduce the latency between a document change and its events, while leading to more frequent polls by the background worker.

Some more notes on the characteristics of async events:

* Atomicity: The event is enqueued within the same transaction as the document change. If the transaction fails, the event is never queued.
* Consistency: Asynchronous events run in a separate transaction from the original change. They see the database state at the time they are executed.
* Ordering: Events are generally processed in the order they were created, though parallel processing may occur within a single batch.

The easiest way to demonstrate that async events do not occur in the same transaction is by causing one to [throw](/docs/reference/query-language/statements/throw.md) an error. As an error inside any part of a transaction will cause the transaction to fail and roll back, the following event which fails about 50% of the time would cause the `CREATE` statement that follows to fail if it were not async. As an async event, however, the events that follow the statement are each run in their own transaction.

```surql
DEFINE TABLE did_not_throw;

DEFINE EVENT may_throw ON person ASYNC THEN {
  IF rand::bool() {
      THROW "This message will never show";
  } ELSE {
    CREATE did_not_throw;  
  }
};

CREATE |person:50|;
count(SELECT * FROM did_not_throw);
```

### The `MAXDEPTH` clause

The `MAXDEPTH` clause is used to set the maximum number of times that an async event can be triggered. The number following this can range from 0 to 16.

The default for `MAXDEPTH` is 3, as events defined on other events that lead to record creation can quickly spiral out of control at greater levels.

Taking the following contrived example:

```surql
DEFINE EVENT start ON start THEN {
    CREATE cat;
};

DEFINE EVENT cat ON person ASYNC MAXDEPTH 4 THEN {
  CREATE |cat:9|;  
};

DEFINE EVENT person ON cat ASYNC MAXDEPTH 4 THEN {
  CREATE |person:9|;
};

CREATE start;

count(SELECT VALUE id FROM person, cat);
```

While the `MAXDEPTH` in this case is only one greater than the default, the sheer number of records created results in the final `count()` score being 20503, compared to 2278 if the default is used.

This is somewhat similar to recursive queries which can also quickly add up.

```surql
-- Create five people
CREATE |person:1..=5|;
-- Make each person friends with each of the four others
UPDATE person SET friends = (SELECT VALUE id
  FROM person).complement([$this.id]);
-- Count after five levels of depth is already 1024!
count(person:1.{..5}.friends);
```

### The `RETRY` clause

The `RETRY` clause is suitable for events that may fail but can succeed on successive attempts.

The example below shows two events that each have a 50% chance of failure and zero retries.

```surql
DEFINE EVENT one ON account ASYNC RETRY 0 THEN {
    IF rand::bool() {
        THROW "Failed!"
    } ELSE {
        CREATE it:worked SET very = "well";
    }
};

DEFINE EVENT two ON account ASYNC RETRY 0 THEN {
    IF rand::bool() {
        THROW "Failed!"
    } ELSE {
        CREATE it:worked SET very = "well";
    }
};

CREATE account;
```

Following up these events with a `SELECT * FROM it` will most likely lead to the following input.

```surql
[
	{
		id: it:worked,
		very: 'well'
	}
]
```

However, with zero retries there is still a 25% chance that the query will only ever lead to the error `"The table 'it' does not exist"`.

---

Source: https://surrealdb.com/docs/learn/schema-management/events-and-triggers/reactive-patterns

# Reactive patterns

Choosing between synchronous and asynchronous events, LIVE SELECT subscriptions, and evolving reactive behaviour safely.

[Events](/docs/learn/schema-management/events-and-triggers/defining-events.md) are SurrealDB's built-in way to react to changes: you attach logic that runs after a create, update, or delete using [`DEFINE EVENT`](/docs/reference/query-language/statements/define/event.md). They do not run during bulk [import](/docs/reference/cli/db/commands/import.md), so plan backfills separately or use the [sql](/docs/reference/cli/db/commands/sql.md) endpoint if you rely on them for derived data. When deciding how to define an event, consider the following:

* What should happen immediately, what can wait, and how do you keep the system predictable?

## Immediate vs deferred work

Every event introduces work triggered by a write. The first and most important decision is whether that work belongs in the same transaction as the write or outside it. The sections below mirror that split; the [`DEFINE EVENT`](/docs/reference/query-language/statements/define/event.md) reference describes `ASYNC`, `MAXDEPTH`, and `RETRY` in full.

### Strong consistency (synchronous events)

By default, events run in the same transaction as the triggering write (see [parameters](/docs/reference/query-language/language-primitives/parameters.md#before-after) such as `$before` and `$after` in the event body). That means:

* If the event fails, the original write is rolled back
* All changes are immediately consistent
* The write includes the cost of the event

You will want to use this pattern when:

* The follow-up must always succeed
* The database must never enter an inconsistent state
* The logic is lightweight and predictable

This might be the case when you need to enforce constraints across tables or when writing audit records that must exist. Remember that [queries inside events bypass permission checks](/docs/learn/schema-management/events-and-triggers/defining-events.md#events-and-permissions), so validate anything security-sensitive explicitly in the `THEN` block.

The tradeoff with this pattern is that event logic should stay as small as practical to avoid added latency on every write.

### Deferred processing (asynchronous events)

With [`ASYNC`](/docs/reference/query-language/statements/define/event.md#async-events), the event is queued and processed outside the original transaction. That implies:

* The write succeeds independently of the event
* The event runs in a separate transaction
* Failures do not roll back the original change

You will want to use this pattern when:

* The write must be fast
* The work is expensive or unreliable
* Temporary inconsistency is acceptable

For the background processing interval, [`MAXDEPTH`](/docs/reference/query-language/statements/define/event.md#the-maxdepth-clause), and [`RETRY`](/docs/reference/query-language/statements/define/event.md#the-retry-clause), see [Async events](/docs/reference/query-language/statements/define/event.md#async-events) in the `DEFINE EVENT` reference.

### `LIVE SELECT`

While different from an event, a [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md) is reactive in that it streams notifications when records matching the selection change. While an event embeds what the database should do in a `THEN` block, a live query only tells subscribers that something changed, so your app (or another service) decides how to react - typically in [SDK code](/docs/reference/javascript/concepts/live-queries.md) using the connection's live-query APIs.

A live query reads a change through the same parameters as an event. `$event`, `$before` and `$after` are set while it checks each changed record, so `LIVE SELECT * FROM order WHERE $event = 'DELETE'` notifies subscribers of deleted orders only, and a condition from an event's `WHEN` clause can usually be reused as a live query filter.

For behaviour on the wire, subscriptions, and production caveats, read [Live queries](/docs/learn/querying/real-time/live-queries.md). To end a subscription, use [`KILL`](/docs/reference/query-language/statements/kill.md) with the query UUID the server returns when you register the live select. [Real-time best practices](/docs/learn/querying/real-time/real-time-best-practices.md#defining-events) also contrasts live queries with defining events when the database can do the work without a client listener.

### Building reactive systems incrementally

The sheer convenience of reactive systems makes them tempting to use early. They are harder to debug when many are introduced together. For example, an error that occurs may be difficult to debug inside an [event](/docs/learn/schema-management/events-and-triggers/defining-events.md) that chains into further writes plus several [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md), especially if all of these reactive patterns were introduced all at once.

For this reason, it is often best to introduce reactive behaviour gradually.

A good rule of thumb is to do the following:

* Start with explicit logic, and build your core behaviour in straightforward queries or application code.
* Stabilise your data model and workflows.
* Make sure the system behaves correctly before introducing automation.
* Introduce reactivity where it adds clear value.
* Move well-understood logic into [events](/docs/learn/schema-management/events-and-triggers/defining-events.md) or [live queries](/docs/learn/querying/real-time/live-queries.md) once the behaviour is predictable.
* Avoid large or highly coupled chains of events.

## Records that expire

SurrealDB does not yet have a time-to-live (TTL) setting or a built-in scheduler, so a record stays until something deletes it. Two patterns cover most needs in the meantime: hide a record's value once it is too old, or delete old records as new ones arrive.

### Hiding expired values with a computed field

A [`COMPUTED`](/docs/learn/schema-management/computed-data/computed-fields.md) field is worked out each time the record is read, so it can compare the current time with a stored timestamp. The field below returns the invite code for an hour and `NONE` after that, and a second field says whether the invite has expired:

```surql
DEFINE FIELD created_at ON invite DEFAULT time::now();
DEFINE FIELD code ON invite COMPUTED IF time::now() - created_at < 1h { secret } ELSE { NONE };
DEFINE FIELD expired ON invite COMPUTED time::now() - created_at >= 1h;

CREATE invite:old SET secret = 'OLD123', created_at = time::now() - 2h;
CREATE invite:new SET secret = 'NEW456';

SELECT id, code, expired FROM invite;
```

```surql title="Output"
[
	{ code: 'NEW456', expired: false, id: invite:new },
	{ code: NONE, expired: true, id: invite:old }
]
```

A query can then leave expired records out with `WHERE !expired`. The expired record and its `secret` are still stored, so give `secret` a [field permission](/docs/reference/query-language/statements/define/field.md) if it must not be read directly.

### Deleting old records with an event

An [event](/docs/reference/query-language/statements/define/event.md) can delete the records that have expired each time a new record is created:

```surql
DEFINE FIELD created_at ON session DEFAULT time::now();
DEFINE INDEX session_created ON session FIELDS created_at;

DEFINE EVENT expire_old ON session WHEN $event = 'CREATE' THEN {
    DELETE session WHERE created_at < time::now() - 1h;
};

CREATE session:old SET created_at = time::now() - 2h;
CREATE session:recent SET created_at = time::now() - 10m;

SELECT VALUE id FROM session;
```

```surql title="Output"
[session:recent]
```

The expired `session:old` is deleted when `session:recent` is created. The index on `created_at` keeps the `DELETE` fast, because it runs on every create. Old records stay until the next record is created, so a table that stops receiving writes keeps them. For cleanup on a fixed schedule instead, run the `DELETE` statement from an external scheduler such as `cron`, for example with [`surreal db sql`](/docs/reference/cli/db/commands/sql.md).

---

Source: https://surrealdb.com/docs/learn/schema-management/files/buckets

# Buckets

Defining file buckets: memory, disk, cloud object storage (Amazon S3, S3-compatible, Google Cloud Storage, Azure Blob Storage), global backends, and permissions.

A bucket is named storage that backs file values (`f"bucket:/path"`). You have a few options when defining a bucket:

* Memory for non-persistent storage.
* A folder on disk for persistence.
* Cloud object storage: Amazon S3 or an S3-compatible service, Google Cloud Storage, or Azure Blob Storage.
* A global backend driven by environment variables.

Once a bucket is defined, you can then read and write to files inside it through the [file functions](/docs/reference/query-language/functions/database-functions/file.md).

Full statement detail: [`DEFINE BUCKET`](/docs/reference/query-language/statements/define/bucket.md). For how pointers look in queries, see [Working with files](/docs/learn/schema-management/files/working-with-files.md).

## Example usage

A bucket backend can be set as "memory" for non-persistent in-memory storage, as "file:/" followed by the path for storage on disk, or as an object storage URL for Amazon S3, an S3-compatible service, Google Cloud Storage or Azure Blob Storage.

### Memory backend

The simplest way to experiment with a bucket for files is by using the memory backend:

```surql
DEFINE BUCKET my_bucket BACKEND "memory";
```

Once this is defined, `my_bucket` can be accessed by using a file pointer: a path prefixed by an `f`.

```surql
-- Create a file by adding some content
f"my_bucket:/my_book.txt".put("Once there were four children whose names were Peter,
  Susan,
  Edmund,
  and Lucy.");
-- Copy it to a new file name
f"my_bucket:/my_book.txt".copy("lion_witch_wardrobe.txt");
-- Read the file as bytes
f"my_bucket:/lion_witch_wardrobe.txt".get();
-- Cast the bytes to a string
<string>f"my_bucket:/lion_witch_wardrobe.txt".get();
```

### File backend

A file backend can be chosen for a bucket by typing `"file:"` and then the rest of the path, if necessary.

```surql
DEFINE BUCKET my_bucket BACKEND "file:/some_directory";
DEFINE BUCKET my_bucket BACKEND "file:/some_directory";
```

A check will then be made to see if the `SURREAL_BUCKET_FOLDER_ALLOWLIST` environment variable contains the path, without which the following error will be generated.

```surql
'File access denied: /some_directory'
```

The following command can be used to start running an instance in which a bucket with a file backend can be defined.

```bash
# Unix
SURREAL_BUCKET_FOLDER_ALLOWLIST="/" surreal db start --user root --pass \
  secret --allow-experimental files

# Windows (PowerShell)
$env:SURREAL_BUCKET_FOLDER_ALLOWLIST = "/" 
surreal db start --user root --pass secret --allow-experimental files
```

### Cloud object storage backend

_(since v3.3.0)_

A bucket can keep its files in object storage by giving `BACKEND` the URL of the store. The scheme selects the service: `s3://`, `s3+https://` or `s3+http://` for Amazon S3 and S3-compatible services such as MinIO, Backblaze B2, Wasabi and Cloudflare R2, `gs://` or `gcs://` for Google Cloud Storage, and `az://` or `azure://` for Azure Blob Storage.

```surql
-- Amazon S3: with no host, requests go to the AWS endpoint for the region
DEFINE BUCKET invoices BACKEND "s3:/acme-invoices?region=eu-west-2&prefix=surrealdb/invoices";

-- An S3-compatible service: the host is the endpoint, and the bucket is the first path segment
DEFINE BUCKET uploads BACKEND "s3+http://surrealdb:strongPassword@localhost:9000/uploads";

-- Google Cloud Storage: the host is the bucket
DEFINE BUCKET reports BACKEND "gs://acme-reports?prefix=surrealdb&service_account=/etc/surrealdb/gcs-service-account.json";

-- Azure Blob Storage: the host is the storage account, then the container
DEFINE BUCKET media BACKEND "az://acmemedia/uploads?prefix=surrealdb";
```

Credentials can go in the URL, but when they are left out the server uses the standard credential sources for each provider, such as the `AWS_*`, `GOOGLE_*` and `AZURE_*` environment variables or an instance role. The URL is stored with the bucket definition and returned by `INFO FOR DB`, so credentials from the environment stay out of the schema. The [`DEFINE BUCKET` reference](/docs/reference/query-language/statements/define/bucket.md#cloud-object-storage-backends) lists every URL parameter and credential source.

These backends exist in native builds only. A WebAssembly build of the engine has the memory backend alone. The memory and file backends are unchanged.

### Global backend

A global backend can also be selected, allowing all namespaces and databases access to the same file storage.

If no backend is selected, the database will search for the environment variable `SURREAL_GLOBAL_BUCKET` and assign this as the global bucket. In this case, files will have a `namespace/database` prefix added (e.g. `my_global_bucket:/test_ns/test_db/somefile.txt`). A second `SURREAL_GLOBAL_BUCKET_ENFORCED` environment variable can also be used, which when set to `true` will enforce usage of the global bucket.

`SURREAL_GLOBAL_BUCKET` accepts the same URLs as `BACKEND`, including the cloud object storage schemes.

If a global backend is set, then a `DEFINE BUCKET` statement can be as short as `DEFINE BUCKET` plus its local name, as the rest of the logic is done via environment variables.

```surql
DEFINE BUCKET my_bucket;

-- Writes to e.g. `my_global_bucket:/test_ns/test_db/my_bucket/my_book.txt`
f"my_bucket:/my_book.txt".put("Once there were four children whose names were Peter,
  Susan,
  Edmund,
  and Lucy.");
```

## Setting permissions on buckets

By default, the permissions on a bucket will be set to FULL unless otherwise specified.

```surql
DEFINE BUCKET my_bucket BACKEND "memory";
INFO FOR DB;
```

```surql title="Output"
{
  accesses: {},
  analyzers: {},
  apis: {},
  buckets: {
    my_bucket: "DEFINE BUCKET my_bucket BACKEND 'memory'
      PERMISSIONS FULL"
  },
  configs: {},
  functions: {},
  models: {},
  modules: {},
  params: {},
  sequences: {},
  tables: {},
  users: {}
}
```

You can set permissions on buckets to control who can perform operations on the files stored in them using the `PERMISSIONS` clause. In the clause three additional variables are available:
- `$action`: The action to be executed (`put`, `get`, `head`, `delete`, `copy`, `rename`, `exists`, `list`)
- `$file`: The [file pointer](/docs/reference/query-language/language-primitives/data-types/files.md) of the file to be accessed
- `$target`: The target [file pointer](/docs/reference/query-language/language-primitives/data-types/files.md) in copy/rename operations

```surql
-- Set permissions for the bucket
DEFINE BUCKET admin_bucket BACKEND "memory"
  PERMISSIONS WHERE $auth.admin = true
```

---

Source: https://surrealdb.com/docs/learn/schema-management/files/working-with-files

# Working with files

File pointers, buckets, and putting or getting bytes from SurrealDB.

File values look like ordinary paths, but are prefixed with an `f` so SurrealDB knows you mean binary storage, not a plain string.

To use files, a bucket must first be defined with the [`DEFINE BUCKET`](/docs/learn/schema-management/files/buckets.md) statement. The easiest way to experiment with file storage is to start with a bucket that holds files in memory.

```surql
DEFINE BUCKET my_bucket BACKEND "memory";
```

Once a bucket has been defined, you can begin calling methods on file pointers such as `.put()` and `.get()` (see the [file functions](/docs/reference/query-language/functions/database-functions/file.md) reference).

```surql
DEFINE BUCKET my_bucket BACKEND "memory";
f"my_bucket:/some_file.txt".put("Some text inside");
f"my_bucket:/some_file.txt".get();
<string>f"my_bucket:/some_file.txt".get();
```

```surql title="Output"
-------- Query --------

b"536F6D65207465787420696E73696465"

-------- Query --------

'Some text inside'
```

## See also

A combination of files and SurrealDB's [encoding functions](/docs/reference/query-language/functions/database-functions/encoding.md#encodingcbordecode) 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.

**Bash**

```bash
# Set the allowlist env var to allow the directory to be accessed
SURREAL_BUCKET_FOLDER_ALLOWLIST="/users/your_user_name" surreal db start \
  --allow-experimental files
```

**PowerShell**

```powershell
# Set the allowlist env var to allow the directory to be accessed
$env:SURREAL_BUCKET_FOLDER_ALLOWLIST = "C:\Users\your_user_name"
surreal db start `
  --allow-experimental files
```

```surql
-- 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");
//- { 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");
//- { 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");
```

---

Source: https://surrealdb.com/docs/learn/schema-management/indexes/index-types-and-strategies

# Index types and strategies

Choosing indexes for lookups, uniqueness, full-text, vectors, and counts.

Indexes speed up the shapes of queries you run often: equality filters, uniqueness checks, text search, and similarity over vectors.

You need the same namespace/database scope and privileges as other `DEFINE` statements. Clause-by-clause detail can be found in the [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md) page.

## Index types

SurrealDB offers a range of indexing capabilities designed to optimise data retrieval and search efficiency.

### Standard (non-unique) index

An index with `FIELDS` or `COLUMNS` and no special clause (`UNIQUE`, `COUNT`, `FULLTEXT`, etc.) is a **standard B-tree index** - the default secondary index type.

Let's create a non-unique index for an age field on a user table.

```surql
-- optimise queries looking for users of a given age
DEFINE INDEX userAgeIndex ON TABLE user FIELDS age;
```

### Unique index

A unique index ensures that each value in the index is unique. A unique index helps enforce uniqueness across records by preventing duplicate entries in fields such as user IDs, email addresses, and other unique identifiers.

Let's create a unique index for the email address field on a user table.

```surql
-- Makes sure that the email address in the user table is always unique
DEFINE INDEX userEmailIndex ON TABLE user FIELDS email UNIQUE;
```

The created index can be viewed using the [`INFO` statement](/docs/reference/query-language/statements/info.md).

```surql
INFO FOR TABLE user;
```

The `INFO` statement will help you understand what indexes are defined in your `TABLE`.

```surql
{
    "events": {},
    "fields": {},
    "indexes": {
        "userEmailIndex": {
            sql: "DEFINE INDEX userEmailIndex ON user FIELDS email UNIQUE"
        }
    },
    "lives": {},
    "tables": {}
}
```

As we defined a `UNIQUE` index on the `email` field, a duplicate entry for that field will throw an error.

```surql
-- Create a user record and set an email ID.
CREATE user:1 SET email = 'test@surrealdb.com';
-- Create another user record and set the same email ID.
CREATE user:2 SET email = 'test@surrealdb.com';
```

```surql title="Output"
"Database index `userEmailIndex` already contains 'test@surrealdb.com',
with record `user:1`"
```

### Composite index

A composite index spans multiple fields of a table. With `UNIQUE`, it enforces uniqueness when the definition of what is unique pertains to more than one field.

```surql
-- Create an index on the account and email fields of the user table
DEFINE INDEX test ON user FIELDS account, email UNIQUE;
```

A composite index also serves queries. The query planner uses it for a `WHERE` clause that filters on its leading fields: `account = 'a'` alone, `account = 'a' AND email = 'x'`, or an equality on `account` followed by a range on the next field. A filter that skips the first field, such as `email = 'x'` on its own, cannot use the index. Use [`EXPLAIN`](/docs/reference/query-language/statements/select.md#the-explain-clause) to confirm which index a query uses.

> [!NOTE]
> Before SurrealDB 2.2.0, the query planner used a composite index only for conditions on its first field. Range conditions on a field after an equality prefix use the index from 3.0.0.

### Count index

_(since v3.0.0)_

A count index uses the `COUNT` special clause instead of the usual `FIELDS` / `COLUMNS` form (which defines a standard B-tree index). Use it with `count()` and `GROUP ALL`.

- **`COUNT`** - full-table counts: `SELECT count() FROM <table> GROUP ALL`.
- **`COUNT WHERE <condition>`** - filtered counts when the query `WHERE` exactly matches the index condition.

An unconditional count index is optional for full-table counts; SurrealDB already uses a `CountScan` fast path that counts keys without deserialising records. `COUNT WHERE` is most useful for repeated filtered counts without scanning the whole table.

```surql
DEFINE INDEX idx ON indexed_reading COUNT;

FOR $_ IN 0..100000 {
    CREATE reading SET temperature = rand::int(0, 10);
};

FOR $_ IN 0..100000 {
    CREATE indexed_reading SET temperature = rand::int(0, 10);
};

-- Wait a moment before running these two
-- queries to ensure the index is built
SELECT count() FROM reading GROUP ALL;
SELECT count() FROM indexed_reading GROUP ALL;
```

Filtered example:

```surql
DEFINE INDEX item_active_count ON item COUNT WHERE status = "active";

SELECT count() FROM item WHERE status = "active" GROUP ALL;
```

A **standard index** (`DEFINE INDEX … FIELDS …` on the same field) can also accelerate filtered counts when it fully covers the `WHERE` clause; you typically choose one approach, not both. For choosing between standard and count indexes (including storage tradeoffs), see [When to use which](/docs/reference/query-language/statements/define/indexes.md#when-to-use-which). For counting several statuses (`IN` lists vs per-status totals), see [Multiple conditions](/docs/reference/query-language/statements/define/indexes.md#multiple-conditions).

### Full-text search (`FULLTEXT`) index

Enables efficient searching through textual data, supporting advanced text-matching features like proximity searches and keyword highlighting.

The [Full-Text search](/docs/learn/data-models/full-text-search/overview.md) index helps implement comprehensive search functionalities in applications, such as searching through articles, product descriptions, and user-generated content.

Let's create a full-text search index for a `name` field on a `user` table.

```surql
-- Define an analyzer
DEFINE ANALYZER example_ascii TOKENIZERS class FILTERS ascii;
DEFINE INDEX userNameIndex ON TABLE user FIELDS name FULLTEXT ANALYZER example_ascii BM25 HIGHLIGHTS;
```

- `FULLTEXT`: enables full-text search on the specified field. Before SurrealDB 3.0.0 this clause was `SEARCH`, which 3.x rejects as a parse error.
- `ANALYZER ascii`: Uses a custom [analyzer](/docs/reference/query-language/statements/define/analyzer.md) called `example_ascii` which uses the class tokenizer and `ascii` filter to analyse the text input.
- `BM25`: Ranking algorithm used for relevance scoring.
- `HIGHLIGHTS`: Allows keyword highlighting in search results output when using the [`search::highlight`](/docs/reference/query-language/functions/database-functions/search.md#searchhighlight) function
- `FIELDS`: a full-text search index can only be used on one field at a time. To use full-text search on more than one field, use a separate `DEFINE INDEX` statement for each one.

## Rebuilding indexes

Indexes can be rebuilt using the [`REBUILD`](/docs/reference/query-language/statements/rebuild.md) statement. This can be useful when you want to update the index definition or when you want to rebuild the index to optimise performance.

You may want to rebuild an index over time to ensure that the index is up-to-date with the latest data in the table.

```surql
REBUILD INDEX userEmailIndex ON user;
```

## Using `CONCURRENTLY` clause

Building indexes can be lengthy and may time out before they're completed. Without `CONCURRENTLY`, `DEFINE INDEX` blocks until the index is ready. The `CONCURRENTLY` clause can be used when the statement should return immediately while the build runs in the background, during which progress can be monitored with [INFO FOR INDEX](/docs/reference/query-language/statements/info.md#index-information).

```surql
-- Create an INDEX concurrently
DEFINE INDEX test ON user FIELDS email CONCURRENTLY;
INFO FOR INDEX test ON user;
INFO FOR INDEX test ON user;
```

## The `DEFER` clause

> [!WARNING]
> `DEFER` is available in SurrealDB 2.5 through 2.x only. On 3.x the clause is rejected at parse time, and this section applies to 2.x deployments. On 3.x, use [`CONCURRENTLY`](/docs/reference/query-language/statements/define/indexes.md#using-concurrently-clause) to build an index in the background - note that it addresses initial builds, not the ongoing write-path queueing `DEFER` provided.

Index updates in SurrealDB occur synchronously during document operations. This ensures immediate consistency, in which all reads return the most recent write. However, this can become a bottleneck during high-volume parallel ingestion, leading to write-write conflicts and increased latency, particularly with Full-Text or Vector indexes.

The `DEFER` clause can be used in this case if eventual consistency is acceptable, namely a setting in which reads may return stale data for a short period, but will eventually converge to the most recent write. An index with this clause will be enqueued in a persistent background queue so that ingestion and indexing are decoupled.

```surql
DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase;
DEFINE INDEX title_index ON blog FIELDS title FULLTEXT ANALYZER simple BM25 HIGHLIGHTS DEFER;
```

Note: As unique indexes offer a guarantee that no records that contravene the index will ever exist, the `UNIQUE` clause cannot be used together with `DEFER`.

## Performance implications

When defining indexes, consider which fields are most frequently queried.

Indexes may improve the performance of SurrealQL statements. This may not be noticeable with small tables but it can be significant for large tables; especially when the indexed fields are used in the `WHERE` clause of a [`SELECT`](/docs/reference/query-language/statements/select.md) statement.

Indexes can also impact the performance of write operations ([INSERT](/docs/reference/query-language/statements/insert.md), [UPDATE](/docs/reference/query-language/statements/update.md), [DELETE](/docs/reference/query-language/statements/delete.md)) since the index needs to be updated accordingly, so the need for read performance has to be balanced against write performance.

---

Source: https://surrealdb.com/docs/learn/schema-management/multi-tenancy/namespace-and-database-architecture

# Namespace & database architecture

Layering namespaces and databases for isolation, security, and strict mode.

SurrealDB scopes data in two levels: a namespace holds one or more databases, and each database holds your tables and other resources. There is no fixed cap on how many namespaces or databases you create. Design is driven by who should be isolated from whom (tenants, environments, product lines) and who is allowed to administer each layer.

In fact, there are some social media platforms that even run by creating an entire database per user.

A more common SaaS pattern is one namespace per tenant so credentials and data never cross tenant boundaries; another pattern is one namespace per environment (`dev`, `staging`, `prod`) with multiple databases inside.

Namespaces are created by root users. Databases live inside a namespace and are created by root or namespace owners/editors once you execute a [`USE`](/docs/reference/query-language/statements/use.md) statement to move to that namespace.

Reference pages: [`DEFINE NAMESPACE`](/docs/reference/query-language/statements/define/namespace.md), [`DEFINE DATABASE`](/docs/reference/query-language/statements/define/database.md).

For convenience, a new running instance will create a new namespace and database that each have the name `main`. This can be disabled by passing in a flag or setting an environment variable when using the [`surreal db start`](/docs/reference/cli/db/environment-variables.md) command.

## Namespaces

### Example: create a namespace

Below shows how you can create a namespace using the `DEFINE NAMESPACE` statement.

```surql
DEFINE NAMESPACE platform_ltd;
```

## Databases

A database is where your application schema actually lives. Options such as `STRICT` control whether CRUD operations may create undefined resources implicitly.

### Example: create a database

Below shows how you can create a database using the `DEFINE DATABASE` statement.

```surql
-- Specify the namespace for the database
USE NS platform_ltd;

-- Define database
DEFINE DATABASE app_vitalsense;
```

## Defining a `STRICT` database

A strict database is one that does not allow a resource to be used unless it has already been defined. The default behaviour in SurrealDB works otherwise, by allowing statements like [CREATE](/docs/reference/query-language/statements/create.md), [INSERT](/docs/reference/query-language/statements/insert.md), and [UPSERT](/docs/reference/query-language/statements/upsert.md) to work.

```surql
CREATE some_new_table;
INFO FOR DATABASE.tables;
```

The output of the [INFO](/docs/reference/query-language/statements/info.md) statement shows that a table called `some_new_table` has been created with a few default clauses.

```surql
{
	some_new_table: 'DEFINE TABLE some_new_table TYPE ANY SCHEMALESS PERMISSIONS NONE'
}
```

Such an operation within a strict database is simply not allowed.

```surql
DEFINE DATABASE new_db STRICT;
USE DATABASE new_db;
CREATE some_new_table;
```

```surql title="Output"
"The table 'some_new_table' does not exist"
```

---

Source: https://surrealdb.com/docs/learn/schema-management/schema-design

# Schema design

What DEFINE does in SurrealDB. How to inspect what you have defined.

Almost everything structural in SurrealDB starts with `DEFINE`: namespaces, databases, tables, fields, indexes, functions, access methods, parameters, and more. The [DEFINE overview](/docs/reference/query-language/statements/define/overview.md) in the API docs shows the syntax for each statement. Each such resource also has an `ALTER` and `REMOVE` statement if you need to change or remove a definition.

## Seeing what is defined

A schema will quickly grow to the point that it is no longer possible to keep in your head. The [`INFO`](/docs/reference/query-language/statements/info.md) statement can be used to show what definitions exist. `INFO` statements can be used on a variety of resources, such as `INFO FOR DATABASE`, `INFO FOR TABLE table_name`, `INFO FOR INDEX index_name`, and so on.

```surql
DEFINE FIELD name ON TABLE person TYPE string COMMENT "Todo: add assertion for maximum length";
INFO FOR TABLE person;
```

```surql output="Response"
{
	events: {},
	fields: {
		name: "DEFINE FIELD name ON person TYPE string COMMENT 'Todo: add assertion for maximum length' PERMISSIONS FULL"
	},
	indexes: {},
	lives: {},
	tables: {}
}
```

Users and other global objects show up when you ask at database level:

```surql
DEFINE USER db_user ON DATABASE PASSWORD "strongpassword" ROLES OWNER;
DEFINE TABLE person SCHEMAFULL;
INFO FOR DB;
```

```surql output="Response"
{
	accesses: {},
	analyzers: {},
	functions: {},
	models: {},
	params: {},
	tables: {
		person: 'DEFINE TABLE person TYPE ANY SCHEMAFULL PERMISSIONS NONE'
	},
	users: {
		db_user: "DEFINE USER db_user ON DATABASE PASSHASH '[REDACTED]' ROLES OWNER"
	}
}
```

## Where to go next

* [Tables](/docs/learn/schema-management/tables-and-fields/tables.md) and [fields](/docs/learn/schema-management/tables-and-fields/fields-and-validation.md)
* [Schema best practices](/docs/learn/schema-management/schema-design/schema-best-practices.md)
* [Schema evolution](/docs/learn/schema-management/schema-design/schema-evolution.md)
* [SurrealKit schema migration](/docs/manage/schema-migration.md) - manage `.surql` schema files and apply them with sync or rollouts

---

Source: https://surrealdb.com/docs/learn/schema-management/schema-design/sample-industry-schemas

# Sample industry schemas

Copy-paste starter schemas for energy, finance, retail, medical, and other domains.

These snippets are starting points that are realistic enough to learn from, short enough to paste into SurrealDB Studio or to lift into [SurrealKit](/docs/manage/schema-migration.md) `.surql` schema files and then reshape. They mix **tables**, **relations**, **computed** fields, **events**, and **indexes** the way a real app might.

> [!NOTE]
> Many fields use [`COMPUTED`](/docs/learn/schema-management/computed-data/computed-fields.md) (SurrealDB 3.0.0 onward). On older versions, replace with a [`future`](/docs/reference/query-language/language-primitives/data-types/futures.md) and `VALUE { … }` as in the futures documentation.

## Adding to this page

Have a sample schema of your own that you'd like to add? If it's about 50 lines in length then feel free to [make a PR](https://github.com/surrealdb/docs.surrealdb.com/edit/main/src/content/doc-surrealdb/reference-guide/sample-industry-schemas.mdx) and we'll credit the addition with a link to your profile on a code hosting platform (e.g. GitHub, GitLab, Codeberg).

You can also [get in touch](/contact) with us if you'd like a sample schema that isn't in this page that fits the industry in which you work.

## Energy and manufacturing

### Project planning

A project management schema that demonstrates activity scheduling, milestone tracking, and dependency management using [graph relationships](/docs/learn/data-models/graph/overview.md). This schema shows how to model complex project workflows with interdependent tasks and progress tracking using [`COMPUTED` fields](/docs/learn/schema-management/computed-data/computed-fields.md) for calculated values.

```surql
DEFINE TABLE project;

-- Activities in a project schedule
DEFINE TABLE activity SCHEMAFULL;
DEFINE FIELD name         ON activity TYPE string;
DEFINE FIELD description  ON activity TYPE option<string>;
DEFINE FIELD start        ON activity TYPE datetime;
DEFINE FIELD end          ON activity TYPE datetime;
DEFINE FIELD duration     ON activity COMPUTED end - start;
DEFINE FIELD progress     ON activity TYPE float ASSERT $value IN 0.0..=1.0;
DEFINE FIELD assigned_to  ON activity TYPE option<record<employee>>;
DEFINE FIELD followed_by  ON activity COMPUTED <-depends_on<-activity;

-- Milestones
DEFINE TABLE milestone SCHEMAFULL;
DEFINE FIELD project      ON milestone TYPE record<project>;
DEFINE FIELD activities   ON milestone TYPE array<record<activity>>;
DEFINE FIELD name         ON milestone TYPE string;
DEFINE FIELD last_updated ON milestone VALUE time::now();
DEFINE FIELD progress     ON milestone COMPUTED math::mean(activities.progress);
DEFINE FIELD is_complete  ON milestone COMPUTED activities.all(|$a| $a.progress > 0.95);

-- Graph-style dependency links
DEFINE TABLE depends_on SCHEMAFULL TYPE RELATION IN activity OUT activity;
DEFINE TABLE activity_of SCHEMAFULL TYPE RELATION IN activity OUT project;

CREATE project:one SET name = "Construction project";

CREATE activity:one SET name = "Project kickoff", start = time::now(), end = time::now() + 2d, progress = 1.0;
CREATE activity:two SET name = "Pour concrete", start = time::now() + 90d, end = time::now() + 100d, progress = 0.0;
CREATE activity:three SET name = "Dry concrete", start = time::now() + 100d, end = time::now() + 107d, progress = 0.0;
CREATE activity:four SET name = "Build on top of concrete", start = time::now() + 107d, end = time::now() + 150d, progress = 0.0;

RELATE activity:two->depends_on->activity:one;
RELATE activity:three->depends_on->activity:two;
RELATE activity:four->depends_on->activity:three;
RELATE [activity:one,activity:two,activity:three, activity:four]->activity_of->project:one;

CREATE milestone:one SET project = project:one, activities = [activity:one], name = "Project start";
CREATE milestone:two SET project = project:one, activities = [activity:two, activity:three, activity:four], name = "Initial construction";

-- See all graph connections between activity and project records
SELECT *, ->? AS joins_to, <-? AS joined_from FROM activity, project;

-- View the current milestones
SELECT * FROM milestone;
```

### SCADA (oil and gas)

Industrial monitoring and control system schema for oil and gas operations. Demonstrates real-time sensor data collection, automated alert generation using [events](/docs/reference/query-language/statements/define/event.md), and [time-series data management](/docs/learn/data-models/time-series/overview.md) with composite keys. Shows how to handle flexible external data integration and [live query monitoring](/docs/reference/query-language/statements/live-select.md).

```surql
DEFINE TABLE sensor SCHEMAFULL;
DEFINE FIELD type ON sensor TYPE array<string> ASSERT $value ALLINSIDE ["pressure", "temperature", "flow", "level"];
DEFINE FIELD location         ON sensor TYPE point;

DEFINE TABLE reading SCHEMAFULL;
DEFINE FIELD id               ON reading TYPE [record<sensor>, datetime];
DEFINE FIELD pressure         ON reading TYPE float;
-- Optional telemetry values
DEFINE FIELD temperature      ON reading TYPE option<float>;
DEFINE FIELD humidity         ON reading TYPE option<float>;
-- Flexible object for weather or external data
DEFINE FIELD weather          ON reading TYPE option<object> FLEXIBLE;

DEFINE TABLE alert SCHEMAFULL;
DEFINE FIELD equipment    ON alert TYPE record<sensor>;
DEFINE FIELD severity     ON alert TYPE string ASSERT $value IN ["critical", "high", "medium", "low", "info"];
DEFINE FIELD message      ON alert TYPE string;
DEFINE FIELD triggered_at ON alert TYPE datetime;

-- Create a sensor
CREATE sensor:one SET type = ["temperature", "pressure"], location = (50.0, 50.0);
-- And a reading for the sensor
CREATE reading:[sensor:one, time::now()] SET 
    pressure = 600,
    -- JSON object sourced from somewhere else, `weather` field is a schemaless object so can be any object format
    weather = { "temperature": 17.4, "humidity": 52.0, "wind_speed": 12.8 };

-- Set up event to generate alerts
DEFINE EVENT alert_from_create ON reading WHEN $event = "CREATE" THEN {
    LET $source = $after.id[0];
    LET $time = $after.id[1];
    -- Select everything over the past 15 minutes up to but not including the present reading
    LET $recents_average = math::mean(SELECT VALUE pressure
      FROM reading:[$source, $time - 15m]..[$source, $time]);
    LET $drop = $recents_average - $after.pressure;
    IF $drop > 15 {
      CREATE alert SET
            equipment = $source,
            severity = "high",
            message = "Pressure drop over 15 PSI: drop of " + <string>$drop,
            triggered_at = time::now();
    };
};

-- Some readings with good values
FOR $_ IN 0..10 {
    -- Sleep to keep timestamp in IDs unique, consider a ULID instead if timestamps may not be unique
    sleep(10ns);
    CREATE reading:[sensor:one, time::now()] SET pressure = 600;
};
-- Pressure has suddenly dropped
CREATE reading:[sensor:one, time::now()] SET pressure = 500;

-- See the alert
SELECT * FROM alert;
-- Or use a LIVE SELECT for alerts: https://surrealdb.com/docs/reference/query-language/statements/live-select
LIVE SELECT * FROM alert;
```

### Risk management

Project risk assessment and mitigation tracking schema. Features temporal risk modelling with active/inactive periods, probability-impact calculations, and automated risk scoring using [`COMPUTED` fields](/docs/learn/schema-management/computed-data/computed-fields.md). Demonstrates [unique constraints](/docs/reference/query-language/statements/define/indexes.md#unique-index) and complex mathematical aggregations across related records.

```surql
DEFINE TABLE risk SCHEMAFULL;
DEFINE FIELD project        ON risk TYPE record<project> REFERENCE;
DEFINE FIELD description    ON risk TYPE string;
DEFINE FIELD category       ON risk TYPE string; -- e.g. "technical", "commercial", "regulatory"
DEFINE FIELD likelihood     ON risk TYPE float ASSERT $value IN 0.0..=1.0;
DEFINE FIELD maximum_impact ON risk TYPE int; -- in dollars, etc.
DEFINE FIELD start          ON risk TYPE datetime;
DEFINE FIELD end            ON risk TYPE datetime;
-- Use a computed field to calculate value on each SELECT
DEFINE FIELD active         ON risk COMPUTED time::now() IN start..=end;
-- Ensure no duplicate `risk` records exist for each project
DEFINE INDEX risk_name      ON risk FIELDS project, description UNIQUE;

-- See all total_impact
DEFINE FIELD total_risk_impact ON project COMPUTED
    math::sum(<~risk.map(|$risk| $risk.maximum_impact * $risk.likelihood));

-- See risks at the current date
DEFINE FIELD current_risk_impact ON project COMPUTED
    math::sum(<~risk.filter(|$r| $r.active).map(|$risk| $risk.maximum_impact * $risk.likelihood));

CREATE project:one;

CREATE risk SET
    project = project:one,
    description = "Migratory elk",
    category = "regulatory",
    likelihood = 0.9,
    start = d'2025-10-01',
    end = d'2025-12-15',
    maximum_impact = 1000000;

CREATE risk SET
    project = project:one,
    description = "Wildfires",
    category = "technical",
    likelihood = 0.5,
    start = d'2025-06-01',
    end =d'2025-10-15',
    maximum_impact = 10000000;

SELECT * FROM project;
```

### Supply chain and contract management

Vendor relationship and contract lifecycle management schema. Covers contract value tracking with change orders, deliverable management, and automated total commitment calculations using [`COMPUTED` fields](/docs/learn/schema-management/computed-data/computed-fields.md). Demonstrates complex financial calculations and status tracking across multiple related entities.

```surql
-- Vendors who supply goods or services
DEFINE TABLE vendor SCHEMAFULL;
DEFINE FIELD name ON vendor TYPE string;

-- Contracts awarded under a project
DEFINE TABLE contract SCHEMAFULL;
DEFINE FIELD project        ON contract TYPE record<project>;
DEFINE FIELD vendor         ON contract TYPE record<vendor>;
DEFINE FIELD title          ON contract TYPE string;
DEFINE FIELD original_value ON contract TYPE int;
DEFINE FIELD total_value    ON contract COMPUTED 
    original_value + math::sum(SELECT VALUE amount FROM change_order
      WHERE contract = $parent.id);
DEFINE FIELD currency ON contract TYPE "dollars" | "euro";
DEFINE FIELD start   ON contract TYPE datetime;
DEFINE FIELD end     ON contract TYPE datetime;

-- Deliverables expected under a contract
DEFINE TABLE deliverable SCHEMAFULL;
DEFINE FIELD contract    ON deliverable TYPE record<contract>;
DEFINE FIELD description ON deliverable TYPE string;
DEFINE FIELD due_date    ON deliverable TYPE datetime;
DEFINE FIELD received    ON deliverable TYPE option<datetime>;
DEFINE FIELD status      ON deliverable COMPUTED IF $parent.received { "complete" } ELSE { "pending" };

-- Change orders during a project
DEFINE TABLE change_order SCHEMAFULL;
DEFINE FIELD contract    ON change_order TYPE record<contract>;
DEFINE FIELD amount      ON change_order TYPE int;
DEFINE FIELD description ON change_order TYPE string;
DEFINE FIELD signed_on   ON change_order TYPE option<datetime>;

-- Total committed value of a project (sum of all contract values)
DEFINE FIELD total_commitment ON project COMPUTED
  math::sum((SELECT VALUE value FROM contract WHERE project = $parent.id));

CREATE project:one;
CREATE vendor:one SET name = "Good vendor";
    CREATE contract:one SET project = project:one, currency = "euro", start = d'2025-12-01', end = d'2026-01-01', original_value = 1000, title = "Services for so-and-so project", vendor = vendor:one;
CREATE change_order SET contract = contract:one, amount = 500, description = "Highway wasn't set up yet";
SELECT * FROM contract;
```

### Procure-to-pay (SAP-aligned starter)

Oil-and-gas-style **procure-to-pay** flow: WBS cost objects, purchase orders, vendor invoices, and payment terms (due on receipt through Net 45 and beyond). Each business object carries an **`sap_*`** external key so rows synced from **SAP PS / MM / FI-AP** can upsert without replacing SAP as the system of record. Continues the **`project`** and **`vendor`** tables from [Supply chain and contract management](#supply-chain-and-contract-management) above, or run this snippet on its own.

SAP payment terms are master data (arbitrary net days, cash discounts, calendar rules). Here you can store a normalised **`net_due`** [`duration`](/docs/reference/query-language/language-primitives/data-types/durations.md) for queries, and treat **`code`** as the authoritative key from SAP. Document-number **`ASSERT`** patterns below are deliberately generic (ten-digit numeric). Assertions on prefixes (for example that PO ranges must start with `45`) can be tightened once your connector documents a specific number range.

Demonstrates [literal unions](/docs/reference/query-language/language-primitives/data-types/literals.md), [`duration`](/docs/reference/query-language/language-primitives/data-types/durations.md), [graph relations](/docs/reference/query-language/statements/relate.md) with [`ENFORCED`](/docs/reference/query-language/statements/define/table.md#using-enforced-to-ensure-that-related-records-exist) endpoints, [record references](/docs/reference/query-language/language-primitives/record-references.md), [field assertions](/docs/reference/query-language/statements/define/field.md#asserting-rules-on-fields) on relation `in` / `out`, and [events](/docs/reference/query-language/statements/define/event.md) for lifecycle rules.

```surql
DEFINE TABLE project SCHEMAFULL;
DEFINE FIELD name ON project TYPE string ASSERT string::len($value) > 3;

DEFINE TABLE vendor SCHEMAFULL;
DEFINE FIELD name ON vendor TYPE string ASSERT string::len($value) > 3;

DEFINE TABLE payment_terms SCHEMAFULL;
DEFINE FIELD code ON payment_terms TYPE string ASSERT $value.len() IN 1..=10;
DEFINE FIELD net_due ON payment_terms TYPE duration;
DEFINE FIELD description ON payment_terms TYPE string;
DEFINE INDEX payment_terms_code ON payment_terms FIELDS code UNIQUE;

DEFINE FIELD sap_vendor_number ON vendor TYPE option<string> ASSERT $value = /[0-9]{10}/;
DEFINE FIELD default_payment_terms ON vendor TYPE option<record<payment_terms>>;

DEFINE TABLE wbs_element SCHEMAFULL;
DEFINE FIELD sap_id ON wbs_element TYPE string ASSERT $value.len() IN 1..=24;
DEFINE FIELD project ON wbs_element TYPE record<project> REFERENCE ON DELETE REJECT;
DEFINE FIELD parent ON wbs_element TYPE option<record<wbs_element>>;
DEFINE FIELD name ON wbs_element TYPE string ASSERT $value.len() > 0;
DEFINE INDEX wbs_sap_id ON wbs_element FIELDS sap_id UNIQUE;

-- Graph: WBS hierarchy and project membership (dual-write with record fields above)
DEFINE TABLE wbs_of SCHEMAFULL TYPE RELATION IN wbs_element OUT project ENFORCED;
ALTER FIELD in ON wbs_of ASSERT in.project = out;
ALTER FIELD out ON wbs_of ASSERT in.project = out;

DEFINE TABLE wbs_child_of SCHEMAFULL TYPE RELATION IN wbs_element OUT wbs_element ENFORCED;
ALTER FIELD in ON wbs_child_of ASSERT in != out
AND in.project = out.project
AND in.parent = out;
ALTER FIELD out ON wbs_child_of ASSERT in != out
AND in.project = out.project
AND in.parent = out;

DEFINE TABLE purchase_order SCHEMAFULL;
-- Ten-digit numeric document numbers are common; add a prefix (e.g. /^45/) per deployment.
DEFINE FIELD sap_po_number ON purchase_order TYPE string ASSERT $value = /[0-9]{10}/;
DEFINE FIELD vendor ON purchase_order TYPE record<vendor> REFERENCE ON DELETE REJECT;
DEFINE FIELD currency ON purchase_order TYPE "CAD" | "USD" | "EUR";
DEFINE FIELD status ON purchase_order TYPE "open" | "closed" | "cancelled" DEFAULT "open";
DEFINE FIELD issued_at ON purchase_order TYPE datetime;
DEFINE INDEX po_sap_number ON purchase_order FIELDS sap_po_number UNIQUE;

DEFINE TABLE ordered_from SCHEMAFULL TYPE RELATION IN purchase_order OUT vendor ENFORCED;
ALTER FIELD in ON ordered_from ASSERT in.vendor = out;
ALTER FIELD out ON ordered_from ASSERT in.vendor = out;

DEFINE TABLE po_line SCHEMAFULL;
DEFINE FIELD purchase_order ON po_line TYPE record<purchase_order> REFERENCE ON DELETE CASCADE;
DEFINE FIELD line_number ON po_line TYPE int ASSERT $value IN 0..=9990;
DEFINE FIELD description ON po_line TYPE string ASSERT $value.len() > 0;
DEFINE FIELD amount_cents ON po_line TYPE int ASSERT $value >= 100;
DEFINE FIELD wbs ON po_line TYPE record<wbs_element> REFERENCE ON DELETE REJECT;
DEFINE INDEX po_line_key ON po_line FIELDS purchase_order, line_number UNIQUE;

DEFINE TABLE charges SCHEMAFULL TYPE RELATION IN po_line OUT wbs_element ENFORCED;
ALTER FIELD in ON charges ASSERT in.wbs = out;
ALTER FIELD out ON charges ASSERT in.wbs = out;

DEFINE TABLE vendor_invoice SCHEMAFULL;
DEFINE FIELD sap_doc_number ON vendor_invoice TYPE string ASSERT $value = /[0-9]{10}/;
DEFINE FIELD purchase_order ON vendor_invoice TYPE record<purchase_order> REFERENCE ON DELETE REJECT;
DEFINE FIELD vendor ON vendor_invoice TYPE record<vendor> ASSERT vendor = purchase_order.vendor;
DEFINE FIELD amount_cents ON vendor_invoice TYPE int ASSERT $value >= 100;
DEFINE FIELD payment_terms ON vendor_invoice TYPE record<payment_terms>;
DEFINE FIELD baseline_date ON vendor_invoice TYPE datetime;
DEFINE FIELD due_date ON vendor_invoice TYPE datetime ASSERT $value >= baseline_date;
DEFINE FIELD status ON vendor_invoice TYPE "parked" | "approved" | "paid" | "blocked" DEFAULT "parked";
DEFINE INDEX invoice_sap_number ON vendor_invoice FIELDS sap_doc_number UNIQUE;

DEFINE TABLE invoices SCHEMAFULL TYPE RELATION IN vendor_invoice OUT purchase_order ENFORCED;
ALTER FIELD in ON invoices ASSERT in.purchase_order = out
AND in.vendor = out.vendor;
ALTER FIELD out ON invoices ASSERT in.purchase_order = out
AND in.vendor = out.vendor;

DEFINE EVENT po_must_have_lines ON purchase_order
    WHEN $event = "UPDATE"
    AND $before.status != "closed"
    AND $after.status = "closed"
    THEN {
        LET $lines = (
            SELECT id
            FROM po_line
            WHERE purchase_order = $after.id
        );
        IF $lines.len() = 0 {
            THROW "Cannot close a purchase order with no lines";
        };
    };

CREATE payment_terms:net30 SET
    code = "NT30",
    net_due = 30d,
    description = "Net 30 days";
CREATE payment_terms:net27 SET
    code = "Z027",
    net_due = 27d,
    description = "Net 27 days";
CREATE payment_terms:payrcpt SET
    code = "Z000",
    net_due = 0d,
    description = "Due on receipt";

CREATE project:pad3 SET name = "Pad 3 expansion";
CREATE wbs_element:pad3 SET
    sap_id = "EP-24-PAD3",
    project = project:pad3,
    name = "Pad 3 package";
CREATE wbs_element:pad3_civil SET
    sap_id = "EP-24-PAD3-CIVIL",
    project = project:pad3,
    parent = wbs_element:pad3,
    name = "Civil & earthworks";

RELATE wbs_element:pad3 -> wbs_of -> project:pad3;
RELATE wbs_element:pad3_civil -> wbs_of -> project:pad3;
RELATE wbs_element:pad3_civil -> wbs_child_of -> wbs_element:pad3;

CREATE vendor:welding SET
    name = "Northern Weld Services Ltd",
    sap_vendor_number = "0000100042",
    default_payment_terms = payment_terms:net30;
CREATE vendor:small SET
    name = "Camp Catering Co-op",
    sap_vendor_number = "0000100891",
    default_payment_terms = payment_terms:payrcpt;

CREATE purchase_order:450001 SET
    sap_po_number = "4500000123",
    vendor = vendor:welding,
    currency = "CAD",
    issued_at = d'2026-05-15';
RELATE purchase_order:450001 -> ordered_from -> vendor:welding;

CREATE po_line:line10 SET
    purchase_order = purchase_order:450001,
    line_number = 10,
    description = "Structural welding - pipe rack",
    amount_cents = 18500000,
    wbs = wbs_element:pad3_civil;
RELATE po_line:line10 -> charges -> wbs_element:pad3_civil;

CREATE vendor_invoice:510001 SET
    sap_doc_number = "5100000456",
    purchase_order = purchase_order:450001,
    vendor = vendor:welding,
    amount_cents = 9250000,
    payment_terms = payment_terms:net30,
    baseline_date = d'2026-06-01',
    due_date = d'2026-07-01',
    status = "approved";
RELATE vendor_invoice:510001 -> invoices -> purchase_order:450001;

-- Record fields (connector / tabular queries)
SELECT *,
purchase_order.{sap_po_number, status, vendor: vendor.name} AS po
FROM po_line
WHERE wbs = wbs_element:pad3_civil;

-- Graph: lines charging a WBS, then their POs and vendor
SELECT
    name,
    sap_id,
    <-charges<-po_line.{ line_number, amount_cents, sap_po_number: purchase_order.sap_po_number } AS lines,
    ->wbs_child_of->wbs_element.{ name, sap_id } AS parent_wbs
FROM wbs_element:pad3_civil;

SELECT
    sap_doc_number,
    amount_cents / 100 AS amount_dollars,
    due_date,
    status
FROM vendor_invoice
WHERE status IN ["parked", "approved"]
ORDER BY due_date;
```

### HSSE (health, safety, security, environment) incidents

Incident reporting and investigation schema using [graph relationships](/docs/learn/data-models/graph/overview.md). Models safety events as edges between employees and projects with severity classification and role identification. Demonstrates graph-style data modelling for incident tracking and analysis.

```surql
-- Projects and employees (nodes)
DEFINE TABLE project SCHEMAFULL;
DEFINE FIELD name ON project TYPE string;

DEFINE TABLE employee SCHEMAFULL;
DEFINE FIELD name ON employee TYPE string;

-- Edges: incident links employee → project
DEFINE TABLE incident SCHEMAFULL TYPE RELATION IN employee OUT project;

DEFINE FIELD severity     ON incident TYPE string ASSERT $value IN ["minor", "moderate", "major", "fatal"];
DEFINE FIELD type         ON incident TYPE string ASSERT $value IN ["safety", "environment", "security", "health"];
DEFINE FIELD description  ON incident TYPE string;
DEFINE FIELD occurred_at  ON incident TYPE datetime;
DEFINE FIELD role         ON incident TYPE string ASSERT $value IN ["witness", "injured", "involved"];

-- Create nodes
CREATE employee:one SET name = "John Doe";
CREATE employee:two SET name = "Sally Lee";
CREATE project:one  SET name = "Pad 3 Expansion";

-- Create incidents as edges with properties
RELATE employee:one->incident->project:one SET 
  severity = "moderate", 
  type = "safety",
  description = "Pinched hand during pipe fitting",
  occurred_at = time::now() - 5d,
  role = "injured";

RELATE employee:two->incident->project:one SET 
  severity = "moderate",
  type = "safety",
  description = "Pinched hand during pipe fitting",
  occurred_at = time::now() - 5d,
  role = "witness";

SELECT id, <-incident[WHERE severity = "moderate"]<-employee FROM project;
```

## Finance

### General bank schema (graph schema)

Multi-currency banking system using [graph relationships](/docs/learn/data-models/graph/overview.md). Demonstrates polymorphic account types (JPY, EUR, CAD, USD) with different field structures, customer-bank relationships, and [unique constraint enforcement](/docs/reference/query-language/statements/define/indexes.md#unique-index). Shows how to model financial relationships with type-specific behaviours.

```surql
DEFINE TABLE bank SCHEMAFULL;
DEFINE FIELD name     ON bank TYPE string;

DEFINE TABLE customer SCHEMAFULL;
DEFINE FIELD name ON customer TYPE string;

DEFINE TABLE customer_of TYPE RELATION IN customer OUT bank;
DEFINE FIELD since ON customer_of TYPE datetime VALUE time::now() READONLY;

DEFINE TABLE jpy SCHEMAFULL;
DEFINE FIELD amount ON jpy TYPE int DEFAULT 0;
DEFINE FIELD cent ON jpy TYPE int READONLY VALUE 0;

DEFINE TABLE eur SCHEMAFULL;
DEFINE FIELD amount ON eur TYPE int DEFAULT 0;
DEFINE FIELD cent   ON eur TYPE int ASSERT $value IN 0..=99;
DEFINE FIELD total  ON eur VALUE amount + (<float>cent / 100);

DEFINE TABLE cad SCHEMAFULL;
DEFINE FIELD amount ON cad TYPE int DEFAULT 0;
DEFINE FIELD cent   ON cad TYPE int ASSERT $value IN 0..=99;
DEFINE FIELD total  ON cad VALUE amount + (<float>cent / 100);

DEFINE TABLE usd SCHEMAFULL;
DEFINE FIELD amount ON usd TYPE int DEFAULT 0;
DEFINE FIELD cent   ON usd TYPE int ASSERT $value IN 0..=99;
DEFINE FIELD total  ON usd VALUE amount + (<float>cent / 100);

DEFINE TABLE account TYPE RELATION IN customer OUT jpy|eur|cad|usd;
DEFINE FIELD since ON account TYPE datetime VALUE time::now() READONLY;

-- stop the same customer opening two wallets in the same currency
DEFINE INDEX unique_wallet ON account FIELDS in, out UNIQUE;

CREATE bank:one SET name = "Central Bank";
CREATE customer:one SET name = "Billy";
RELATE customer:one->customer_of->bank:one;
RELATE customer:one->account->(CREATE ONLY eur SET amount = 100, cent = 50);
RELATE customer:one->account->(CREATE ONLY jpy SET amount = 10000);

SELECT ->account->eur.total FROM customer:one;
```

### Other bank-customer schema

Traditional bank-customer schema with advanced features including [record references](/docs/reference/query-language/language-primitives/record-links.md#record-references), automated cent handling through [events](/docs/reference/query-language/statements/define/event.md), and historical interest rate tracking. Demonstrates event-driven data validation, [parameter usage](/docs/reference/query-language/statements/define/param.md), and complex relationship management with reference fields.

```surql
DEFINE PARAM $CURRENCIES VALUE ["EUR", "JPY", "USD", "CAD"];

DEFINE TABLE account SCHEMAFULL;
DEFINE FIELD customer ON account TYPE record<customer> REFERENCE;
DEFINE FIELD currency ON account TYPE string ASSERT $value IN $CURRENCIES;
DEFINE FIELD amount   ON account TYPE int;
DEFINE FIELD cent     ON account TYPE option<int>;

DEFINE TABLE customer SCHEMAFULL;
DEFINE FIELD name     ON customer TYPE string;
DEFINE FIELD bank     ON customer TYPE record<bank> REFERENCE;

DEFINE TABLE bank SCHEMAFULL;
DEFINE FIELD name         ON bank TYPE string;
DEFINE FIELD code         ON bank TYPE string;  -- e.g., BIC or internal short code
DEFINE FIELD swift        ON bank TYPE option<string>;
DEFINE FIELD supported_currencies ON bank TYPE set<string> ASSERT $value ALLINSIDE $CURRENCIES;
DEFINE FIELD interest_rate ON bank TYPE float DEFAULT 0.0;
DEFINE FIELD historical_interest_rates ON bank TYPE array<{ rate: float, set_at: datetime }> DEFAULT [];
DEFINE FIELD customers ON bank COMPUTED <~customer;

-- No assert for cent field, but event to update when > 100 or < 0
DEFINE EVENT update_cents ON account WHEN $event = "UPDATE" THEN {
    IF cent > 99 {
        UPDATE $after SET cent -= 100, amount += 1;
    } ELSE IF cent < 0 {
        UPDATE $after SET cent += 100, amount -= 1;
    }
};

-- No assert for cent field, but event to update when > 100 or < 0
DEFINE EVENT update_interest_rate ON bank WHEN $event = "UPDATE" THEN {
    IF $before.interest_rate != $after.interest_rate {
        UPDATE $this SET historical_interest_rates += { rate: $after.interest_rate, set_at: time::now() };
    }
};

CREATE bank:one SET name = "Bank of One", code = "ONEBANK", supported_currencies = ["EUR", "JPY"];
UPDATE bank:one SET interest_rate = 5.0;
CREATE customer:one SET bank = bank:one, name = "Galen Pathwarden";
CREATE account:one SET customer = customer:one, currency = "JPY", amount = 10000;

SELECT *, customers.{id, name} FROM bank;
```

### Customers and money transfers

Secure money transfer system with credit-based limits and transaction logging. Features [custom functions](/docs/reference/query-language/statements/define/function.md) for atomic transfers, credit level enforcement, and audit trails. Demonstrates transaction safety, business rule enforcement, and financial data integrity.

```surql
DEFINE TABLE customer SCHEMAFULL;
-- trusted customers can have greater negative amounts
DEFINE FIELD amount ON customer ASSERT $value >= -1000 * credit_level;
DEFINE FIELD credit_level ON customer TYPE int ASSERT $value IN 0..=5;

-- Logs for money transfers
DEFINE TABLE transfer SCHEMAFULL;
DEFINE FIELD from     ON transfer TYPE record<customer>;
DEFINE FIELD to       ON transfer TYPE record<customer>;
DEFINE FIELD amount   ON transfer TYPE int;
DEFINE FIELD ts       ON transfer TYPE datetime DEFAULT time::now();

DEFINE FUNCTION fn::send_money($from: record<customer>, $to: record<customer>, $amount: int) -> record<transfer> {
-- Use manual transaction for all statements so all changes are rolled back
-- if something is wrong
    BEGIN;
    IF $amount < 1 {
        THROW "Can't send less than 1 ";
    };
    UPDATE $from SET amount -= $amount;
    UPDATE $to SET amount += $amount;
    LET $tx = CREATE ONLY transfer SET from = $from, to = $to, amount = $amount;
    COMMIT;
-- Return the transfer record as a receipt the caller can use
    $tx.id
};

CREATE customer:one SET amount = 100, credit_level = 0;
CREATE customer:two SET amount = 500, credit_level = 5;

-- customer:one has bad credit, can't be negative
fn::send_money(customer:one, customer:two, 500);
-- but customer:two can
fn::send_money(customer:two, customer:one, 1000);

SELECT * FROM customer;
SELECT * FROM transfer;
```

### Loans and repayments

Loan management system with automated interest calculations and repayment scheduling. Features [parameterised loan terms](/docs/reference/query-language/statements/define/param.md), mathematical payment calculations using [custom functions](/docs/reference/query-language/statements/define/function.md), and status tracking. Demonstrates complex financial formulas, temporal data management, and regulatory compliance constraints.

```surql
-- Some government-set maximum term for loans
DEFINE PARAM $MAX_TERM VALUE 84;

DEFINE TABLE loan SCHEMAFULL;
DEFINE FIELD customer      ON loan TYPE record<customer>;
DEFINE FIELD principal     ON loan TYPE int; -- Total borrowed, in cents
DEFINE FIELD interest_rate ON loan TYPE float; -- e.g., 5.5 for 5.5%
DEFINE FIELD issued_at     ON loan TYPE datetime;
-- loans issuable at units of 6 months each
DEFINE FIELD term_months   ON loan TYPE int ASSERT $value % 6 = 0
  AND $value <= $MAX_TERM;
DEFINE FIELD balance       ON loan TYPE int; -- Remaining amount to repay
DEFINE FIELD status        ON loan TYPE string ASSERT $value IN ["active", "paid", "defaulted"];

DEFINE TABLE repayment SCHEMAFULL;
DEFINE FIELD loan       ON repayment TYPE record<loan>;
DEFINE FIELD due_date   ON repayment TYPE datetime;
DEFINE FIELD amount     ON repayment TYPE int;
DEFINE FIELD paid       ON repayment TYPE bool DEFAULT false;
DEFINE FIELD paid_at    ON repayment TYPE option<datetime>;
DEFINE FIELD method     ON repayment TYPE option<string>; -- e.g., "auto", "manual"

FOR $loan IN SELECT * FROM loan {
    LET $update_rate = 1 + ($loan.interest_rate / 365);
    UPDATE $loan SET balance = <int>math::round(balance * $update_rate);
};

DEFINE FUNCTION fn::repayment_amount($loan: record<loan>) -> float {
    LET $P = $loan.principal;
    LET $annual = $loan.interest_rate / 100;
    LET $r = $annual / 12;              -- Monthly interest rate
    LET $n = $loan.term_months;

    math::round(
        $P * $r / (1 - math::pow(1 + $r, -$n))
    );
};

CREATE customer:one;
CREATE loan:one SET customer = customer:one, principal = 5000000, interest_rate = 5.0, issued_at = time::now(), term_months = 12, balance = 5000000, status = "active";
UPDATE loan:one SET balance -= fn::repayment_amount(loan:one);
```

### Fraud prevention patterns

Anti-fraud detection system using [events](/docs/reference/query-language/statements/define/event.md) and temporal analysis. Implements velocity checks, new account restrictions, and suspicious transaction pattern detection. Demonstrates real-time fraud prevention, temporal constraints, and complex business rule enforcement through database events.

```surql
DEFINE FIELD created_at ON account VALUE time::now() READONLY;
DEFINE EVENT cancel_high_volume
  ON TABLE sends WHEN $event = "CREATE" THEN {
    IF $after.amount > 1000 AND time::now() - $after.in.created_at < 1d {
        THROW "New accounts can only send up to $1000 per transaction";
    }
};

DEFINE FIELD sent_at ON TABLE sends VALUE time::now() READONLY;

DEFINE EVENT cancel_rapid_transactions
  ON TABLE sends WHEN $event = "CREATE" THEN {
    LET $sender = $after.in;
    LET $receiver = $after.out;
    -- Disallow more than two transactions within a 5 minute period
    LET $recents = 
        $sender->sends[WHERE out = $receiver]
        .filter(|$tx| time::now() - $tx.sent_at < 5m);
    IF $recents.len() > 2 {
        THROW "Can't send that many times within a short period of time";
    };
};
```

### Using SurrealDB Studio's graph visualisation to see fraudulent activities

For more on these queries and their visual output, see [this dedicated blog post](/blog/fraud-detection-with-surrealdb).

Star pattern: one card used to pay large number of accounts:

```surql
DEFINE FIELD paid_at ON pays DEFAULT time::now();

-- sketchy cards
FOR $card IN CREATE |card:10| {
    FOR $_ IN 0..rand::int(5, 15) {
        LET $payee = UPSERT ONLY account;
        RELATE $card->pays->$payee SET amount = rand::int(100, 1000);    
    };
};

-- regular card
CREATE card:normal;
FOR $_ IN 0..rand::int(5, 15) {
    LET $payee = UPSERT ONLY account;
    RELATE card:normal->pays->$payee SET amount = rand::int(100, 1000), paid_at = time::now() - rand::duration(1d, 100d);
};

SELECT id, ->pays.filter(|$payment| time::now() - $payment.paid_at < 1d).out FROM card;
```

Tight communities that interact mostly among themselves:

```surql
-- Regular community of 200
CREATE |account:200|;
-- Smaller community that interacts among itself
CREATE |account:5| SET is_sketchy = true;

-- The sketchy community interacts only between itself
-- the regular community has more general interactions
-- and sometimes sends money to the sketchy accounts
FOR $account IN SELECT * FROM account {
    FOR $_ IN 0..10 {
        LET $counterpart = IF $account.is_sketchy {
            rand::enum(SELECT * FROM account WHERE is_sketchy)
        } ELSE {
            rand::enum(SELECT * FROM account)
        };
        RELATE $account->sends_to->$counterpart SET amount = rand::int(100, 1000);
    }
};

SELECT id, ->sends_to->account FROM account;
```

Circles showing loops of money returning to its origin:

```surql
CREATE |account:50|;
CREATE |account:1..16| SET is_sketchy = true;

FOR $sketchy IN SELECT * FROM account WHERE is_sketchy {
    LET $counterpart = rand::enum(SELECT * FROM account WHERE is_sketchy
      AND !<-sent);
    RELATE $sketchy->sent->$counterpart SET amount = rand::int(100, 1000);
};

LET $normal = SELECT * FROM account WHERE !is_sketchy;
FOR $account IN SELECT * FROM account WHERE !is_sketchy {
    LET $counterpart = rand::enum(SELECT * FROM $normal);
    RELATE $account->sent->$counterpart SET amount = rand::int(100, 1000);
};

SELECT id, ->sent->account FROM account;
```

## Gaming

### Characters and quests

RPG game system with character progression, inventory management, and quest tracking. Features polymorphic item effects, character statistics, and complex game state management. Demonstrates flexible data modelling for gaming applications with rich object structures and [relationship tracking](/docs/learn/data-models/graph/overview.md).

```surql
-- Characters controlled by players
DEFINE TABLE character SCHEMAFULL;
DEFINE FIELD name     ON character TYPE string;
DEFINE FIELD level    ON character TYPE int DEFAULT 1;
DEFINE FIELD xp       ON character TYPE int DEFAULT 0;
DEFINE FIELD class    ON character TYPE string ASSERT $value IN ["warrior", "mage", "rogue"];
DEFINE FIELD stats    ON character TYPE { str: int, dex: int, int: int };

-- Items in the game world
DEFINE TABLE item SCHEMAFULL;
DEFINE FIELD name     ON item TYPE string;
DEFINE FIELD type     ON item TYPE string ASSERT $value IN ["weapon", "armor", "potion"];
DEFINE FIELD rarity   ON item TYPE string ASSERT $value IN ["common", "rare", "epic", "legendary"];
DEFINE FIELD effects  ON item TYPE array<{ str: int } | { int: int } | { heal: int }>; // etc.

-- Items possessed by characters
DEFINE TABLE owns TYPE RELATION IN character OUT item;
DEFINE FIELD equipped ON owns TYPE bool DEFAULT false;

-- Quests available in the world
DEFINE TABLE quest SCHEMAFULL;
DEFINE FIELD name      ON quest TYPE string;
DEFINE FIELD required_level ON quest TYPE int DEFAULT 1;
DEFINE FIELD rewards   ON quest TYPE { exp: int, items: array<record<item>> };

-- Character quest progress
DEFINE TABLE quest_log TYPE RELATION IN character OUT quest;
DEFINE FIELD status       ON quest_log TYPE string ASSERT $value IN ["active", "completed"];
DEFINE FIELD started_at   ON quest_log TYPE datetime DEFAULT time::now();
DEFINE FIELD completed_at ON quest_log TYPE option<datetime>;

-- Events
DEFINE TABLE character_event SCHEMAFULL;
DEFINE FIELD character  ON character_event TYPE record<character>;
DEFINE FIELD details    ON character_event TYPE 
    { type: "combat", exp: int, against: string, summary: string } |
    { type: "item_used", item: record<item>, summary: string } |
    { type: "quest_update", summary: string };
DEFINE FIELD ts         ON character_event TYPE datetime DEFAULT time::now();

-- Create a new character
CREATE character:aria SET name = "Aria", class = "mage", stats = { str: 4, dex: 6, int: 12 };

-- Give Aria an item
RELATE character:aria->owns->(CREATE ONLY item SET name = "Wand of Sparks", type = "weapon", rarity = "rare", effects = [{ int: 2 }]);

-- Start a quest
RELATE character:aria->quest_log->quest:slime_hunt SET status = "active";
```

## Aerospace and astronomy

### Telescopes and observations

Astronomical observation tracking system with instrument management and data collection. Features geospatial telescope locations, flexible observation metadata, and scientific data URL management. Demonstrates [point data types](/docs/reference/query-language/language-primitives/data-types/geometries.md#point), complex temporal relationships, and scientific data organisation patterns.

```surql
-- Telescopes (instruments)
DEFINE TABLE telescope SCHEMAFULL;
DEFINE FIELD name        ON telescope TYPE string;
DEFINE FIELD location    ON telescope TYPE point;
DEFINE FIELD aperture_mm ON telescope TYPE int; -- e.g. 200 for 8" scope

-- Astronomical targets
DEFINE TABLE target SCHEMAFULL;
DEFINE FIELD name        ON target TYPE string;
DEFINE FIELD type        ON target TYPE string ASSERT $value IN ["star", "planet", "nebula", "galaxy", "asteroid"];

-- Observation logs
DEFINE TABLE observed SCHEMAFULL TYPE RELATION IN telescope OUT target;
DEFINE FIELD observer        ON observed TYPE record<person>;
DEFINE FIELD observed_at     ON observed TYPE datetime;
DEFINE FIELD observed_until  ON observed TYPE option<datetime>;
DEFINE FIELD exposure_length ON observed VALUE IF observed_until { observed_until - observed_at } ELSE { 0ns };
DEFINE FIELD seeing          ON observed TYPE option<float>; -- arcseconds
DEFINE FIELD notes           ON observed TYPE option<string>;
DEFINE FIELD filter          ON observed TYPE option<string> ASSERT $value IN ["B", "V", "R", "I", "H-alpha", "OIII", "IR"];
DEFINE FIELD sky_conditions  ON observed TYPE option<string> ASSERT $value IN ["clear", "thin cloud", "hazy", "overcast"];
DEFINE FIELD data_url        ON observed TYPE option<string>; -- e.g. to FITS file, rendered image, or DOI

CREATE telescope:one SET name = "The one telescope", location = (-68.44, -29.14), aperture_mm = 200;
CREATE target:venus SET type = "planet", name = "Venus";
CREATE person:one;

RELATE telescope:one->observed->target:venus SET 
    observer = person:one,
    observed_at = time::now(),
    observed_until = time::now() + 1h,
    filter = "R",
    seeing = 0.7,
    sky_conditions = "clear",
    data_url = "https://astro.example.org/data/venus-2025.fits";

```

### Launch telemetry

Space launch monitoring system with real-time telemetry data collection. Features component-level tracking, [time-series data management](/docs/learn/data-models/time-series/overview.md) with composite keys, and launch lifecycle status tracking. Demonstrates high-frequency data ingestion, temporal range queries, and [live data streaming](/docs/reference/query-language/statements/live-select.md).

```surql
-- A specific launch instance (e.g., Falcon 9 Flight 100)
DEFINE TABLE launch SCHEMAFULL;
DEFINE FIELD name         ON launch TYPE string;
DEFINE FIELD vehicle_name ON launch TYPE option<string>;
DEFINE FIELD scheduled_at ON launch TYPE datetime;
DEFINE FIELD liftoff_at   ON launch TYPE option<datetime>;
DEFINE FIELD status       ON launch TYPE string ASSERT $value IN ["scheduled", "launched", "scrubbed", "failed", "success"] DEFAULT "scheduled";
DEFINE FIELD completed    ON launch TYPE option<datetime>;
 
-- Components involved in the launch
DEFINE TABLE component SCHEMAFULL;
DEFINE FIELD launch     ON component TYPE record<launch>;
DEFINE FIELD name       ON component TYPE string; -- e.g., "first_stage", "engine_1"
DEFINE FIELD type       ON component TYPE string ASSERT $value IN ["stage", "engine", "payload", "fairing"];

-- Time-series telemetry linked to a component
DEFINE TABLE telemetry SCHEMAFULL;
DEFINE FIELD id            ON telemetry TYPE [record<component>, datetime]; -- [component, ulid]
DEFINE FIELD altitude_m    ON telemetry TYPE option<float>;
DEFINE FIELD velocity_mps  ON telemetry TYPE option<float>;
DEFINE FIELD thrust_kN     ON telemetry TYPE option<float>;
DEFINE FIELD pressure_kPa  ON telemetry TYPE option<float>;
DEFINE FIELD temperature_C ON telemetry TYPE option<float>;
DEFINE FIELD status        ON telemetry TYPE option<string>;

CREATE launch:one SET name = "Launch 1", vehicle_name = "Fire rocket", scheduled_at = time::now() - 5s, liftoff_at = time::now() - 1s;
CREATE component:one SET launch = launch:one, name = "Engine 1", type = "engine";
CREATE component:two SET launch = launch:one, name = "Engine 2", type = "engine";

-- Add durations to all datetimes below to simulate passage of time
CREATE telemetry:[component:one, time::now()] SET temperature_C = 30.5, status = "good";
CREATE telemetry:[component:one, time::now() + 1s] SET temperature_C = 30.7, status = "good";
CREATE telemetry:[component:one, time::now() + 2s] SET temperature_C = 30.9, status = "good";
CREATE telemetry:[component:one, time::now() + 3s] SET temperature_C = 35.0, status = "good";
CREATE telemetry:[component:two, time::now()] SET temperature_C = 30.5, status = "good";
CREATE telemetry:[component:two, time::now() + 1s] SET temperature_C = 30.7, status = "good";
CREATE telemetry:[component:two, time::now() + 2s] SET temperature_C = 30.9, status = "good";
CREATE telemetry:[component:two, time::now() + 3s] SET temperature_C = 35.0, status = "good";

UPDATE launch:one SET completed = time::now() + 5s;

-- Get all telemetry for component:two during launch:one
SELECT * FROM telemetry:[component:two, launch:one.liftoff_at]..=[component:two, launch:one.completed];

-- Or LIVE SELECT during the flight
LIVE SELECT * FROM telemetry WHERE id[0] = component:two;
```

## Defense / mission operations

### Missions and tasks

Military mission management system with unit tracking and operational logging. Features hierarchical command structure, real-time status updates, and audit trails. Demonstrates organisational modelling and [geospatial tracking](/docs/reference/query-language/language-primitives/data-types/geometries.md#point).

```surql
-- Mission-level directive
DEFINE TABLE operation SCHEMAFULL;
DEFINE FIELD name        ON operation TYPE string;
DEFINE FIELD status      ON operation TYPE string ASSERT $value IN ["planned", "active", "complete", "aborted"] DEFAULT "planned";
DEFINE FIELD commander   ON operation TYPE option<record<person>>;
DEFINE FIELD start_time  ON operation TYPE option<datetime>;
DEFINE FIELD end_time    ON operation TYPE option<datetime>;

DEFINE TABLE unit SCHEMAFULL;
DEFINE FIELD members     ON unit TYPE array<record<person>>;
DEFINE FIELD operation   ON unit TYPE record<operation>;
DEFINE FIELD name        ON unit TYPE string; -- e.g., "drone-2", "squad-a"
DEFINE FIELD type        ON unit TYPE string ASSERT $value IN ["drone", "vehicle", "infantry", "support"];
DEFINE FIELD status      ON unit TYPE string ASSERT $value IN ["ready", "deployed", "engaged", "inactive"];

-- Time-stamped unit log (e.g., movement, engagement, report)
DEFINE TABLE log SCHEMAFULL;
DEFINE FIELD id          ON log TYPE [record<unit>, datetime]; -- [unit, timestamp]
DEFINE FIELD message     ON log TYPE string;
DEFINE FIELD status      ON log TYPE option<string>; -- e.g., "engaged", "moving", "waiting"
DEFINE FIELD lonlat      ON log TYPE option<point>;
DEFINE FIELD visibility  ON log TYPE option<string> ASSERT $value IN ["clear", "obscured", "night"];

-- Tasks assigned within a mission
DEFINE TABLE task SCHEMAFULL;
DEFINE FIELD operation   ON task TYPE record<operation>;
DEFINE FIELD name        ON task TYPE string;
DEFINE FIELD objective   ON task TYPE string;
DEFINE FIELD assigned_to ON task TYPE option<array<record<unit>>>;
DEFINE FIELD priority    ON task TYPE string ASSERT $value IN ["high", "medium", "low"];
DEFINE FIELD completed   ON task TYPE bool DEFAULT false;

CREATE operation:alpha SET name = "Operation Alpha", commander = person:one, start_time = time::now();

CREATE unit:squad1 SET operation = operation:alpha, name = "squad-1", type = "infantry", status = "deployed", members = [person:one, person:two];
CREATE unit:drone1 SET operation = operation:alpha, name = "drone-1", type = "drone", status = "ready", members = [person:three, person:four];

CREATE task SET 
  operation = operation:alpha, 
  name = "Secure Ridge", 
  objective = "Clear hilltop sector", 
  assigned_to = [unit:squad1], 
  priority = "high";

-- Log messages (simulate time with + durations)
CREATE log:[unit:squad1, time::now()] SET message = "Entered zone", status = "moving", lonlat = (44.2, 6.3);
CREATE log:[unit:squad1, time::now() + 3m] SET message = "Engaged hostiles", status = "engaged", visibility = "clear";
CREATE log:[unit:drone1, time::now()] SET message = "Recon sweep complete", status = "waiting", lonlat = (44.3, 6.2);
```

## Retail

### People, products and commerce

E-commerce platform schema with customer profiles, product catalogue, and shopping cart management. Features flexible address storage, multi-currency support, and timestamp tracking. Demonstrates e-commerce data modelling with [flexible object fields](/docs/reference/query-language/language-primitives/data-types/objects.md#flexible-objects) and relationship management.

```surql
-- Person / customer profile
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name     ON person TYPE string;
DEFINE FIELD email    ON person TYPE string ASSERT string::is_email($value);
DEFINE FIELD address  ON person TYPE object;
DEFINE FIELD time     ON person TYPE object;
DEFINE FIELD time.created_at ON person TYPE datetime DEFAULT time::now();
DEFINE FIELD time.updated_at ON person TYPE datetime VALUE time::now();

-- Payment method linked to person
DEFINE TABLE payment_details SCHEMAFULL;
DEFINE FIELD person          ON payment_details TYPE record<person>;
DEFINE FIELD stored_cards    ON payment_details TYPE array<object>;
DEFINE FIELD time            ON payment_details TYPE object;
DEFINE FIELD time.created_at ON payment_details TYPE datetime DEFAULT time::now();
DEFINE FIELD time.updated_at ON payment_details TYPE datetime VALUE time::now();

-- Seller profile
DEFINE TABLE seller SCHEMAFULL;
DEFINE FIELD name            ON seller TYPE string;
DEFINE FIELD email           ON seller TYPE string ASSERT string::is_email($value);
DEFINE FIELD time            ON seller TYPE object;
DEFINE FIELD time.created_at ON seller TYPE datetime DEFAULT time::now();
DEFINE FIELD time.updated_at ON seller TYPE datetime VALUE time::now();

-- Product listings
DEFINE TABLE product SCHEMAFULL;
DEFINE FIELD name            ON product TYPE string;
DEFINE FIELD price           ON product TYPE number;
DEFINE FIELD currency        ON product TYPE string ASSERT $value IN ["USD", "GBP", "CAD"];
DEFINE FIELD category        ON product TYPE string;
DEFINE FIELD seller          ON product TYPE record<seller>;
DEFINE FIELD time            ON product TYPE object;
DEFINE FIELD time.created_at ON product TYPE datetime DEFAULT time::now();
DEFINE FIELD time.updated_at ON product TYPE datetime VALUE time::now();

-- Wishlist links (person -> product)
DEFINE TABLE wishlist TYPE RELATION FROM person TO product SCHEMAFULL;
DEFINE FIELD colour ON wishlist TYPE string;
DEFINE FIELD size   ON wishlist TYPE string;
DEFINE FIELD time   ON wishlist TYPE object;
DEFINE FIELD time.created_at ON wishlist TYPE datetime DEFAULT time::now();
DEFINE FIELD time.deleted_at ON wishlist TYPE option<datetime>;

-- Cart links (person -> product)
DEFINE TABLE cart TYPE RELATION FROM person TO product SCHEMAFULL;
DEFINE FIELD quantity ON cart TYPE number;
DEFINE FIELD price    ON cart TYPE number;
DEFINE FIELD currency ON cart TYPE string ASSERT $value IN ["CAD", "EUR", "USD"];
DEFINE FIELD time     ON cart TYPE object;
DEFINE FIELD time.created_at ON cart TYPE datetime DEFAULT time::now();
DEFINE FIELD time.updated_at ON cart TYPE datetime VALUE time::now();
```

### Orders, reviews, reports

Order processing and analytics system with review management and business intelligence. Features order lifecycle tracking, automated analytics tables, and [full-text search](/docs/reference/query-language/statements/define/analyzer.md) capabilities. Demonstrates complex aggregations, [materialised views](/docs/reference/query-language/statements/define/table.md#pre-computed-table-views), and search optimisation for e-commerce applications.

```surql
-- Orders placed (person -> product)
DEFINE TABLE order TYPE RELATION FROM person TO product SCHEMAFULL;
DEFINE FIELD quantity          ON order TYPE number;
DEFINE FIELD price             ON order TYPE number;
DEFINE FIELD currency          ON order TYPE string;
DEFINE FIELD order_status      ON order TYPE string ASSERT $value IN ["pending", "processed", "shipped", "cancelled"];
DEFINE FIELD shipping_address ON order TYPE object FLEXIBLE;
DEFINE FIELD payment_method    ON order TYPE string;
DEFINE FIELD time              ON order TYPE object;
DEFINE FIELD time.created_at   ON order TYPE datetime DEFAULT time::now();
DEFINE FIELD time.updated_at   ON order TYPE datetime VALUE time::now();
DEFINE FIELD time.processed_at ON order TYPE option<datetime>;
DEFINE FIELD time.shipped_at   ON order TYPE option<datetime>;

-- Product reviews
DEFINE TABLE review TYPE RELATION FROM person TO product SCHEMAFULL;
DEFINE FIELD rating       ON review TYPE number ASSERT $value IN 0..=5;
DEFINE FIELD review_text  ON review TYPE string;
DEFINE FIELD time         ON review TYPE object;
DEFINE FIELD time.created_at ON review TYPE datetime DEFAULT time::now();
DEFINE FIELD time.updated_at ON review TYPE datetime VALUE time::now();

-- Indexes and analytics
DEFINE FUNCTION fn::number_of_unfulfilled_orders() -> array<{ count: int }> {
  RETURN (SELECT count() FROM order
    WHERE order_status NOTINSIDE ["processed", "shipped"] GROUP ALL);
};

-- Monthly order summary
DEFINE TABLE monthly_sales TYPE NORMAL SCHEMAFULL AS 
  SELECT 
    count() AS number_of_orders, 
    time::format(time.created_at, '%Y-%m') AS month, 
    math::sum(price * quantity) AS sum_sales, 
    currency 
  FROM order 
  GROUP BY month, currency;

-- Average product rating
DEFINE TABLE avg_product_review TYPE NORMAL SCHEMAFULL AS 
  SELECT 
    count() AS number_of_reviews, 
    math::mean(<float> rating) AS avg_review, 
    ->product.id AS product_id, 
    ->product.name AS product_name 
  FROM review 
  GROUP BY product_id, product_name;

-- Full-text search
DEFINE ANALYZER blank_snowball TOKENIZERS blank FILTERS lowercase, snowball(english);
DEFINE INDEX review_content ON review FIELDS review_text FULLTEXT ANALYZER blank_snowball BM25 HIGHLIGHTS;
```

## Medical

### Patient records and encounters

Healthcare management system with patient records, encounter tracking, and clinical data management. Features vital signs [time-series data](/docs/learn/data-models/time-series/overview.md), medication tracking, and automated encounter lifecycle management using [events](/docs/reference/query-language/statements/define/event.md). Demonstrates healthcare data modelling with temporal data, clinical workflows, and medical record compliance patterns.

```surql
-- Patient record
DEFINE TABLE patient SCHEMAFULL;
DEFINE FIELD name      ON patient TYPE string;
DEFINE FIELD dob       ON patient TYPE datetime;
DEFINE FIELD gender    ON patient TYPE string ASSERT $value IN ["male", "female", "other", "uncertain"];
DEFINE FIELD email     ON patient TYPE option<string> ASSERT string::is_email($value);
DEFINE FIELD created_at ON patient TYPE datetime DEFAULT time::now();

-- One healthcare visit
DEFINE TABLE encounter SCHEMAFULL;
DEFINE FIELD patient     ON encounter TYPE record<patient>;
DEFINE FIELD occurred_at ON encounter TYPE datetime DEFAULT time::now();
DEFINE FIELD type        ON encounter TYPE string ASSERT $value IN ["checkup", "emergency", "followup", "consult"];
DEFINE FIELD reason      ON encounter TYPE option<string>;
DEFINE FIELD location    ON encounter TYPE option<string>;
DEFINE FIELD ongoing    ON encounter TYPE bool DEFAULT true;
DEFINE FIELD ended_at   ON encounter TYPE option<datetime>;

DEFINE EVENT close_encounter ON encounter WHEN $event = "UPDATE" THEN {
    IF $before.ongoing = true AND $after.ongoing = false {
        UPDATE $this SET ended_at = time::now();
    }
};

-- Vital signs time-series (per encounter)
DEFINE TABLE vital_signs SCHEMAFULL;
DEFINE FIELD id        ON vital_signs TYPE [record<encounter>, datetime];
DEFINE FIELD heart_rate     ON vital_signs TYPE option<int> ASSERT $value IN 20..=300;
DEFINE FIELD bp_systolic    ON vital_signs TYPE option<int> ASSERT $value IN 40..=300;
DEFINE FIELD bp_diastolic   ON vital_signs TYPE option<int> ASSERT $value IN 20..=200;
DEFINE FIELD temp_c         ON vital_signs TYPE option<float> ASSERT $value IN 25.0..=45.0;
DEFINE FIELD notes     ON vital_signs TYPE option<string>;

-- Diagnoses made during encounter
DEFINE TABLE diagnosis SCHEMAFULL;
DEFINE FIELD encounter ON diagnosis TYPE record<encounter>;
DEFINE FIELD code      ON diagnosis TYPE string; -- e.g., ICD-10
DEFINE FIELD label     ON diagnosis TYPE string;
DEFINE FIELD confirmed ON diagnosis TYPE bool DEFAULT true;

-- Medications prescribed
DEFINE TABLE medication SCHEMAFULL;
DEFINE FIELD encounter ON medication TYPE record<encounter>;
DEFINE FIELD name      ON medication TYPE string;
DEFINE FIELD dose_mg   ON medication TYPE float;
DEFINE FIELD frequency ON medication TYPE string; -- e.g., "2x daily"
DEFINE FIELD duration  ON medication TYPE string; -- e.g., "7 days"
DEFINE FIELD prn       ON medication TYPE bool DEFAULT false; -- "as needed"

-- Notes written by practitioner
DEFINE TABLE note SCHEMAFULL;
DEFINE FIELD id      ON note TYPE [record<encounter>, datetime];
DEFINE FIELD author  ON note TYPE string;
DEFINE FIELD content ON note TYPE string;
DEFINE FIELD tags    ON note TYPE option<array<string>>;

CREATE patient:one SET name = "Alex Quinn", dob = d'1988-06-12', gender = "male";
CREATE encounter:one SET patient = patient:one, type = "checkup", reason = "Routine annual";

-- Vital signs log
CREATE vital_signs:[encounter:one, time::now()] SET heart_rate = 72, bp_systolic = 120, bp_diastolic = 80, temp_c = 36.8;

-- Diagnosis
CREATE diagnosis SET encounter = encounter:one, code = "E66.9", label = "Obesity, unspecified";

-- Medication
CREATE medication SET encounter = encounter:one, name = "Metformin", dose_mg = 500, frequency = "2x daily", duration = "30 days";

-- Progress note
CREATE note:[encounter:one, time::now()] SET author = "Dr. Leung", content = "Patient reports improved energy since last visit.";
```

## Related SurrealQL statements

- [SurrealKit schema migration](/docs/manage/schema-migration.md) - official CLI for versioning and applying schema from `.surql` files
- [DEFINE TABLE](/docs/reference/query-language/statements/define/table.md)
- [DEFINE FIELD](/docs/reference/query-language/statements/define/field.md)
- [RELATE](/docs/reference/query-language/statements/relate.md)
- [DEFINE INDEX](/docs/reference/query-language/statements/define/indexes.md)
- [DEFINE FUNCTION](/docs/reference/query-language/statements/define/function.md)
- [DEFINE EVENT](/docs/reference/query-language/statements/define/event.md)
- [DEFINE PARAM](/docs/reference/query-language/statements/define/param.md)
- [DEFINE ANALYZER](/docs/reference/query-language/statements/define/analyzer.md)

---

Source: https://surrealdb.com/docs/learn/schema-management/schema-design/schema-best-practices

# Schema best practices

Best practices for creating schemas in SurrealDB.

With SurrealDB, you can create a schema that is as simple or as complex as you need it to be. This page contains a number of best practices for creating schemas that are both easy to understand and easy to maintain.

When those definitions live in source control and need to roll out safely to shared databases, follow [SurrealKit schema migration](/docs/manage/schema-migration.md) in the **Manage** section for sync, rollouts, testing in CI, and related tooling.

## Define arrays and sets with a type and length

In addition to a type, both arrays and sets can have a required number of items built into the type definition itself. A value with more or fewer items than the stated length is rejected. The definition below pairs this with an assertion using the [`array::all()`](/docs/reference/query-language/functions/database-functions/array.md#arrayall) function to also ensure that every item in the `small_bytes` field is between 0 and 255.

```surql
DEFINE FIELD small_bytes ON data TYPE array<int, 8> ASSERT $value.all(|$int| $int IN 0..=255);
```

> [!NOTE]
> Before SurrealDB 3.0.0, the number in `array<int, 8>` was a maximum length, so an array with fewer items passed. From 3.0.0 it is an exact length.

Learn more about [database functions](/docs/reference/query-language/functions/database-functions.md).

## Define individual indexes of an array

Even the individual indexes of an array can be defined. This is useful for data types like RGB colours that must be exactly three items in length.

```surql
DEFINE FIELD rgb ON colour TYPE array<int, 3>;
DEFINE FIELD rgb[0] ON colour TYPE int ASSERT $value IN 0..=255;
DEFINE FIELD rgb[1] ON colour TYPE int ASSERT $value IN 0..=255;
DEFINE FIELD rgb[2] ON colour TYPE int ASSERT $value IN 0..=255;

CREATE colour SET rgb = [0, 2, 30];
-- Fails: must have three items
CREATE colour SET rgb = [0, 2];
-- Fails: must be between 0 and 255
CREATE colour SET rgb = [0, 2, 400];
```

[Learn more about assertions in `DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md#asserting-rules-on-fields)

## How to work with objects inside `SCHEMALESS` and `SCHEMAFULL` tables

The behaviour of an object as a field of a table depends on whether the table is `SCHEMALESS` (the default) or `SCHEMAFULL`.

Inside a schemaless table, the only time an object will be schemafull is if the structure of the object is indicated using the [literal](/docs/reference/query-language/language-primitives/data-types/literals.md) syntax.

```surql
-- This table is schemaless
DEFINE TABLE some_table;

-- So an object defined on it will be schemaless
DEFINE FIELD some_object ON some_table TYPE object;
-- Same for an array of objects
DEFINE FIELD some_objects ON some_table TYPE array<object>;

-- But this is schemafull because it has a set structure
DEFINE FIELD some_specific_objects ON some_table TYPE array<{ a: string }>;
```

Inside a schemafull table, a field typed as `object` or `array<object>` (or any other definition that contains an `object`) is schemafull by default. Add the `FLEXIBLE` field clause after `TYPE` to allow arbitrary keys on every object in that field's type.

```surql
DEFINE TABLE some_table SCHEMAFULL;

-- Schemafull object and array of objects
DEFINE FIELD some_object ON some_table TYPE object;
DEFINE FIELD some_objects ON some_table TYPE array<object>;

-- Field clause makes objects inside these fields schemaless
DEFINE FIELD flexible_object ON some_table TYPE object FLEXIBLE;
DEFINE FIELD flexible_objects ON some_table TYPE array<object> FLEXIBLE;
```

Inside a schemafull table, any input value that does not match the defined schema will cause an error.

```surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON user TYPE string;
DEFINE FIELD metadata ON user TYPE object;
DEFINE FIELD metadata.created_at ON user TYPE datetime;
DEFINE FIELD metadata.age ON user TYPE int;

CREATE user SET name = "Billy", metadata = {
    created_at: time::now(),
    age: 5,
    wrong_field: "WRONG DATA"
};
```

```surql title="Output"
"Found field 'metadata.wrong_field', but no such field exists for table 'user'"
```

If you have data that includes a non-defined field in such a table, you can use the destructuring operator to access the current structure and only pass on the necessary fields for the operation.

```surql
-- This object has too much info and capitalization doesn't match
LET $chaotic_content = {
    name: "Billy",
    unneeded_number: 10,
    metadata: {
        CREATED_AT: time::now(),
        age: 5,
        wrong_field: "WRONG DATA"
    }
};

-- Pass on the needed fields and rename CREATED_AT to lowercase
CREATE user CONTENT $chaotic_content.{
    name,
    metadata.{
        created_at: CREATED_AT,
        age
    }
};
```

Defining a specific function to return the expected structure can be a nice convenience in this case.

```surql
DEFINE FUNCTION fn::filter_for_user($obj: object) -> object {
    $obj.{ name, metadata.{ created_at: CREATED_AT, age }};
};

LET $chaotic_content = {
    name: "Billy",
    unneeded_number: 10,
    metadata: {
        CREATED_AT: time::now(),
        age: 5,
        wrong_field: "WRONG DATA"
    }
};

CREATE user CONTENT fn::filter_for_user($chaotic_content);
```

## Use `THROW` to add more detailed error messages to `ASSERT` clauses

A `DEFINE FIELD` statement allows an `ASSERT` clause to be added in order to ensure that the value, which here is represented as the parameter `$value`, meets certain expectations. A simple example here makes sure that the `name` field on the `person` table is under 20 characters in length.

```surql
DEFINE FIELD name ON person TYPE string ASSERT $value.len() < 20;

CREATE person SET name = "Mr. Longname who has much too long a name";
```

In this case, the default error message is pretty good.

```surql
"Found 'Mr. Longname who has much too long a name' for field `name`, with record `person:2gpvut914k1qfysqs3lc`, but field must conform to: $value.len() < 20"
```

However, `ASSERT` only expects a truthy value at the end and otherwise isn't concerned at all with what happens before. This means that you can outright customise the logic, including a custom error message. Let's give this a try.

```surql
DEFINE FIELD name ON person TYPE string ASSERT {
    IF $value.len() >= 20 {
        THROW "`" + <string>$value + "` too long, must be under 20 characters. Up to `" + $value.slice(0,19) + "` is acceptable";
    } ELSE {
       RETURN true;
    }
};

CREATE person SET name = "Mr. Longname who has much too long a name";
```

Not bad! The error message now includes the value and the part of it that is acceptable:

```surql
'An error occurred: `Mr. Longname who has much too long a name` too long, must be under 20 characters.
Up to `Mr. Longname who ha` is acceptable'
```

## Use formatters on internal datetimes for strings with alternative formats

A lot of legacy systems require datetimes to be displayed in a format that doesn't quite match a `datetime`.

That doesn't mean that you have to give up the precision of a `datetime` though. By using the [`time::format()`](/docs/reference/query-language/language-primitives/formatters.md) function, you can keep the actual stored date as a precise SurrealQL `datetime` and then use that to output a string in any format you like.

```surql
DEFINE FIELD created_at ON user VALUE time::now() READONLY;
DEFINE FIELD since ON user VALUE time::format(created_at, "%Y-%m-%d");

CREATE user RETURN id, since;
```

```surql title="Output"
[
	{
		id: user:50s2riya8fm3cdbrhwpe,
		since: '2026-02-12'
	}
]
```

## Use `!!$value` in `DEFINE` statements

As the `!` operator reverses the truthiness of a value, using it twice in a row as `!!` returns a value's truthiness. As empty and default values (such as 0 for numbers) are considered to be non-truthy, this operator is handy if you want to ensure that a value is both present and not empty.

```surql
DEFINE FIELD name ON character TYPE string;
DEFINE FIELD metadata ON character TYPE object;
-- Works because "" is of type string
CREATE character SET name = "", metadata = {};

DEFINE FIELD OVERWRITE name ON character TYPE string ASSERT !!$value;
-- Now returns an error because "" and {} are non-truthy
CREATE character SET name = "", metadata = {};
```

## Use `DEFINE PARAM` for clarity

If you find that parts of your table- or field-specific code are getting a bit long, it might be time to think about moving parts of it to a [database-wide parameter](/docs/reference/query-language/statements/define/param.md).

```surql
DEFINE FIELD month_published
    ON book 
    TYPE string 
    ASSERT $value IN ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
```

Doing so makes the code cleaner and easier to reuse in other parts of the schema.

```surql
DEFINE PARAM $MONTHS
    VALUE ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

DEFINE FIELD month_published ON book TYPE string ASSERT $value IN $MONTHS;
DEFINE FUNCTION fn::do_something_with_month($input: string) {
    IF !($input IN $MONTHS) {
        THROW "Some error about wrong input";
    } ELSE {
        -- do something with months here
    }
};
```

## Use literals to return rich error output

Error types in programming languages often take the form of a long list of possible things that could go wrong. SurrealQL's [literal](/docs/reference/query-language/language-primitives/data-types/literals.md) type allows you to specify a list of all possible forms it could take, which makes it well suited to error logic.

```surql
DEFINE PARAM $ERROR_CODES VALUE [200, 300, 400, 500];

DEFINE FUNCTION fn::return_response($input: 
    { type: "internal_error", message: string } |
    { type: "bad_request", message: string } | 
    { type: "invalid_date", got: any, expected: "YYYY-MM-DD" } |
    int) -> object | int {
    IF $input.is_int() {
        IF $input IN $ERROR_CODES {
            $input
        } ELSE {
            THROW "Input must be one of " + <string>$ERROR_CODES;
        }
    } ELSE {
        $input
    }
};

fn::return_response(500);
fn::return_response(999999);
fn::return_response({ type: "internal_error", message: "You can't do that"});
fn::return_response(a:wrong_argument);
```

```surql title="Output"
-------- Query --------
500

-------- Query --------
'An error occurred: Input must be one of [200, 300, 400, 500]'

-------- Query --------
{
	message: "You can't do that",
	type: 'internal_error'
}

-------- Query --------
"Expected `{ message: string, type: 'internal_error' } | { message: string, type: 'bad_request' } | { expected: 'YYYY-MM-DD', got: any, type: 'invalid_date' } | int` but found `a:wrong_argument`"
```

## Use graph queries in the schema

While graph queries are usually seen in `SELECT` statements in the documentation, they can live inside your database schema just like any other datatype or expression. In the schema below for a family tree, any inserted record must either have a parent (via the `<-parent_of<-person` path) or be `first_generation`.

```surql
DEFINE FIELD parents ON person ASSERT <-parent_of<-person
  OR first_generation;

-- Is first_generation, doesn't need to indicate parents
CREATE person:one SET first_generation = true;

CREATE person:two;
//- Error:
//- 'Found NONE for field `parents`, with record `person:two`,
//- but field must conform to: <-parent_of<-person OR first_generation'

-- Give person:two a parent
RELATE person:one->parent_of->person:two;
-- CREATE now works 
CREATE person:two;
```

This pattern is possible because `RELATE` statements can be used before the records to relate exist. To disallow this, you can add the [`ENFORCED`](/docs/reference/query-language/statements/define/table.md#using-enforced-to-ensure-that-related-records-exist) clause to a `DEFINE TABLE table_name TYPE RECORD` definition.

---

Source: https://surrealdb.com/docs/learn/schema-management/schema-design/schema-evolution

# Schema evolution

Changing schema over time with ALTER and related patterns.

Production databases rarely stay still, and in SurrealDB they tend to move from less to more strict over time as you become more aware of the expected behaviour of your database. Some examples of schema changes are tightening a table from schemaless to schemafull, changing permissions, or preparing an index for removal. SurrealDB's `ALTER` statement changes definitions in place without always rewriting a full `DEFINE` block.

When you are ready to version and apply those changes across environments (for example from CI or with reviewed rollouts), use [SurrealKit schema migration](/docs/manage/schema-migration.md) - the official `.surql`-based workflow documented in the **Manage** section.

For a wholesale replacement, you can always use `DEFINE` with the `OVERWRITE` clause.

## Changing the type of a field

Changing the `TYPE` of a field changes its definition, but the records already stored keep their old values. A record whose value does not match the new type can still be read, but any later write to that record fails until the value is converted. To change the type of a field on a table that has data, change the definition and then convert the stored values with an `UPDATE`:

```surql
DEFINE FIELD price ON product TYPE string;
CREATE product:1 SET price = '10';
CREATE product:2 SET price = '12.5';

ALTER FIELD price ON product TYPE number;

-- Fails, because product:1 still holds a string
UPDATE product:1 SET name = 'Lamp';
//- "Couldn't coerce value for field `price` of `product:1`: Expected `number` but found `'10'`"

-- Convert every stored value to the new type
UPDATE product SET price = <number> price;
//- [{ id: product:1, price: 10 }, { id: product:2, price: 12.5 }]
```

The `UPDATE` fails for the whole table if any one value cannot be converted, such as a price of `'ten'`, and then changes nothing. On a large table, run it in batches with a `WHERE` clause, such as `UPDATE product SET price = <number> price WHERE price.is_string()`, so that each run converts what remains.

## Tightening a table from schemaless to schemafull

A common pattern is to start with a completely schemaless table, adding defined fields one at a time to ensure that they are present in all records of a certain table. If you are certain that no other fields should be present, you can move the entire table from `SCHEMALESS` to `SCHEMAFULL` to ensure that only defined fields are present.

```surql
DEFINE TABLE user SCHEMALESS;
DEFINE FIELD name ON TABLE user TYPE string;
CREATE user SET name = "LordofSalty";

-- Now make it schemafull so undeclared fields are rejected
ALTER TABLE user SCHEMAFULL;
```

## Adjusting permissions without restating the whole table

`ALTER` only needs the parts you are changing. Here the table stays schemafull; only create permission moves from the default to FULL.

```surql
DEFINE TABLE user SCHEMAFULL;

ALTER TABLE user PERMISSIONS FOR create FULL;
```

## Idempotent alterations

In scripts, `IF EXISTS` avoids failing when the resource name was never created in that environment.

```surql
ALTER TABLE IF EXISTS user SCHEMAFULL;
REMOVE TABLE IF EXISTS writer;
```

## Where to read more

* [`ALTER` overview](/docs/reference/query-language/statements/alter/overview.md)
* [Schema best practices](/docs/learn/schema-management/schema-design/schema-best-practices.md)
* [SurrealKit schema migration](/docs/manage/schema-migration.md) - sync and rollout workflows for schema files

---

Source: https://surrealdb.com/docs/learn/schema-management/tables-and-fields/fields-and-validation

# Fields and validation

Defining fields: types, defaults, VALUE, ASSERT, permissions, and references.

Fields are where you can spell out what each table field means by using certain clauses, such as its type (`TYPE`), optional default values (`DEFAULT`), how writes are normalised (`VALUE`), what counts as valid data (`ASSERT`), or who may see or change it (`PERMISSIONS`). A [`COMMENT`](/docs/reference/query-language/statements/define/overview.md#comments-on-definitions) is a good place for comparison rules and invariants that are not fully captured by the type alone.

You need database-level access and an active [`USE`](/docs/reference/query-language/statements/use.md) scope, same as for tables.

Computed fields (derived on every read) have their own learn page: [Computed fields](/docs/learn/schema-management/computed-data/computed-fields.md).

## Example usage

The following expression shows the simplest way to use the `DEFINE FIELD` statement.

```surql
-- Declare the name of a field.
DEFINE FIELD email ON TABLE user;
```

The fields of an object and the items in an array can be defined individually using the `.` operator for objects, or the indexing operator for arrays.

```surql
-- Define nested object property types
DEFINE FIELD emails.address ON TABLE user TYPE string;
DEFINE FIELD emails.primary ON TABLE user TYPE bool;

-- Define individual fields on an array
DEFINE FIELD metadata[0] ON person TYPE datetime;
DEFINE FIELD metadata[1] ON person TYPE int;
```

### Simple data types

```surql
-- Set a field to have the string data type
DEFINE FIELD email ON TABLE user TYPE string;

-- Set a field to have the datetime data type
DEFINE FIELD created ON TABLE user TYPE datetime;

-- Set a field to have the bool data type
DEFINE FIELD locked ON TABLE user TYPE bool;

-- Set a field to have the number data type
DEFINE FIELD login_attempts ON TABLE user TYPE number;
```

A `|` vertical bar can be used to allow a field to be one of a set of types. The following example shows a field that can be a [`UUID`](/docs/reference/query-language/language-primitives/data-types/uuids.md) or an [`int`](/docs/reference/query-language/language-primitives/data-types/numbers.md#integer-numbers), perhaps for `user` records that have varying data due to two different legacy ID types.

```surql
-- Set a field to have either the uuid or int type
DEFINE FIELD user_id ON TABLE user TYPE uuid|int;
```

### Array type

You can also set a field to have the array data type. The array data type can be used to store a list of values. You can also set the data type of the array's contents, as well as the required number of items that it must hold.

```surql
-- Set a field to have the array data type
DEFINE FIELD roles ON TABLE user TYPE array<string>;

-- Set a field to have the array data type, equivalent to `array<any>`
DEFINE FIELD posts ON TABLE user TYPE array;

-- Set a field to have the array object data type
DEFINE FIELD emails ON TABLE user TYPE array<object>;

-- Set a field that holds exactly 640 bytes
DEFINE FIELD bytes ON TABLE data TYPE array<int, 640> ASSERT $value.all(|$val| $val IN 0..=255);

-- Field for a block in a game showing the possible distinct directions a character can move next.
-- The array can contain no more than four directions
DEFINE FIELD next_paths ON TABLE block 
  TYPE array<"north" | "east" | "south" | "west"> 
  VALUE $value.distinct() 
  ASSERT $value.len() <= 4;
```

### Making a field optional

You can make a field optional by wrapping the inner type in an `option`, which allows you to store `NONE` values in the field.

For a nullable field in the SQL sense, use `string | null` to allow `NULL`, or `option<string | null>` to allow both `NULL` and a missing field. The [`DEFINE FIELD` reference](/docs/reference/query-language/statements/define/field.md#making-a-field-optional) compares the three forms.

```surql
-- A user may enter a biography, but it is not required.
-- By using the option type you also allow for NONE values.
DEFINE FIELD biography ON TABLE user TYPE option<string>;
```

The example below shows how to define a field `user` on a `POST` table. The field is of type [record](/docs/reference/query-language/language-primitives/record-links.md). This means that the field can store a `record<user>` or `NONE`.

```surql
DEFINE FIELD user ON TABLE post TYPE option<record<user>>;
```

### Flexible objects in schemafull tables

On a `SCHEMAFULL` table, objects are schemafull by default. The `FLEXIBLE` field clause (written after `TYPE`) marks a field so that every `object` in its type accepts arbitrary keys. This includes objects nested in arrays, options, and unions - not only a top-level `TYPE object` field.

```surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON TABLE user TYPE string;
DEFINE FIELD metadata ON TABLE user TYPE object FLEXIBLE;
DEFINE FIELD metadata.user_id ON TABLE user TYPE int;
```

Taking the following `CREATE` statement:

```surql
CREATE ONLY user SET
  name = "User1",
  metadata = {
      user_id: 8876687,
      country_code: "ee",
      time_zone: "EEST",
      age: 25
};
```

Without `FLEXIBLE`, the `metadata` field is a schemafull object and only declared subfields such as `metadata.user_id` are accepted.

With `FLEXIBLE`, the field accepts any extra keys on `metadata` while still requiring `name` and a valid `metadata.user_id`.

```surql title="Output"
{
	id: user:lsdk473e279oik1k484b,
	metadata: {
		age: 25,
		country_code: 'ee',
		time_zone: 'EEST',
		user_id: 8876687
	},
	name: 'User1'
}
```

### Using the `DEFAULT` clause to set a default value

You can set a default value for a field using the `DEFAULT` clause. The default value will be used if no value is provided for the field.

```surql
-- A user is not locked by default.
DEFINE FIELD locked ON TABLE user TYPE bool
-- Set a default value if empty
  DEFAULT false;
```

### Using the `DEFAULT ALWAYS` clause

In addition to the `DEFAULT` clause, you can use the `DEFAULT ALWAYS` clause to set a default value for a field. The `ALWAYS` keyword indicates that the `DEFAULT` clause is used not only on `CREATE`, but also on `UPDATE` if the value is empty (NONE).

```surql
DEFINE TABLE product SCHEMAFULL;
-- Set a default value of 123.456 for the primary field
DEFINE FIELD primary ON product TYPE number DEFAULT ALWAYS 123.456;
```

With the above definition, the `primary` field will be set to `123.456` when a new `product` is created without a value for the `primary` field or with a value of `NONE`. When an existing `product` is updated with a value specified, the result will be the new value.

In the case of `NULL` or a mismatching type, an error will be returned.

```surql
-- This will return an error
CREATE product:test SET primary = NULL;

-- result 
"Couldn't coerce value for field `primary` of `product:test`: Expected `number` but found `NULL`"
```

On the other hand, if a valid number is provided during creation or update, that number will be used instead of the default value. In this case, `123.456`.

```surql
-- This will set the value of the `primary` field to `123.456`
CREATE product:test;

-- This will set the value of the `primary` field to `463.456`
UPSERT product:test SET primary = 463.456;

-- This will set the value of the `primary` field to `123.456`
UPSERT product:test SET primary = NONE;

```

### Using the `VALUE` clause to set a field's value

The `VALUE` clause differs from `DEFAULT`. A default value is only calculated if no other value is given, and otherwise the value given in a query is accepted.

```surql
DEFINE FIELD updated ON TABLE user DEFAULT time::now();

-- Set `updated` to the year 1900
CREATE user SET updated = d"1900-01-01";
-- Then set to the year 1910
UPDATE user SET updated = d"1910-01-01";
```

A `VALUE` clause, on the other hand, will ignore attempts to set the field to any other value.

```surql
DEFINE FIELD updated ON TABLE user VALUE time::now();

-- Ignores 1900 date, sets `updated` to current time
CREATE user SET updated = d"1900-01-01";
-- Ignores again, updates to current time
UPDATE user SET updated = d"1900-01-01";
```

As the example above shows, a `VALUE` clause sets the value every time a record is modified (created or updated). However, the value will not be recalculated in a `SELECT` statement, which simply accesses the current set value.

```surql
DEFINE FIELD updated ON TABLE user VALUE time::now();

CREATE user:one;
SELECT * FROM ONLY user:one;
-- Sleep for one second
SLEEP 1s;
-- `updated` is still the same
SELECT * FROM ONLY user:one;
```

To create a field that is calculated each time it is accessed, a [`computed field`](/docs/reference/query-language/statements/define/field.md#restrictions-on-computed-fields) can be used.

```surql
DEFINE FIELD accessed_at ON TABLE user COMPUTED time::now();

CREATE user:one;
SELECT * FROM ONLY user:one;
-- Sleep for one second
SLEEP 1s;
-- `accessed_at` is a different value now
SELECT * FROM ONLY user:one;
```

### Altering a passed value

You can alter a passed value using the `VALUE` clause. This is useful for altering the value of a field before it is stored in the database.

In the example below, the `VALUE` clause is used to ensure that the email address is always stored in lowercase characters by using the [`string::lowercase`](/docs/reference/query-language/functions/database-functions/string.md#stringlowercase) function.

```surql
-- Ensure that an email address is always stored in lowercase characters
DEFINE FIELD email ON TABLE user TYPE string
  VALUE string::lowercase($value);
```

## Asserting rules on fields

You can take your field definitions even further by using asserts. Assert can be used to ensure that your data remains consistent. For example you can use asserts to ensure that a field is always a valid email address, or that a number is always positive.

```surql
-- Give the user table an email field. Store it in a string
DEFINE FIELD email ON TABLE user TYPE string
  -- Check if the value is a properly formatted email address
  ASSERT string::is_email($value);
```

As the `ASSERT` clause expects an expression that returns a boolean, an assertion with a custom message can be manually created by returning `true` in one case and using a [`THROW`](/docs/reference/query-language/statements/throw.md) clause otherwise.

```surql
DEFINE FIELD num ON data TYPE int ASSERT {
    IF $input % 2 = 0 {
        RETURN true
    } ELSE {
        THROW "Tried to make a " + <string>$this + " but `num` field requires an even number"
    }
};

CREATE data:one SET num = 11;
```

```surql title="Error output"
'An error occurred: Tried to make a { id: data:one, num: 11 } but `num` field requires an even number'
```

### Making a field `READONLY`

The `READONLY` clause can be used to prevent any updates to a field. This is useful for fields that are automatically updated by the system. To make a field `READONLY`, add the `READONLY` clause to the `DEFINE FIELD` statement. As seen in the example below, the `created` field is set to `READONLY`.

```surql
DEFINE FIELD created ON resource VALUE time::now() READONLY;
```

## Setting permissions on fields

By default, the permissions on a field are set to `FULL` unless otherwise specified. That means once a [table](/docs/learn/schema-management/tables-and-fields/tables.md#defining-permissions) allows an operation for a record user, the field is included unless you narrow it with your own `PERMISSIONS` clause. Tables default the other way: omitting table `PERMISSIONS` stores `PERMISSIONS NONE`.

```surql
DEFINE FIELD some_info ON TABLE some_table TYPE string;
INFO FOR TABLE some_table;
```

```surql title="Output"
{
	events: {},
	fields: {
		info: 'DEFINE FIELD info ON some_table TYPE string PERMISSIONS FULL'
	},
	indexes: {},
	lives: {},
	tables: {}
}
```

You can set permissions on fields to control who can perform operations on them using the `PERMISSIONS` clause. The `PERMISSIONS` clause can be used to set permissions for `SELECT`, `CREATE`, and `UPDATE` operations. The `DELETE` operation only relates to records and, as such, is not available for fields.

Like table permissions, field permissions apply to [record users](/docs/learn/security/authentication/users.md#record-users) (and guests when enabled), not to system users.

```surql
/[test]

[[test.results]]
value = "NONE"

*/

-- Set permissions for the email field
DEFINE FIELD email ON TABLE user
  PERMISSIONS
    FOR select WHERE published=true OR user=$auth.id
    FOR update WHERE user=$auth.id OR $auth.role="admin";
```

## Order of operations when setting a field's value

When a record is written, a field whose `VALUE`, `DEFAULT` or `COMPUTED` clause reads another field is evaluated after that field. In the example below, `full_name` reads `first_name` and `last_name`, so it is evaluated after both have been lowercased:

```surql
DEFINE TABLE person SCHEMAFULL;

DEFINE FIELD first_name
  ON TABLE person TYPE string VALUE string::lowercase($value);
DEFINE FIELD last_name
  ON TABLE person TYPE string VALUE string::lowercase($value);
DEFINE FIELD full_name
  ON TABLE person             VALUE first_name + ' ' + last_name;

CREATE person SET first_name = "Bob", last_name = "BOBSON";
```

```surql title="Output"
[
	{
		first_name: 'bob',
		full_name: 'bob bobson',
		id: person:5a2k6ujjg0r4l1xk4ef1,
		last_name: 'bobson'
	}
]
```

Fields that do not read each other are evaluated in the order they were defined, and `ASSERT` clauses run afterwards, once every field holds its final value. Two fields whose values each read the other form a cycle, which SurrealDB refuses when the second field is defined. See [Field evaluation order](/docs/reference/query-language/statements/define/field.md#field-evaluation-order) for the details.

> [!NOTE]
> Before 3.3.0, fields were evaluated in alphabetical order of their names. On those versions `full_name` is evaluated before `last_name` has been lowercased, and the example above gives `'bob BOBSON'`.

## Defining a literal on a field

A field can also be defined as a [literal type](/docs/reference/query-language/language-primitives/data-types/literals.md), by specifying one or more possible values and/or permitted types.

```surql
DEFINE FIELD coffee
  ON TABLE order TYPE "regular" | "large" | { special_order: string };

CREATE order:good SET coffee = { special_order: "Venti Quadruple Ristretto Half-Decaf Soy Latte with 4 pumps of sugar-free vanilla syrup" };
CREATE order:bad SET coffee = "small";
```

```surql title="Output"
-------- Query --------

[
	{
		coffee: {
			special_order: 'Venti Quadruple Ristretto Half-Decaf Soy Latte with 4 pumps of sugar-free vanilla syrup'
		},
		id: order:good
	}
]

-------- Query --------
"Found 'small' for field `coffee`, with record `order:bad`, but expected a 'regular' | 'large' | { special_order: string }"
```

---

Source: https://surrealdb.com/docs/learn/schema-management/tables-and-fields/record-id-best-practices

# Record ID best practices

How best to make a decision on what kind of record ID format to use in your database.

A record ID is chosen once and queried forever, so its format decides how easily records can be looked up, ranged over and kept unique. This page collects the tips and trade-offs for choosing one.

## Tips and best practices for record IDs

This page contains a number of tips and best practices when working with record IDs in SurrealDB.

### Why choose the right record ID format

Choosing an apt record ID format is especially important because record IDs in SurrealQL are immutable. Take the following `user` records for example:

```surql
FOR $i IN 0..5 {
    CREATE user SET user_num = $i, name = "User number " + <string>user_num;
};
```

Each of these `user` records will have a random ID, such as `user:wvjqjc5ebqvfg3aw7g61`. If a decision is made to move away from random IDs to some other form, such as an incrementing number, this will have to be done manually.

```surql
FOR $user IN SELECT * FROM user {
    -- Use type::record to make a record ID
    -- from the user_num field
    CREATE type::record("user", $user.user_num);
    -- Then delete the old user
    DELETE $user;
};

SELECT * FROM user;
```

The final query returning just the IDs shows that they have been recreated with new IDs.

```surql title="Output"
[
	{
		id: user:0,
		name: 'User number 0'
	},
	{
		id: user:1,
		name: 'User number 1'
	},
	{
		id: user:2,
		name: 'User number 2'
	},
	{
		id: user:3,
		name: 'User number 3'
	},
	{
		id: user:4,
		name: 'User number 4'
	}
]
```

However, record IDs are also used as [record links](/docs/reference/query-language/language-primitives/record-links.md) and to create [graph relations](/docs/reference/query-language/statements/relate.md). If this is the case, more work will have to be done in order to recreate the former state.

The following example shows five `user` records, which each have a 50% chance of liking each of the other users.

```surql
FOR $i IN 0..5 {
    CREATE user SET user_num = $i, name = "User number " + <string>user_num;
};

LET $users = SELECT * FROM user;
FOR $user IN $users {
    LET $others = array::complement($users, [$user.id]);
    FOR $counterpart IN $others {
        IF rand::bool() {
            RELATE $user->likes->$counterpart;
        }
    }
};
```

Finding out the current relational state can be done with a query like the following which shows all of the graph tables in which a record is located at the `in` or `out` point. The `?` is a wildcard operator, returning any and all tables found at this point of the graph query.

```surql
SELECT
    id,
    ->?->? AS did, 
    <-?<-? AS done_to
FROM user;
```

```surql title="Output"
[
	{
		did: [
			user:zwfnk4by9gmopf6eeqm0
		],
		done_to: [
			user:d6bx6sch5li8qmhq3ljl,
			user:ekovipptanvmgr8f48v6
		],
		id: user:6ycb63zr0k3cpzwel1ga
	},
	{
		did: [
			user:ekovipptanvmgr8f48v6,
			user:6ycb63zr0k3cpzwel1ga,
			user:zk7tpaduzaiuswll58sg
		],
		done_to: [],
		id: user:d6bx6sch5li8qmhq3ljl
	}
    -- and so on..
]
```

SurrealDB Studio's [graph visualisation view](/blog/whats-new-in-surrealist-3-2#graph-visualisation) can help as well.

![SurrealDB Studio's graph view showing possible output from the previous randomized query in which each of the five user records may or may not like another user. In this case, the output resembles a rhombus with an extra line jutting out from the top left.](~/assets/img/surrealql/datamodel/graph_view.png)

The following sections cover what to consider when deciding what sort of record ID format to use.

### Meaningful sortable IDs are faster to query

Records are returned in ascending record ID order by default. As the following query shows, a `SELECT` statement on a large number of `user` records with random IDs will show those with record identifiers starting with a large number of zeroes. While the IDs are sortable, the IDs themselves are completely random.

```surql
CREATE |user:200000| RETURN NONE;
SELECT VALUE id FROM user LIMIT 4;
```

```surql title="Output"
[
	user:0001th0nnywnczi7mrvk,
	user:000t5r3y7u8stqtecvht,
	user:000tjk1nbi1it1bedplc,
	user:001dfral92ltbdznypcd
]
```

For a large number of records, pagination can be used to retrieve a certain amount of records at a time.

```surql
-- Returns the same four records as above
SELECT VALUE id FROM user START 0 LIMIT 2;
SELECT VALUE id FROM user START 2 LIMIT 2;
```

```surql title="Output"
-------- Query --------

[
	user:0001th0nnywnczi7mrvk,
	user:000t5r3y7u8stqtecvht
]

-------- Query --------

[
	user:001dfral92ltbdznypcd,
	user:001hv9g1uzh32nophrpo
]
```

As record ranges are very performant, consider moving any fields that may be used in a `WHERE` clause into the ID itself.

In the following example, a number of `user` records are created using the default random ID, plus a `num` field that tracks in which order the user was created.

```surql
FOR $num IN 0..100 {
    CREATE user SET num = $num;
    sleep(1ms); -- Simulate a bit of time between user creation
};

SELECT * FROM user WHERE num IN 50..=51;
SELECT * FROM user START 50 LIMIT 2;
```

As the output from the `SELECT` statements shows, a `WHERE` clause is needed to find two users starting at a `num` of 50, as `START 50` starts based on the order of the record IDs, which are entirely random.

```surql
-------- Query --------

[
	{
		id: user:pqpeg0edt8kpda907o01,
		num: 50
	},
	{
		id: user:ty6qr7zyob5dh882it08,
		num: 51
	}
]

-------- Query --------

[
	{
		id: user:hvfp5m5ty7n2k95dbamv,
		num: 70
	},
	{
		id: user:hvfumcmmveuolg4e2h26,
		num: 36
	}
]
```

Using a ULID in this case will allow the IDs to remain random, but still sorted by date of creation.

```surql
FOR $num IN 0..100 {
    CREATE user:ulid() SET num = $num;
    sleep(1ms);
};

SELECT * FROM user WHERE num IN 50..=51;
SELECT * FROM user START 50 LIMIT 2;
```

The `START 50 LIMIT 2` query is more performant, and the entire `num` field could be removed if its only use is to return records by order of creation.

```surql title="Same record IDs for both queries this time"
-------- Query --------

[
	{
		id: user:01JM1AHN7DDN7XM5KZ2RR2YM1S,
		num: 50
	},
	{
		id: user:01JM1AHN7FS4A3B6RNFCF64H90,
		num: 51
	}
]

-------- Query --------

[
	{
		id: user:01JM1AHN7DDN7XM5KZ2RR2YM1S,
		num: 50
	},
	{
		id: user:01JM1AHN7FS4A3B6RNFCF64H90,
		num: 51
	}
]
```

### Move exact matches in array-based record IDs to the front

Take the following `event` records which can be queried as a performant record range.

```surql
CREATE event:[d'2025-05-05T08:00:00Z', user:one, "debug"] SET info = "Logged in";
CREATE event:[d'2025-05-05T08:10:00Z', user:one, "debug"] SET info = "Logged out";
CREATE event:[d'2025-05-05T08:01:00Z', user:two, "debug"] SET info = "Logged in";
```

The ordering of the ID in this case is likely not ideal, because the first item in the array, a `datetime`, will be the first to be evaluated in a range scan. A query such as the one below on a range of dates will effectively ignore the second and third parts of the ID.

```surql
SELECT * FROM event:[d'2025-05-05', user:one, "debug"]..[d'2025-05-06', user:one, "debug"];

-- Same result! user name and "debug" are irrelevant
-- SELECT * FROM event:[d'2025-05-05']..[d'2025-05-06'];
```

```surql title="Output"
[
	{
		id: event:[
			d'2025-05-05T08:00:00Z',
			user:one,
			'debug'
		],
		info: 'Logged in'
	},
	{
		id: event:[
			d'2025-05-05T08:01:00Z',
			user:two,
			'debug'
		],
		info: 'Logged in'
	},
	{
		id: event:[
			d'2025-05-05T08:10:00Z',
			user:one,
			'debug'
		],
		info: 'Logged out'
	}
]
```

Instead, the parts of the array that are more likely to be exactly matched (such as `user:one` and `"debug"`) should be moved to the front.

```surql
CREATE event:[user:one, "debug", d'2025-05-05T08:00:00Z'] SET info = "Logged in";
CREATE event:[user:one, "debug", d'2025-05-05T08:10:00Z'] SET info = "Logged out";
CREATE event:[user:two, "debug", d'2025-05-05T08:01:00Z'] SET info = "Logged in";
```

Using this format, queries can now be performed for a certain user and logging level, over a range of datetimes.

```surql
-- Only returns events for user:one and "debug"
SELECT * FROM event:[user:one, "debug", d'2025-05-05']..[user:one, "debug", d'2025-05-06'];
```

```surql title="Output"
[
	{
		id: event:[
			user:one,
			'debug',
			d'2025-05-05T08:00:00Z'
		],
		info: 'Logged in'
	},
	{
		id: event:[
			user:one,
			'debug',
			d'2025-05-05T08:10:00Z'
		],
		info: 'Logged out'
	}
]
```

### Auto-incrementing IDs

While SurrealDB does not use auto-incrementing IDs by default, this behaviour can be achieved in a number of ways. From SurrealDB 3.0.0, the shortest is a [sequence](/docs/reference/query-language/statements/define/sequence.md), whose [`sequence::nextval()`](/docs/reference/query-language/functions/database-functions/sequence.md) function returns the next number each time it is called.

```surql
DEFINE SEQUENCE person_id START 1;
CREATE type::record("person", sequence::nextval("person_id"));
CREATE type::record("person", sequence::nextval("person_id"));
```

```surql title="Output"
[
	{
		id: person:2
	}
]
```

A sequence value is not returned when the transaction that took it fails, so the IDs it produces are unique and increasing but can have gaps.

Another way is to use the [`record::id()`](/docs/reference/query-language/functions/database-functions/record.md#recordid) function on the latest record, which returns the latter part of a record ID (the '1' in the record ID `person:1`). This can then be followed up with the [`type::record()`](/docs/reference/query-language/functions/database-functions/type.md#typerecord) function to create a new record ID.

```surql
-- Create records from person:1 to person:10
CREATE |person:1..11|;
LET $latest = SELECT VALUE id FROM ONLY person ORDER BY id DESC LIMIT 1;
CREATE type::record("person", $latest.id() + 1);
```

```surql title="Output"
[
	{
		id: person:11
	}
]
```

The `ORDER BY id DESC LIMIT 1` query scans the table backwards and stops at the first record, so it stays fast on a large table.

> [!NOTE]
> Before SurrealDB 2.3.0, `ORDER BY id DESC` read and sorted every record in the table, which made this query slow on large tables.

Where the IDs must have no gaps, use a separate record that holds a single value representing the latest ID. An [`UPSERT`](/docs/reference/query-language/statements/upsert.md) statement is best here, which will allow the counter to be initialised if it does not yet exist, and updated otherwise. This is best done [inside a manual transaction](/docs/reference/query-language/statements/begin.md) so that the latest ID will be rolled back if any failures occur when creating the next record.

```surql
BEGIN TRANSACTION;
UPSERT person_id:counter SET num += 1;
-- Creates a person:1
CREATE type::record("person", person_id:counter.num);
COMMIT TRANSACTION;

BEGIN TRANSACTION;
-- Latest ID is now 2
UPSERT person_id:counter SET num += 1;
-- Whoops, invalid datetime format
-- Transaction fails and all changes are rolled back
CREATE type::record("person", person_id:counter.num) SET created_at = <datetime>'2025_01+01';
COMMIT TRANSACTION;

-- Latest ID is still 1
RETURN person_id:counter.num;
```

### Record IDs are record links

As a record ID is a pointer to all of the data of a record, a single record ID is enough to access all of a record's fields. This behaviour is the key to the convenience of [record links](/docs/reference/query-language/language-primitives/record-links.md) in SurrealDB, as holding a record ID is all that is needed for one record to have a link to another.

When using a standalone record ID as a record pointer, be sure to use the record ID itself.

```surql
CREATE person:1 SET data = {
    some: "demo",
    data: "for",
    demonstration: "purposes"
};

LET $record = SELECT id FROM person:1;
SELECT * FROM $record;
```

The output of the above query is just the `id` field on its own, as the `$record` parameter is an object with an `id` field, not the `id` field (the pointer) itself.

```surql title="Output"
[
	{
		id: person:1
	}
]
```

To rectify this, `id.*` can be used to follow the pointer to the entire data for the record.

```surql
SELECT id.* FROM $record;
```

```surql title="Output"
[
	{
		id: {
			data: {
				data: 'for',
				demonstration: 'purposes',
				some: 'demo'
			},
			id: person:1
		}
	}
]
```

---

Source: https://surrealdb.com/docs/learn/schema-management/tables-and-fields/record-ids-and-addressing

# Record IDs and addressing

How record IDs work, ranges, and practical choices for addressing data.

SurrealDB record IDs are composed of a table name and a record identifier separated by a `:` in between, allowing for a simple and consistent way to reference records across the database. Record IDs are used to uniquely identify records within a table, to [query](/docs/reference/query-language/statements/select.md), [update](/docs/reference/query-language/statements/update.md), and [delete](/docs/reference/query-language/statements/delete.md) records, and serve as [links](/docs/reference/query-language/language-primitives/record-links.md) from one record to another.

Record IDs can be constructed in a number of ways, including [alphanumeric text](/docs/reference/query-language/language-primitives/data-types/record-ids.md#text-record-ids), complex Unicode text and symbols, [numbers](/docs/reference/query-language/language-primitives/data-types/record-ids.md#numeric-record-ids), arrays, objects, [built-in ID generation functions](/docs/reference/query-language/language-primitives/data-types/record-ids.md#random-ids), and [a function to generate an ID from values](/docs/reference/query-language/functions/database-functions/type.md#typerecord).

All of the following are examples of valid record IDs in SurrealQL.

```surql
company:surrealdb
company:w6xb3izpgvz4n0gow6q7
reaction:`🤪`
weather:['London', d'2025-02-14T01:52:50.375Z']
```

As all record IDs are unique, trying to create a new record with an existing record ID will return an error. To create a record or modify it if the ID already exists, use an [`UPSERT`](/docs/reference/query-language/statements/upsert.md) statement or an [`INSERT`](/docs/reference/query-language/statements/insert.md#example-usage) statement with an `ON DUPLICATE KEY UPDATE` clause.

## Types of record IDs

### Random IDs

When you [create a record](/docs/reference/query-language/statements/create.md) without specifying the full ID, a random identifier is assigned after the table name.

```surql
CREATE company;
```

```surql title="Output"
[
	{
		id: company:ezs644u19mae2p68404j
	}
]
```

Record IDs can be generated with a number of built-in ID generation functions, which are cryptographically secure and suitable for dispersion across a distributed datastore. These include a 20 digit alphanumeric ID (the default), sequentially incrementing and temporally sortable ULID Record identifiers, and UUID version 7 Record identifiers.

```surql
-- Generate a random record ID 20 characters in length
-- Charset: `abcdefghijklmnopqrstuvwxyz0123456789`
CREATE temperature:rand() SET time = time::now(), celsius = 37.5;
-- Identical to the above CREATE statement, because
-- :rand() is the default random ID format
CREATE temperature SET time = time::now(), celsius = 37.5;

-- Generate a ULID-based record ID
CREATE temperature:ulid() SET time = time::now(), celsius = 37.5;
-- Generate a UUIDv7-based record ID
CREATE temperature:uuid() SET time = time::now(), celsius = 37.5;
```

### Text record IDs

Text record IDs can contain letters, numbers and `_` characters.

```surql
CREATE company:surrealdb SET name = 'SurrealDB';
CREATE user_version_2025 SET name = 'Alucard';
```

To create a record ID with complex characters, use <code>`</code> (backticks) around the table name and/or record identifier.

```surql
CREATE article:`8424486b-85b3-4448-ac8d-5d51083391c7` SET
    time = time::now(),
    author = person:tobie;

CREATE `Artykuł`:100 SET
    author = person:`Lech_Wałęsa`;
```

### Numeric record IDs

If you create a record ID with a number as a string, it will be stored with <code>`</code> backticks to differentiate it from a number.

```surql
CREATE article SET id = 10;
CREATE article SET id = "10";
CREATE article SET id = "article10";
SELECT VALUE id FROM article;
```

As the record ID `article:10` is different from ```article:`10` ```, no errors are returned when creating and both records turn up in the output of the `SELECT` statement. Meanwhile, the article with the identifier `article10` does not use backticks as there is no `article10` number to differentiate it from.

```surql title="Output"
[
	article:10,
	article:`10`,
    article:article10
]
```
If a numeric value is specified without any decimal point suffix and is within the range `-9223372036854775808` to `9223372036854775807` then the value will be parsed, stored, and treated as a 64-bit signed integer.

Any numeric numbers outside of the range of a signed 64-bit integer will be stored as a string.

```surql
CREATE temperature:17493 SET time = time::now(), celsius = 37.5;
CREATE year:29878977097987987979232 SET
    events = [
        "Galactic senate convenes",
        "Mr. Bean still waits in a field"
    ];
```

```surql title="Output"
-------- Query --------

[
	{
		celsius: 37.5f,
		id: temperature:17493,
		time: d'2025-02-17T06:21:08.911Z'
	}
]

-------- Query --------

[
	{
		events: [
			'Galactic senate convenes',
			'Mr. Bean still waits in a field'
		],
		id: year:`29878977097987987979232`
	}
]
```
### Array-based record IDs

Record IDs can be constructed out of arrays and even objects. This sort of record ID is most used when you have a field or two that will be used to look up records inside a [record range](/docs/reference/query-language/language-primitives/data-types/record-ids.md#record-ranges), which is extremely performant. This is in contrast to using a `WHERE` clause to filter, which involves a table scan.

Records in SurrealDB can store arrays of values, including other nested arrays or objects within them. Different types of values can be stored within the same array, unless defined otherwise.

```surql
CREATE weather:['London', d'2025-02-13T05:00:00Z'] SET
    temperature = 5.7,
    conditions = "cloudy";
```
### Why record ranges are performant

The main reason why record ranges are so performant is simply because the database knows ahead of time in which area to look for records in a query, and therefore has a smaller "surface area" to work in.

This can be demonstrated by seeing what happens when a single record range query encompasses all of the records in a database. The example below creates 10,000 `player` records that have an array-based record ID that begins with `'mage'`, allowing them to be used in a record range query, as well as a field called `class` that is also `'mage'`, which will be used in a `WHERE` clause to compare performance.

Interestingly, in this case a record range query is only somewhat more performant. This is because both queries end up iterating over 10,000 records, with the only difference being that the query with a `WHERE` clause also checks to see if the value of the `class` field is equal to `'mage'`.

```surql
FOR $_ IN 0..10000 {
    CREATE player:['mage', rand::id()] SET class = 'mage';
};

LET $_ = SELECT * FROM player:['mage', NONE]..['mage', ..];
LET $_ = SELECT * FROM player WHERE class = 'mage';
```
If the number of `player` records is extended to a larger number of classes, however, the difference in performance will be much larger. In this case the record range query is still only iterating a relatively small surface area of 10,000 records, while the second one has ten times this number to go through in addition to the `WHERE` clause on top.

```surql
FOR $_ IN 0..10000 {
  CREATE player:['mage', rand::id()] SET class = 'mage';
  CREATE player:['barbarian', rand::id()] SET class = 'barbarian';
  CREATE player:['rogue', rand::id()]     SET class = 'rogue';
  CREATE player:['bard', rand::id()]      SET class = 'bard';
  CREATE player:['sage', rand::id()]      SET class = 'sage';
  CREATE player:['psionic', rand::id()]   SET class = 'psionic';
  CREATE player:['thief', rand::id()]     SET class = 'thief';
  CREATE player:['paladin', rand::id()]   SET class = 'paladin';
  CREATE player:['ranger', rand::id()]    SET class = 'ranger';
  CREATE player:['cleric', rand::id()]    SET class = 'cleric';
};

LET $_ = SELECT * FROM player:['mage', NONE]..['mage', ..];
LET $_ = SELECT * FROM player WHERE class = 'mage';
```
## Defining record IDs in a schema

The type name of a record ID is `record`, which by default allows any sort of record. This type can be set inside a [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statement.

```surql
DEFINE FIELD possessions ON TABLE person TYPE option<array<record>>;
DEFINE FIELD friends ON TABLE person TYPE option<array<record<person>>>;
```
## IDs that are unique across tables

A record ID is unique within its table, so `car:490` and `van:490` can both exist. To make a key unique across several tables, and to find a record by its key alone, keep a registry table whose records have the same key and link to the record. An event on each table creates the registry entry, and because two records cannot have the same ID, a second table using the key fails and its write is rolled back:

```surql
DEFINE EVENT register ON car WHEN $event = "CREATE" THEN {
	CREATE type::record("uid", record::id($after.id)) SET record = $after.id;
};
DEFINE EVENT register ON van WHEN $event = "CREATE" THEN {
	CREATE type::record("uid", record::id($after.id)) SET record = $after.id;
};

CREATE car:490 SET model = "A";
CREATE van:490 SET model = "B";
```

```text title="Error output"
Error while processing event register: Database record `uid:490` already exists
```
The registry then finds a record without knowing its table:

```surql
uid:490.record.*;
```

```surql title="Output"
{ id: car:490, model: 'A' }
```

With random keys, such as the default IDs or `uuid()`, two tables are very unlikely to choose the same key, and the registry is then mainly a way to find a record by its key.

---

Source: https://surrealdb.com/docs/learn/schema-management/tables-and-fields/tables

# Tables

Declaring tables: schemaless vs schemafull, relations, views, changefeeds, and permissions.

A table is the first structural unit most people define: it is a named home for records. Even a `SELECT` statement requires a table to be defined before it will work.

```surql
SELECT *
  FROM doesnt_exist; -- Error: "The table 'doesnt_exist' does not exist"
DEFINE TABLE doesnt_exist;
SELECT * FROM doesnt_exist;
```

As a convenience, the creation of a record in a non-strict database will [define the table](/docs/reference/query-language/statements/define/database.md#defining-a-strict-database) for you. The following example does not return any errors because the table definition will exist once `CREATE` is executed.

```surql
CREATE doesnt_exist;
SELECT * FROM doesnt_exist;
```

`DEFINE TABLE` can also set high-level rules, such as whether new fields are allowed without a definition, whether records are normal documents or graph edges, whether the table is a pre-computed view, and who may select / create / update / delete. A [`COMMENT`](/docs/reference/query-language/statements/define/overview.md#comments-on-definitions) is optional, and is useful when the table's role, record-ID convention, or graph edges would otherwise be unclear to readers or to agents that load the schema via [`INFO`](/docs/reference/query-language/statements/info.md) or MCP.

Individual fields still use [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md); the table statement does not replace that.

You need appropriate auth (root, namespace, or database owner/editor) and an active [`USE`](/docs/reference/query-language/statements/use.md) for namespace and database. Full grammar and every clause live under [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) in the reference.

## Defining a changefeed on a table

The following expression shows how you can define a `CHANGEFEED` for a table. After creating, updating, and deleting records in the table as usual, using `SHOW CHANGES FOR` returns the mutations recorded in that window. If an entry reflects an update to an existing record and the feed stores differences (`INCLUDE ORIGINAL`), the diff is a **reverse diff**, namely the operations needed to reach the state immediately before that write. See [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md#example-usage) for full examples and response shapes.

```surql
-- Define the changefeed and its duration
-- Optionally, append INCLUDE ORIGINAL to include info
-- on the current record before a change took place
DEFINE TABLE reading CHANGEFEED 3d;

-- Create some records in the reading table
CREATE reading SET story = "Once upon a time";
CREATE reading SET story = "there was a database";

-- Replay changes to the reading table since a certain date
-- Must be after the timestamp at which the changefeed began
SHOW CHANGES FOR TABLE reading SINCE d"2025-09-07T01:23:52Z" LIMIT 10;

-- Alternatively, show the changes for the table since a version number
SHOW CHANGES FOR TABLE reading SINCE 0 LIMIT 10;
```

## Schemafull tables

The following example demonstrates the `SCHEMAFULL` portion of the `DEFINE TABLE` statement. When a table is defined as schemafull, the database strictly enforces any schema definitions that are specified using the `DEFINE TABLE` statement. New fields can not be added to a `SCHEMAFULL` table unless they are defined via the [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statement.

```surql
-- Create schemafull user table.
DEFINE TABLE user SCHEMAFULL;

-- Define some fields.
DEFINE FIELD firstName ON TABLE user TYPE string;
DEFINE FIELD lastName ON TABLE user TYPE string;
DEFINE FIELD email ON TABLE user TYPE string
  ASSERT string::is_email($value);

-- Statement succeeds as all defined fields are present
CREATE user CONTENT {
    firstName: 'Tobie',
    lastName: 'Hitchcock',
    email: 'Tobie.Hitchcock@surrealdb.com'
};
```

## Schemaless tables

The following example demonstrates the `SCHEMALESS` portion of the `DEFINE TABLE` statement. This allows you to explicitly state that the specified table has no schema.

```surql
-- Create schemaless user table.
DEFINE TABLE user SCHEMALESS;

-- Define some fields.
DEFINE FIELD firstName ON TABLE user TYPE string;
DEFINE FIELD lastName ON TABLE user TYPE string;
DEFINE FIELD email ON TABLE user TYPE string
  ASSERT string::is_email($value);

-- Statement succeeds even with extra `photoURI` field, as table is schemaless
CREATE user:tobie SET 
    firstName = 'Tobie', 
    lastName = 'Hitchcock', 
    email = 'Tobie.Hitchcock@surrealdb.com', 
    photoURI = 'photo/yxCFi22Jw2.webp';

-- Statement fails because `email` does not pass validation
CREATE user:jaime SET 
    firstName = 'Jamie', 
    lastName = 'Hitchcock', 
    email = 'Jamie.Hitchcock', 
    photoURI = 'photo/yxCFi22Jw2.webp';
```

## `DROP` tables and pre-computed table views

In SurrealDB, like in other databases, you can create views. The way you create views is using the `DEFINE TABLE` statement like you would for any other table, then adding the `AS` clause at the end with your `SELECT` query.

`DROP` tables are useful in combination with events or foreign (view) tables, as you can compute a record and drop the input.

```surql
DEFINE TABLE review DROP;
-- Define a table as a view which aggregates data from the review table
DEFINE TABLE avg_product_review TYPE NORMAL AS
SELECT
	count() AS number_of_reviews,
	math::mean(<float> rating) AS avg_review,
	->product.id AS product_id,
	->product.name AS product_name
FROM review
GROUP BY product_id, product_name;

-- Query the projection
SELECT * FROM avg_product_review;
```

## Defining permissions

Table `PERMISSIONS` control what [record users](/docs/learn/security/authentication/users.md#record-users) (and [guests](/docs/learn/security/authorization/capabilities.md#guest-access), when guest access is enabled) may do with records in that table. They do not restrict [system users](/docs/learn/security/authentication/users.md#system-users) at the root, namespace, or database level, as those users are governed by roles instead.

If you omit the clause, SurrealDB will default to `PERMISSIONS NONE` which denies `SELECT`, `CREATE`, `UPDATE`, and `DELETE` for record users until you grant access explicitly. The opposite shorthand is `PERMISSIONS FULL`, which allows all four operations.

```surql
CREATE some_table;
DEFINE TABLE some_other_table;

INFO FOR DB;
```

```surql title="Output"
{
	analyzers: {},
	functions: {},
	models: {},
	params: {},
	scopes: {},
	tables: {
		some_other_table: 'DEFINE TABLE some_other_table TYPE ANY SCHEMALESS PERMISSIONS NONE',
		some_table: 'DEFINE TABLE some_table TYPE ANY SCHEMALESS PERMISSIONS NONE'
	},
	tokens: {},
	users: {}
}
```

You can also set independent rules for selecting, creating, updating, and deleting data. Each `FOR` clause is a SurrealQL expression evaluated in the context of the current authentication (often using the [`$auth`](/docs/learn/security/authorization/permissions-and-row-level-security.md) parameter which is set when a record user authenticates).

```surql
-- Specify access permissions for the 'post' table
DEFINE TABLE post SCHEMALESS
	PERMISSIONS
		FOR select
			-- Published posts can be selected
			WHERE published = true
			-- A user can select all their own posts
			OR user = $auth.id
		FOR create, update
			-- A user can create or update their own posts
			WHERE user = $auth.id
		FOR delete
			-- A user can delete their own posts
			WHERE user = $auth.id
			-- Or an admin can delete any posts
			OR $auth.admin = true
;
```

Field permissions work the same way but default to `PERMISSIONS FULL` instead of `NONE`. The table is the main access gate, while field permissions only narrow further when you need to (for example hiding a password). With `FULL`, a field follows the table's rules without adding its own. See [Setting permissions on fields](/docs/learn/schema-management/tables-and-fields/fields-and-validation.md#setting-permissions-on-fields) and [Permissions & row-level security](/docs/learn/security/authorization/permissions-and-row-level-security.md).

## Table with specialized `TYPE` clause

When defining a table in SurrealDB, you can specify the type of data that can be stored in the table. This can be done using the `TYPE` clause, followed by either `ANY`, `NORMAL`, or `RELATION`.

With `TYPE ANY`, you can specify a table to store any type of data, whether it's a normal record or a relational record.

With `TYPE NORMAL`, you can specify a table to only store "normal" records, and not relations. When a table is defined as `TYPE NORMAL`, it will not be able to store relations. This can be useful when you want to restrict the type of data that can be stored in a table in schemafull mode.

Finally, with `TYPE RELATION`, you can specify a table to only store relational type content. This can be useful when you want to restrict the type of data that can be stored in a table.

```surql
DEFINE TABLE person TYPE ANY;
DEFINE TABLE person;
```

With `TYPE NORMAL`, you can specify a table to only store "normal" records, and not relations.

```surql
-- Since it's default, we can also omit the TYPE clause
DEFINE TABLE person TYPE NORMAL;
```

With `TYPE RELATION`, you can specify a table to only store relational type content, and restrict what kind of relations can be stored.

```surql
-- Just a RELATION table, no constraints on the type of table
DEFINE TABLE likes TYPE RELATION;

-- Define a relation table, and constrain the type of relation which can be stored
DEFINE TABLE likes TYPE RELATION FROM user TO post;
-- OR use IN and OUT alternatively to FROM and TO
DEFINE TABLE likes TYPE RELATION IN user OUT post;
-- To allow a link to one of a possible set of record types, use the | operator
DEFINE TABLE likes TYPE RELATION FROM user TO post|video;
DEFINE TABLE likes TYPE RELATION IN user OUT post|video;
```

## Using ENFORCED to ensure that related records exist

As relations are represented by standalone tables, they can be constructed before any linked records exist.

```surql
RELATE city:one->road_to->city:two SET
    distance = 12.4,
    slope = 5.4;
```

As such, a query on the relation will return nothing until the records it has been defined upon are created.

```surql
SELECT ->road_to->city FROM city;

CREATE city:one, city:two;
SELECT ->road_to->city FROM city;
```

```surql title="Output"
-------- Query --------

[]

-------- Query --------

[
	{
		"->road_to": {
			"->city": [
				city:two
			]
		}
	},
	{
		"->road_to": {
			"->city": []
		}
	}
]
```

If this behaviour is not desirable, the `ENFORCED` clause can be used on a table of `TYPE RELATION` to disallow a `RELATE` statement from working unless it points to existing data.

```surql
DEFINE TABLE road_to TYPE RELATION IN city OUT city ENFORCED;

RELATE city:one->road_to->city:three SET
    distance = 5.5,
    slope = 30.0;
```

```surql title="Output"
"The record 'city:one' does not exist"
```

---

Source: https://surrealdb.com/docs/learn/security

# Security

Secure a SurrealDB deployment with authentication and authorisation. Plus security best practices.

SurrealDB provides a layered security model that covers how users and systems prove their identity, what they are allowed to do once authenticated, and the operational practices that keep a deployment safe.

This section is organised into three areas:

- [Authentication](/docs/learn/security/authentication/overview.md) - signing in with credentials, record-based access, and third-party identity providers.
- [Authorization](/docs/learn/security/authorization/permissions-and-row-level-security.md) - controlling access at the table, field, and row level with the `PERMISSIONS` clause and JWTs.
- [Best practices](/docs/learn/security/best-practices/security-best-practices.md) - guidance on secure configuration, token handling, network exposure, and common pitfalls.

## Authorization

- [Tokens & JWTs](/docs/learn/security/authorization/tokens-and-jwts.md) - how a token carries identity and what the database checks

- [Guest access](/docs/learn/security/authorization/capabilities.md#guest-access) - letting clients that have not signed in read public tables or call public functions, turned on with `--allow-guests` and limited by each table's and function's `PERMISSIONS`

Logic that goes beyond reading and writing records, such as an order that has to update stock and create an invoice together, can live in the database as a [custom function](/docs/learn/querying/concepts-and-guides/custom-functions.md) or behind an HTTP endpoint defined with [`DEFINE API`](/docs/reference/query-language/statements/define/api.md). A function's `PERMISSIONS` clause decides who may call it, while its body runs with the caller's own table and field permissions - see [function permissions](/docs/reference/query-language/statements/define/function.md#permissions).

## Troubleshooting

- [Troubleshooting](/docs/learn/security/best-practices/troubleshooting.md) - what a rejected token, an expired session or a denied permission looks like, and what to check

---

Source: https://surrealdb.com/docs/learn/security/authentication/overview

# Authentication methods

The four ways to authenticate with SurrealDB - system users, record users, JWT access and bearer access - what each is for, and how to sign in over HTTP or from an SDK.

SurrealDB has four authentication methods. Which one you want depends on who is signing in: a person administering the instance, an end user of your application, a client an external identity provider has already authenticated, or another system.

Each method is defined in SurrealQL and then used the same way from every interface, so the choice below decides the rest of the page.

## Choosing a method

| Method | Who it is for | Defined with | Signed in with |
| ------ | ------------- | ------------ | -------------- |
| [System users](/docs/learn/security/authentication/users.md#system-users) | Operators and services administering an instance | [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md) at root, namespace or database level | A username and password |
| [Record users](/docs/learn/security/authentication/users.md#record-users) | End users of your application, one record each | [`DEFINE ACCESS ... TYPE RECORD`](/docs/reference/query-language/statements/define/access/record.md), whose `SIGNUP` and `SIGNIN` clauses are queries you write | Whatever fields your `SIGNIN` query reads |
| [JWT access](/docs/reference/query-language/statements/define/access/jwt.md) | Clients an external provider has already authenticated | [`DEFINE ACCESS ... TYPE JWT`](/docs/reference/query-language/statements/define/access/jwt.md), holding the issuer's public key or shared secret | A token the provider issued |
| [Bearer access](/docs/reference/query-language/statements/define/access/bearer.md) | Other systems and software | [`DEFINE ACCESS ... TYPE BEARER`](/docs/reference/query-language/statements/define/access/bearer.md), plus an [`ACCESS ... GRANT`](/docs/reference/query-language/statements/access.md#grant) per client | The key from a grant |

System users and record users answer to credentials SurrealDB holds. JWT access holds no end user credential: it checks the signature on a token an external provider issued, then trusts the claims inside it, which is how an OpenID Connect or OAuth provider is brought in. It does hold the material for that check, and which kind matters - a [public key](/docs/reference/query-language/statements/define/access/jwt.md#public-key-cryptography) or a [JWKS URL](/docs/reference/query-language/statements/define/access/jwt.md#json-web-key-set-jwks) can only verify, while an [HMAC](/docs/reference/query-language/statements/define/access/jwt.md#hash-based-message-authentication-code-hmac) key is symmetric and can also sign. Bearer access sits between the two, issuing a key per client that you can [audit](/docs/reference/query-language/statements/access.md#show) and [revoke](/docs/reference/query-language/statements/access.md#revoke) without touching the user it acts as.

> [!WARNING]
> `TYPE JWT` uses `HS256` when no algorithm is given, and the HMAC algorithms (`HS256`, `HS384`, `HS512`) take one secret that both signs and verifies. Anyone holding it can issue tokens with any claims they like, and SurrealDB will trust them. Protect that key on the SurrealDB side as well as at the issuer, or define the access method with a public key or a JWKS URL, neither of which can sign.

> [!NOTE]
> A record user is scoped to one database and restricted by table and field [permissions](/docs/learn/security/authorization/permissions-and-row-level-security.md). A root system user is not restricted by permissions at all, so it is the wrong credential to put in an application.

## Signing in

Every interface signs in with the same credentials, so an example written for one carries over to the others.

**SurrealQL, HTTP and the SDKs** are covered end to end on [Users](/docs/learn/security/authentication/users.md), which defines a user of each kind and then signs in as it.

**Over HTTP**, post the credentials to [`POST /signin`](/docs/reference/rest-api/http-protocol.md#signin), or create a record user with [`POST /signup`](/docs/reference/rest-api/http-protocol.md#signup). Both return a token for later requests:

```bash
curl -X POST \
	-H "Accept: application/json" \
	-d '{"user":"root", "pass":"secret"}' \
	http://localhost:8000/signin
```

**From an SDK**, each language documents its own `signin` call and the shape of the credentials it takes:

[Rust](/docs/reference/rust/methods/signin.md) · [JavaScript](/docs/reference/javascript/concepts/authentication.md) · [Python](/docs/reference/python/concepts/authentication.md) · [Go](/docs/reference/golang/concepts/authentication.md) · [Java](/docs/reference/java/concepts/authentication.md) · [Kotlin](/docs/reference/kotlin/concepts/authentication.md) · [.NET](/docs/reference/dotnet/methods/signin.md) · [PHP](/docs/reference/php/v1/methods/signin.md) · [Swift](/docs/reference/swift/methods/signin.md) · [Mojo](/docs/reference/mojo/methods/signin.md)

## After signing in

- [Sessions](/docs/learn/security/authentication/users.md#sessions) - what the connection carries once authenticated, and how tokens and sessions expire.
- [Permissions and row-level security](/docs/learn/security/authorization/permissions-and-row-level-security.md) - what the authenticated user is then allowed to read and write.
- [Tokens and JWTs](/docs/learn/security/authorization/tokens-and-jwts.md) - the claims SurrealDB reads from a token, and the parameters they populate.
- [Security best practices](/docs/learn/security/best-practices/security-best-practices.md) - choosing expiry, storing secrets, and what to expose to a browser.

---

Source: https://surrealdb.com/docs/learn/security/authentication/summary

# Summary

This page summarizes some of the security features offered by SurrealDB as well as some security elements of its development process with the goal of providing a starting point to both new and experienced users who wish to know more about the security of SurrealDB.

This page is a starting point for the security of SurrealDB: the features the product offers, and the security practices behind how it is built. Each section links to the fuller treatment elsewhere in the documentation.

> [!NOTE]
> This page is intended to direct the reader to other more specific and comprehensive resources. Some information shown in this page may be simplified or omit information that could be relevant in a particular scenario. When available, we recommend that you consult the provided references.

## Product

### Capabilities
SurrealDB offers the ability to limit its functionality to what is strictly required to reduce its attack surface. Most capabilities (e.g. scripting, networking…) are disabled by default. Even when enabled, capabilities can be restricted to specific targets such as functions that can be executed or network addresses that outbound connections can be made to.
- [Security: Capabilities](/docs/learn/security/authorization/capabilities.md)
- [Security Best Practices: Capabilities](/docs/learn/security/best-practices/security-best-practices.md#capabilities)

### System users
SurrealDB is managed by system users. Such users can be defined at the root, namespace and database level, which they can sign into with username and password. The password for those users is stored hashed and salted using the [Argon2id](https://datatracker.ietf.org/doc/html/rfc9106#name-recommendations) algorithm with default parameters, which is resistant to modern attacks. Passwords can also be provided to SurrealDB already hashed in the form of a passhash, ensuring that the SurrealDB server never has knowledge of the original password. When defining a user, the maximum duration for authentication tokens and authenticated sessions can be explicitly defined to mitigate the impact of compromised credentials.
- [Statement: DEFINE USER](/docs/reference/query-language/statements/define/user.md)
- [Security Best Practices: Expiration](/docs/learn/security/best-practices/security-best-practices.md#expiration)

#### Roles
SurrealDB implements Role-Based Access Control (RBAC) for system users at any level. This means that even if a person or system needs to authenticate with a SurrealDB user at the root, namespace or database level, its access can be restricted within that level by the owner, editor and viewer roles to minimize the impact of an incident involving the user.
- [Statement: DEFINE USER (Roles)](/docs/reference/query-language/statements/define/user.md#roles)
- [Security: Authentication (System Users)](/docs/learn/security/authentication/users.md#system-users)
- [Security Best Practices: Least Privilege](/docs/learn/security/best-practices/security-best-practices.md#least-privilege)

### Record users
SurrealDB allows the creation of users that can be easily signed up but with no access to the database aside from specifically defined permissions. This allows clients like single-page applications or mobile applications to directly connect to the database and access certain data or even run arbitrary queries. When accessing the database as a record user, users will be restricted by table and field permissions, which deny all operations by default. End users can independently sign up and sign in to use SurrealDB following custom logic that can be defined with SurrealQL.
- [Statement: DEFINE ACCESS ... TYPE RECORD](/docs/reference/query-language/statements/define/access/record.md)
- [Security: Authentication (Record Users)](/docs/learn/security/authentication/users.md#record-users)

#### Permissions
SurrealDB enforces table and field permissions for record users. Those permissions ensure that record users can only perform explicitly defined actions over explicitly defined data. Permissions are specified when defining a table or a field and use SurrealQL syntax to establish the conditions under which the table or the field can be queried with SELECT, UPDATE, CREATE and DELETE operations individually. Tables default to `PERMISSIONS NONE`, so a record user cannot query any data unless you grant access; fields default to `PERMISSIONS FULL`.
- [Statement: DEFINE TABLE](/docs/reference/query-language/statements/define/table.md#defining-permissions)
- [Statement: DEFINE FIELD](/docs/reference/query-language/statements/define/field.md#setting-permissions-on-fields)
- [Permissions & row-level security](/docs/learn/security/authorization/permissions-and-row-level-security.md)

### JSON web tokens
SurrealDB internally uses JWTs to perform and manage authentication for both system and record users. It also supports accepting tokens issued by third party authentication providers in order to authenticate as a system user on any level as well as a record user for an application. This ensures that advanced authentication features not present in SurrealDB can be integrated through a third party provider. SurrealDB's support for JSON Web Key Sets (JWKS) makes this integration simpler and more reliable.
- [Statement: DEFINE ACCESS ... TYPE JWT](/docs/reference/query-language/statements/define/access/jwt.md)
- [Statement: DEFINE ACCESS ... TYPE RECORD ... WITH JWT](/docs/reference/query-language/statements/define/access/record.md#with-json-web-token)
- [Tutorial: Integrate Auth0 as an authentication provider](/docs/explore/tutorials/tutorials/auth0-integration.md)
- [Tutorial: Integrate AWS Cognito as an authentication provider](/docs/explore/tutorials/tutorials/aws-cognito-integration.md)
- [Security Best Practices: JSON Web Tokens](/docs/learn/security/best-practices/security-best-practices.md#json-web-tokens)
- [Security Best Practices: Expiration](/docs/learn/security/best-practices/security-best-practices.md#expiration)

### Custom authentication
SurrealDB allows record users to authenticate using a token that can be issued by a third party or SurrealDB itself after successful authentication. When verifying these tokens, custom logic can be implemented using SurrealQL to abort authentication while returning a custom error if certain conditions are not met. This logic can be used to implement various kinds of token auditing and revocation mechanisms. Additionally, tokens issued by SurrealDB can be customised to be signed with specific keys or using a specific algorithm so that other services can rely on the authentication provided by SurrealDB.
- [Statement: DEFINE ACCESS ... TYPE RECORD ... AUTHENTICATE](/docs/reference/query-language/statements/define/access/record.md#with-authenticate-clause)
- [Statement: DEFINE ACCESS ... TYPE RECORD ... WITH ISSUER](/docs/reference/query-language/statements/define/access/record.md#with-issuer)
- [Security Best Practices: Expiration](/docs/learn/security/best-practices/security-best-practices.md#expiration)

### Parametrized queries
SurrealDB is usually queried through [multiple SDKs](/docs/languages.md) and an [RPC interface](/docs/reference/rest-api/rpc-protocol.md). The default query method for both of those interfaces is designed to accept query logic and variables separately to prevent query injection attacks like SQL injection. This separation ensures that user-controlled inputs are not mixed with any business logic defined in SurrealQL.
- [Interfaces: RPC (Query Method)](/docs/reference/rest-api/rpc-protocol.md#query)
- [Interfaces: Rust SDK (Query Method)](/docs/reference/rust/methods/query.md)
- [Security Best Practices: Query Safety](/docs/learn/security/best-practices/security-best-practices.md#query-safety)

### Sessions
SurrealDB accepts persistent connections through its RPC interface in the form of sessions. Sessions will usually be associated with an authentication token that represents a system user or a record user. Sessions and tokens can be configured to have different expiration times. Thanks to this, tokens can be issued to last the minimum time required to mitigate the impact of an attacker stealing the token while ensuring that sessions can last as long as required for the service or application.
- [Security: Sessions](/docs/learn/security/authentication/users.md#sessions)
- [Statements: DEFINE USER (Duration)](/docs/reference/query-language/statements/define/user.md#duration)
- [Statements: DEFINE ACCESS (Duration)](/docs/reference/query-language/statements/define/access.md#duration)
- [Security Best Practices: Expiration](/docs/learn/security/best-practices/security-best-practices.md#expiration)
- [Interfaces: RPC (Authenticate)](/docs/reference/rest-api/rpc-protocol.md#authenticate)

### Cryptographic functions
SurrealDB provides a series of cryptographic functions that can be called from within SurrealQL in order to implement modern security practices in your application. This includes state of the art password hashing algorithms such as Argon2, Bcrypt, Scrypt and PBKDF2. Traditional hashing algorithms like SHA-256 and SHA-512 are also provided for other applications such as integrity verification.
- [Cryptographic functions](/docs/reference/query-language/functions/database-functions/crypto.md)
- [Security Best Practices: Passwords](/docs/learn/security/best-practices/security-best-practices.md#passwords)

### Other security functions
SurrealDB also provides other functions that support developers with building secure applications. An example of this are functions which encode and sanitize HTML content that is stored in the database to prevent code injection (which can lead to cross-site scripting, clickjacking or content injection) when displaying the content in an HTML page.
- [string::html::encode() function](/docs/reference/query-language/functions/database-functions/string.md#stringhtmlencode)
- [string::html::sanitize() function](/docs/reference/query-language/functions/database-functions/string.md#stringhtmlsanitize)
- [Security Best Practices: Content Safety](/docs/learn/security/best-practices/security-best-practices.md#content-safety)

### Transport layer security
SurrealDB has the ability to provide a secure TLS connection to its HTTP server without the need for a reverse proxy or load balancer. A certificate and a private key can be provided when starting the server in order to provide a secure connection.
- [CLI Start command](/docs/reference/cli/db/commands/start.md)
- [Security Best Practices: Encryption in Transit](/docs/learn/security/best-practices/security-best-practices.md#encryption-in-transit)

## Process

### Open source security
SurrealDB has an open source security policy extending its security process to the wider community. This policy is made available through GitHub and a “security.txt” file, and allows SurrealDB to benefit from the security expertise and resources of its community. In turn, it provides the community with safe and responsible avenues to contribute to the security of a source available product that they rely on.
- [GitHub: Security Policy](https://github.com/surrealdb/surrealdb/security/policy)
- [Website: Well-Known Security File](/.well-known/security.txt)

### Responsible disclosure
SurrealDB encourages external contributors to report security vulnerabilities following a small set of practices described in the open source security policy. SurrealDB commits to address all legitimate reports within three days, work on resolving the issue while keeping the reporter updated and crediting the reporter when an advisory is eventually published. The responsible disclosure process protects legitimate security reporters from legal repercussions and promotes an open discussion around the security of SurrealDB.
- [GitHub: Security Policy (Reporting a Vulnerability)](https://github.com/surrealdb/surrealdb/security/policy#reporting-a-vulnerability)

### Security advisory
SurrealDB releases security advisories whenever a significant security issue has been resolved in the product. These advisories provide details about the vulnerability, its potential impact, affected versions and possible workarounds. The publication of advisories assists both humans and automations in identifying existing risks early as well as being aware of how to immediately mitigate or resolve them.
- [GitHub: Security Policy (Security Advisories)](https://github.com/surrealdb/surrealdb/security/policy#security-advisories)
- [GitHub: Security Advisories](https://github.com/surrealdb/surrealdb/security/advisories)

### Software composition analysis
SurrealDB includes SCA in its development process by using both the Cargo Deny binary crate for Rust code as well as Dependabot in its CI/CD pipelines. The latter ensures that changes including dependencies with known vulnerabilities cannot be merged unless those vulnerabilities are explicitly acknowledged in a public file; this usually requires either updating or replacing the affected dependency. The former provides notification of emerging vulnerabilities in dependencies that are currently being used by SurrealDB so that they can be updated or replaced.
- [GitHub: Security Policy (Dependencies)](https://github.com/surrealdb/surrealdb/security/policy#dependencies)
- [GitHub: Cargo Deny Configuration](https://github.com/surrealdb/surrealdb/blob/main/deny.toml)

### Fuzzing
SurrealDB automatically identifies crashes and other security-relevant bugs using automated coverage-based fuzzing. This process is hosted by the Google OSS-Fuzz project and continuously runs SurrealDB with a huge range of valid and invalid inputs mutated with the help of artificial intelligence in order to cover every part of the code. This enables the early detection of edge cases which may result in security issues. The OSS-Fuzz project automatically publicly discloses bugs that have not been fixed in 90 days.
- [GitHub: Security Policy (Fuzzing)](https://github.com/surrealdb/surrealdb/security/policy#fuzzing)

### Supply chain security
SurrealDB aims to reduce the risk and impact of compromised dependencies in its supply chain by implementing processes and automation to ensure that new and updated dependencies are consciously evaluated, especially when they have not yet been audited by trusted organisations or they extend their access to Rust standard library interfaces. This is accomplished in Rust code by using the Cargo ACL and Cargo Vet binary crates, both of which will need to pass in CI/CD before any changes to dependencies can be merged.
- [GitHub: Supply Chain Security](https://github.com/surrealdb/surrealdb/blob/main/supply-chain/README.md)

---

Source: https://surrealdb.com/docs/learn/security/authentication/users

# Users

There are multiple forms of authentication built into SurrealDB, supporting server-side and client-side authentication.

SurrealDB has two kinds of user, and they exist for different purposes: one administers the database, the other belongs to the application built on it.

- [System users](#system-users): Created by the SurrealDB administrator and used for managing and consuming the database.
- [Record users](#record-users): Used for consuming the database within permissions logic, they allow custom signup and signin.

## System users

System users are users defined directly on SurrealDB by an administrator.

Users may belong to different levels (root, namespace or database) and have different roles assigned to limit what they can do to the system. Users are defined with the [DEFINE USER](/docs/reference/query-language/statements/define/user.md) statement.

SurrealDB implements RBAC (Role Based Access Control) to define what a user can do. Each user is assigned one or more roles (currently limited to the built-in `OWNER`, `EDITOR` and `VIEWER` roles) and will be allowed to perform an action over a resource as long as at least one of their roles allow it.

Go to [DEFINE USER](/docs/reference/query-language/statements/define/user.md) for more information.

### Example: Define a root-level user

Root-level users have visibility into all namespaces and databases, which means that their permissions apply to all of those levels.

In this example we will create a root-level user `john` with a password and the `OWNER` role:

```surql
DEFINE USER john ON ROOT PASSWORD "VerySecurePassword!" ROLES OWNER;
```

Note that even a root-level user can be given a limited role such as `VIEWER`. This can be useful for automated services that need to monitor each namespace and database, or colleagues who need to see data but should not change it.

```surql
DEFINE USER birthday_bot ON ROOT PASSWORD "botpassword9!" ROLES VIEWER;
DEFINE USER clumsy_ceo ON ROOT PASSWORD "password" ROLES VIEWER COMMENT "Don't let the CEO have more than VIEWER access";
```

This explainer video covers the user groups SurrealDB authenticates as, and the roles a system user can hold:

[Watch on YouTube](https://www.youtube.com/watch?v=cGAxH9FezUY)

### Sign in using the new user

Examples using the JavaScript SDK or a raw HTTP request.

#### JavaScript SDK

```javascript
const db = new Surreal();
db.connect('ws://localhost:8000/rpc', {
	namespace: 'main',
	database: 'main',
});

db.signin({
	username: 'john',
	password: 'VerySecurePassword!',
});
```

#### HTTP request

```bash
curl -X POST \
	-H "Accept: application/json" \
	-d '{"user":"john", "pass":"VerySecurePassword!"}' \
	http://localhost:8000/signin
```

### Example: Define a database-level user

Database-level users have visibility into all resources that belong to the database where the user is defined.

In this example we will create a database-level user `mary` with a password and the `EDITOR` role:

```surql
DEFINE USER mary ON DATABASE PASSWORD "VerySecurePassword!" ROLES EDITOR;
```

### Sign in using the new user

Examples using the JavaScript SDK or a raw HTTP request.

Notice how we need to pass along `NS` and `DB` properties here, to let SurrealDB know where the user is defined.

#### JavaScript SDK

```javascript
const db = new Surreal();
db.connect('ws://localhost:8000/rpc', {
	namespace: 'main',
	database: 'main',
});

db.signin({
	// Because we are signin in a database user, we need to let SurrealDB know on which database this user is located.
	namespace: 'main',
	database: 'main',

	username: 'mary',
	password: 'VerySecurePassword!',
});
```

#### HTTP request

```bash
curl -X POST \
	-H "Accept: application/json" \
	-d '{"NS":"main", "DB":"main", "user":"mary", "pass":"VerySecurePassword!"}' \
	http://localhost:8000/signin
```

## Record users

Record users represent users that are defined as a record in a database instead of through the `DEFINE USER` statement. Since these users exist as regular database records, they can have associated fields containing any information required for authentication and authorization.

Thanks to this, SurrealDB is able to offer mechanisms to define your own signin and signup logic as well as custom table and field permissions for record users. This feature contributes to making SurrealDB an all-in-one BaaS (Backend-as-a-Service).

Record users are defined with the [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md) statement of `TYPE RECORD`.

A record access is configured with the following specific clauses:

- `SIGNUP`: Defines the logic for when a user signs up as a record user. Usually creates a new record in a table.
- `SIGNIN`: Defines the logic for when a user signs in as a record user. Usually checks credentials against table records.

By default, record users have no permissions. They don't use the Role-Based Access Control (RBAC) system and can only access data if allowed by a `PERMISSIONS` clause, which is defined on every data resource (i.e. tables and fields) and defaults to `NONE`.

To learn more about creating a record user, refer to the [DEFINE ACCESS ... TYPE RECORD](/docs/reference/query-language/statements/define/access/record.md) documentation.

### Example: Setup record authentication

We will go over one of the many ways you can set up record authentication. Given you can define your own logic, there is not a single way to do it. Feel free to modify where needed!

#### Define the user table and fields

Typically, you would define a user table where new records are created every time a user signs up.

In the following code snippet we will define the `user` table and a few `fields` that enforce the following:

- An authenticated user can select, update and delete its own user record.
- Asserts that the email provided by the user is actually an email address.
- Forbid users to use an email that is already in use by another user. We do this by creating a unique index for the email field.

```surql title="Define tables and fields"
DEFINE TABLE user SCHEMAFULL
	PERMISSIONS
		FOR select, update, delete WHERE id = $auth.id;

DEFINE FIELD name ON user TYPE string;
DEFINE FIELD email ON user TYPE string ASSERT string::is_email($value);
DEFINE FIELD password ON user TYPE string;

DEFINE INDEX email ON user FIELDS email UNIQUE;
```

##### Define the user record access

Defining the `user` record access allows users to sign in and sign up by using the table and fields defined in the previous step.

We will configure the record access like this:

- The signin logic needs the `email` and `password` parameters to be provided by the user. In the query, we can use them as `$email` and `$password`.
- The signup logic needs the `name`, `email` and `password` parameters to be provided by the user. In the query, we can use them as `$name`, `$email` and `$password`.

```surql title="Scope definition"
DEFINE ACCESS user ON DATABASE TYPE RECORD
	SIGNIN (
		SELECT * FROM user WHERE email = $email
		  AND crypto::argon2::compare(password, $password)
	)
	SIGNUP (
		CREATE user CONTENT {
			name: $name,
			email: $email,
			password: crypto::argon2::generate($password)
		}
	);
```

### Sign up as a user record

Now that the record access is defined, we can start using it.

Examples using the JavaScript SDK or a raw HTTP request.

#### JavaScript SDK

```js
const db = new Surreal();
db.connect('ws://localhost:8000/rpc', {
	namespace: 'main',
	database: 'main',
});

db.signup({
	namespace: 'main',
	database: 'main',

	// Provide the name of the access method
	access: 'user',

	// Provide the variables used by the signup query
	variables: {
    	name: 'John Doe',
    	email: 'john.doe@example.com',
    	password: 'VerySecurePassword!',
	}
});
```
#### HTTP request

```bash
curl -X POST \
	-H "Accept: application/json" \
	-d '{"NS":"main", "DB":"main", "AC":"user", "name":"John Doe", "email":"john.doe@example.com", "password":"VerySecurePassword!"}' \
	http://localhost:8000/signup
```

### Sign in as a record user

Once a user has signed up, it can now sign in when needed. A sign-in authenticates the session it is made on. To sign in several users over one WebSocket connection, the JavaScript SDK can open a separate session for each user with `db.newSession()`, on SurrealDB 3.0 and later. See [Multiple sessions](/docs/reference/javascript/concepts/multiple-sessions.md).

Examples using the JavaScript SDK or a raw HTTP request.

#### JavaScript SDK

```js
const db = new Surreal();
db.connect('ws://localhost:8000/rpc', {
	namespace: 'main',
	database: 'main',
});

db.signin({
	namespace: 'main',
	database: 'main',

	// Provide the name of the access method
	access: 'user',

	// Provide the variables used by the signin query
	variables: {
    	email: 'john.doe@example.com',
    	password: 'VerySecurePassword!',
	}
});
```

#### HTTP request

```bash
curl -X POST \
	-H "Accept: application/json" \
	-d '{"NS":"main", "DB":"main", "AC":"user", "email":"john.doe@example.com", "password":"VerySecurePassword!"}' \
	http://localhost:8000/signin
```

## Sessions

Whenever authentication is performed with any kind of user against SurrealDB, a session is established between the client and the SurrealDB server with which the connection was established. These sessions exist only in memory on the server for the duration of the connection, whether it is a single request through the [HTTP REST API](/docs/reference/rest-api/http-protocol.md) or through multiple requests in the same connection using the [WebSocket API](/docs/reference/rest-api/rpc-protocol.md) and any of the [SDKs](/docs/languages.md) that use it.

### Parameters

Certain security-related parameters are automatically set by SurrealDB in the context of a session. These parameters can be referenced in SurrealQL during authentication and authorization.

#### Session

The `$session` parameter contains information about the current session. This parameter is set in every SurrealDB session.

In the following example, you can see the result of the `SELECT * FROM $session` query in an authenticated session for a record user using the [HTTP REST API](/docs/reference/rest-api/http-protocol.md):

```json
{
	"ac": "user",
	"db": "main",
	"exp": null,
	"id": "example-client",
	"ip": "127.0.0.1",
	"ns": "main",
	"or": "http://www.example.com",
	"rd": "user:example",
	"tk": {
		"AC": "user",
		"DB": "main",
		"ID": "user:example",
		"NS": "main",
		"exp": 1723118226,
		"iat": 1723114626,
		"iss": "SurrealDB",
		"jti": "3b3fe74a-955c-46d7-9400-363848912292",
		"nbf": 1723114626
	}
}
```

On the root of the object, you will find the following fields:

- `ip`: The IP address that established the connection with SurrealDB.
- `exp`: The time at which the session will expire.
  - Will be `NONE` or `null` when the session does not expire.
- `ns`: The name of the currently selected namespace.
  - Will be `NONE` or `null` when no namespace is selected.
- `db`: The name of the currently selected database.
  - Will be `NONE` or `null` when no database is selected.
- `rd`: The record identifier of the currently authenticated record user.
  - Will be `NONE` or `null` when unauthenticated or authenticated as a system user.
- `ac`: The name of the access method that was used to authenticate.
  - Will be `NONE` or `null` when not authenticated with an access method.
- `or`: The value of the `Origin` header of the HTTP request.
  - This header is usually set by browsers to identify the site that originated the request.
- `id`: The value of the `surreal-id` header of the HTTP request.
  - This value can be set by clients to identify their individual sessions to the server.
- `tk`: An object containing the claims present in the authentication token used to establish the session.
  - Will be `NONE` or `null` when not authenticated.

The values stored in the session parameter can be accessed through the [`session::*` family of functions](/docs/reference/query-language/functions/database-functions/session.md).

#### Token

The `$token` parameter contains the claims contained in the token used to authenticate the current session. This parameter is set in every authenticated SurrealDB session.

In this example, you can see the result of the `SELECT * FROM $token` query in an authenticated session for a record user:

```js
{
	"AC": "user",
	"DB": "main",
	"ID": "user:example",
	"NS": "main",
	"exp": 1723118226,
	"iat": 1723114626,
	"iss": "SurrealDB",
	"jti": "3b3fe74a-955c-46d7-9400-363848912292",
	"nbf": 1723114626
}
```

Whenever authentication is done directly using a token, this object will contain the claims contained in that token.

When that token is issued by SurrealDB after successful authentication, the object will usually contain the following fields:

- `NS`: The name of the namespace the user is authenticated in.
  - Will be `NONE` or `null` when the user is authenticated at the root level.
- `DB`: The name of the database the user is authenticated in.
  - Will be `NONE` or `null` when the user is authenticated at the root or namespace level.
- `ID`: The record identifier of the currently authenticated record user.
  - Will not be present when not authenticated as a record user.
- `AC`: The name of the access method that is used to authenticate.
  - Will be `NONE` or `null` when authenticating without an access method.

The following fields correspond to claims defined in the standard JWT implementation described in [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519):

- `iat`: The time at which the token was issued.
- `nbf`: The time before which the token will not be accepted to establish new authenticated sessions.
- `exp`: The time after which the token will not be accepted to establish new authenticated sessions.
- `jti`: A unique identifier used to reference the token.
- `iss`: A string identifying the entity which issued the token.

#### Auth

The `$auth` parameter points to the record belonging to the current authenticated record user. This parameter is set only when the session is authenticated with an existing record user.

When the `$auth` parameter is set, you can access any of the fields of the record corresponding to the authenticated user (e.g. `$auth.name` or `$auth.email`) via that parameter.

`$auth` itself is the user's record ID, such as `user:o57nbpz66v6a2l0l9kog`. Reading a field through it, including `$auth.id`, fetches the user record, and so needs the user to be allowed to select that record. On a table with no permissions for record users, which includes a table created implicitly on the first write, `$auth.email` and `$auth.id` are `NONE`. Use `$auth` itself wherever the record ID is enough, and give the user table a `select` permission where the fields are needed:

```surql
DEFINE TABLE user SCHEMALESS
    PERMISSIONS FOR select WHERE id = $auth;
```

A common use is to store the user who created a record. A field with `VALUE $auth READONLY` sets it to the signed-in user on creation and refuses any later change:

```surql
DEFINE FIELD author ON post VALUE $auth READONLY;
```

A record user who runs `CREATE post SET title = 'Hi'` gets a post whose `author` is their own record ID, and a user cannot set `author` to anyone else.

### Expiration

Authenticated sessions remain valid for a certain duration. This duration is `NONE` by default, meaning that sessions will not expire unless otherwise specified. This duration can be customised on both the `DEFINE USER` and `DEFINE ACCESS` statements to any specific value defining the maximum duration of an authenticated session associated with that user or access method. After the defined duration, the authenticated session will expire. For example, the `DEFINE USER example DURATION FOR SESSION 1d` clause will ensure that any authenticated sessions for the `example` user will expire after a day.

Expired sessions can no longer be used to call authenticated methods and doing so will result in a specific error indicating that the session has expired. SurrealQL can be used to check the expiration of the active session by running `SELECT exp FROM $session`, which shows the Unix time when the session will expire or `NONE` in the case that the session does not expire.

Clients can reuse a connection with an expired session to refresh the session using valid credentials. This includes calling the [`signin`](/docs/reference/rest-api/rpc-protocol.md#signin) method to obtain a new token with credentials and reauthenticate the session using that token or calling the [`authenticate`](/docs/reference/rest-api/rpc-protocol.md#authenticate) method to reauthenticate the session with an existing token.

It is important to note that token duration and session duration are independent concepts. Token duration, which can be similarly customised, refers to the validity of the token (i.e. the value of its `exp` claim), during which it can be used to establish an authenticated session. Tokens issued by SurrealDB have a default duration of one hour. Token expiration is used to limit the time during which a token can be compromised resulting in unauthorized access; tokens are often stored in the client and could be stolen with attacks like cross-site scripting. Session expiration can be used to ensure that users are required to reauthenticate in order to prove that they still have access to valid credentials as well as to limit the impact of a compromised client with an established session; sessions are stored in memory in the server and cannot be similarly stolen. It is recommended that tokens are configured to last for as little as necessary before being exchanged for an authenticated session, whereas sessions are recommended to last for as little as necessary to allow for the typical client to complete a set of authenticated actions.

---

Source: https://surrealdb.com/docs/learn/security/authorization/capabilities

# Capabilities

SurrealDB is secure by default and is suitable for all database use cases. It offers powerful features like scripting, functions or network access from within your SurrealQL queries.

Capabilities control features such as scripting, functions and network access. Most capabilities are disabled by default and must be explicitly enabled by an administrator per use case.

> [!IMPORTANT]
> Capabilities are configured per instance from **Instance settings** in [SurrealDB Studio](https://studio.surrealdb.com). See [Configure an instance](/docs/manage/instances/configure.md) for the available settings.

When a query wants to use a capability that is not allowed, SurrealDB will reject it.

```surql title="Rejected Query"
http::get("https://www.surrealdb.com");

["Access to network target 'www.surrealdb.com:443' is not allowed"]
```

This rejection will also be logged in the SurrealDB server.

```bash title="Rejected Query Logging"
WARN surrealdb_core::ctx::context: Capabilities denied outgoing network connection attempt, target: 'www.surrealdb.com:443'
```

In production deployments, we recommend denying all capabilities by default and specifically allowing only those required.

```bash
surreal db start --deny-all --allow-funcs "array, string, crypto::argon2, http::get" --allow-net api.example.com:443
```

You can learn more about best practices when using capabilities in our [Security Best Practices](/docs/learn/security/best-practices/security-best-practices.md#capabilities) guide.

## Priority

By default, all capabilities are denied unless allowed. Some few capabilities (e.g. functions) are allowed by default.

Capabilities can be configured globally (e.g. `--allow-all`, `--deny-all`), generally (e.g. `--allow-net`, `--deny-funcs`) or specifically (e.g. `--deny-net 192.168.1.1`, `--allow-funcs string::len`). When capabilities are configured, the more specific capabilities prevail over the less specific. At the same level of specificity, denies always prevail over allows.

### Examples

Capabilities configured generally prevail over those defined globally:
- Running with `--deny-all --allow-scripting` will deny all capabilities except for scripting.
- Running with `--allow-all --deny-net` will allow all capabilities except for network.

Capabilities configured specifically prevail over those defined globally or generally:
- Running with `--deny-all --allow-net example.com` will deny all capabilities except network connections to `example.com`.
- Running with `--allow-all --deny-funcs http` will allow all capabilities except for calling functions of the `http` family.
- Running with `--deny-funcs --allow-funcs string::len` will deny all functions except for `string::len`.
- Running with `--allow-net --deny-net 10.0.0.0/8` will allow all network connections except to the `10.0.0.0/8` block.

Capabilities denied specifically prevail over those allowed specifically:
- Running with `--deny-funcs crypto --allow-funcs md5` will deny all functions of the `crypto` including `crypto::md5`.
- Running with `--allow-funcs crypto --deny-funcs md5` will allow all functions of the `crypto` family except for `crypto::md5`.

## Server versus client configuration

Capability flags on [`surreal db start`](/docs/reference/cli/db/commands/start.md) configure the **running database instance**. That is what enforces permissions when clients connect over HTTP, WebSocket, RPC, or [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) to a remote endpoint.

[`surreal db sql`](/docs/reference/cli/db/commands/sql.md) accepts the same flags, but they apply differently:

| How you connect | Configure capabilities on |
| --- | --- |
| Remote server (`ws://`, `http://`, …) | **`surreal db start`** (or server env vars) |
| Embedded storage (`memory`, `rocksdb://…`, …) | **`surreal db sql`** |

When using a remote server, flags on `surreal db sql` do **not** enable or disable execution-time checks such as [`eval::*`](/docs/reference/query-language/functions/database-functions/eval.md), arbitrary-query gates, or experimental features. They can still affect **REPL line parsing** before a query is sent.

See [Capabilities and remote connections](/docs/reference/cli/db/commands/sql.md#capabilities-and-remote-connections) for detail.

## List

List of options for allowing capabilities:

<table>
    <thead>
        <tr>
            <th scope="col">Option</th>
            <th colspan="2" scope="col">Description</th>
            <th scope="col">Default</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Option">
                -A, --allow-all
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Allow all capabilities except for those more specifically denied like experimental features
            </td>
            <td scope="row" data-label="Default">
                False
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                -A, --allow-arbitrary-query
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Denies arbitrary queries to be used by user groups. Possible user groups are: 'guest', 'record', and 'system'.
            </td>
            <td scope="row" data-label="Default">
                False
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --allow-experimental
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Allow the usage of one or more experimental features. Possible values are `files` and `surrealism`, separated by a comma. See <a href="/docs/reference/cli/db/commands/start.md#experimental-capabilities">experimental capabilities</a> for which tag enables each feature. (The legacy tag <code>gql</code> is still accepted for compatibility but is unused from 3.3.0 - GQL is on by default.)
            </td>
            <td scope="row" data-label="Default">
                None
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --allow-eval-query [&lt;target&gt;,...]
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Allow the <code>eval::surql</code> and <code>eval::gql</code> functions for certain user groups (<code>guest</code>, <code>record</code>, <code>system</code>). Denied for everyone by default, even under <code>--allow-all</code>. Cannot bypass <code>--deny-arbitrary-query</code> for the same subject. See <a href="#eval-queries">eval queries</a>.
            </td>
            <td scope="row" data-label="Default">
                None
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --allow-funcs [&lt;target&gt;,...]
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Allow execution of all functions except for functions that are specifically denied. Alternatively, you can provide a comma-separated list of function names to allow
            </td>
            <td scope="row" data-label="Default">
                None
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --allow-guests
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Allow non-authenticated users to execute queries when authentication is enabled
            </td>
            <td scope="row" data-label="Default">
                False
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --allow-net [&lt;target&gt;,...]
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Allow all outbound network access except for network targets that are specifically denied. Alternatively, you can provide a comma-separated list of network targets to allow
            </td>
            <td scope="row" data-label="Default">
                None
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --allow-scripting
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Allow execution of embedded scripting functions
            </td>
            <td scope="row" data-label="Default">
                False
            </td>
        </tr>
    </tbody>
</table>

List of options for denying capabilities:

<table>
    <thead>
        <tr>
            <th scope="col">Option</th>
            <th colspan="2" scope="col">Description</th>
            <th scope="col">Default</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Option">
                -D, --deny-all
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Deny all capabilities except for those more specifically allowed
            </td>
            <td scope="row" data-label="Default">
                False
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                -D, --deny-arbitrary-query
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Denies arbitrary queries from being used by user groups. Possible user groups are: 'guest', 'record', and 'system'
            </td>
            <td scope="row" data-label="Default">
                False
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --deny-eval-query [&lt;target&gt;,...]
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Deny the <code>eval::surql</code> and <code>eval::gql</code> functions for certain user groups (<code>guest</code>, <code>record</code>, <code>system</code>). Specifically denied groups prevail over allowed groups. See <a href="#eval-queries">eval queries</a>.
            </td>
            <td scope="row" data-label="Default">
                None
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --deny-funcs [&lt;target&gt;,...]
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Deny execution of all functions except for functions that are specifically allowed. Alternatively, you can provide a comma-separated list of function names to deny
            </td>
            <td scope="row" data-label="Default">
                None
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --deny-guests
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Prevent non-authenticated users from executing queries when authentication is enabled
            </td>
            <td scope="row" data-label="Default">
                False
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --deny-net [&lt;target&gt;,...]
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Deny all outbound network access except for network targets that are specifically allowed. Alternatively, you can provide a comma-separated list of network targets to deny
            </td>
            <td scope="row" data-label="Default">
                None
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Option">
                --deny-scripting
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Deny execution of embedded scripting functions
            </td>
            <td scope="row" data-label="Default">
                False
            </td>
        </tr>
    </tbody>
</table>

## Guest access

Guest access is used when you want to expose certain parts of a database to non-authenticated users. It's useful when you want to serve datasets publicly and still require authentication for the rest of the system.

Even when this capability is allowed, a guest user can only execute functions or data operations like SELECT, CREATE, etc, and only if the `PERMISSIONS` clause for the resource being used in the query allows it.

```surql
-- Prepare tables with custom PERMISSIONS
test/test> DEFINE TABLE protected PERMISSIONS NONE;
test/test> DEFINE TABLE public PERMISSIONS FULL;

-- When guest access is allowed
$ surreal db start --allow-guests

test/test> CREATE public;
[{ id: public:uy0qzy31v4xox8vivrd4 }]

test/test> SELECT * FROM public;
[{ id: public:uy0qzy31v4xox8vivrd4 }]

test/test> CREATE protected;
[]

test/test> SELECT * FROM protected;
[]

-- When guest access is denied
$ surreal db start --deny-guests

test/test> CREATE public;
There was a problem with the database: There was a problem with the database: IAM error: Not enough permissions to perform this action

test/test> SELECT * FROM public;
There was a problem with the database: There was a problem with the database: IAM error: Not enough permissions to perform this action

test/test> CREATE protected;
There was a problem with the database: There was a problem with the database: IAM error: Not enough permissions to perform this action

test/test> SELECT * FROM protected;
There was a problem with the database: There was a problem with the database: IAM error: Not enough permissions to perform this action
```

## Functions

SurrealDB offers [built-in functions](/docs/reference/query-language/functions/database-functions/array.md) to perform common operations like string manipulation, math, etc. Users can also define [their own functions](/docs/reference/query-language/statements/define/function.md) with custom logic.

In certain environments, you may not want users to use specific functions (i.e. `http::*`) or execute any custom function at all. You can use the allow/deny lists to configure what functions are allowed and what functions are denied.

```bash
// Allow all functions except the http family and crypto::md5()
surreal db start --allow-funcs --deny-funcs "http","crypto::md5"

// Allow certain custom functions only (all custom functions start with "fn::")
surreal db start --allow-funcs "fn::shared_fn"
```

A denied function is refused however a query calls it, including through method syntax. With `--deny-funcs string::len`, both `string::len('abcd')` and `'abcd'.len()` return the error `Function 'string::len' is not allowed to be executed`.

> [!WARNING]
> In SurrealDB 3.3.0, method syntax was able to call a denied function: `'abcd'.len()` returned `4` while `string::len('abcd')` was refused. SurrealDB 3.3.1 fixes this.

## Network

SurrealDB can make outbound network connections from [`http::*`](/docs/reference/query-language/functions/database-functions/http.md) functions and from JWKS fetches used by [JWT access methods](/docs/reference/query-language/statements/define/access/jwt.md#json-web-key-set-jwks). Use `--allow-net` and `--deny-net` to control which targets those connections may reach.

```bash
# Deny network access to localhost and private IPv4 ranges
surreal db start --allow-net --deny-net "127.0.0.1","localhost","10.0.0.0/8","192.168.0.0/16","172.16.0.0/12"

# Allow access to an internal system on port 443 only
surreal db start --allow-net internal.example.com:443

# Allow access to some private networks but not to others
surreal db start --allow-net 10.0.0.0/16 --deny-net 10.10.0.0/24
```

SurrealDB resolves hostnames with DNS, then checks every resulting IP address against `--allow-net` and `--deny-net`. A hostname that resolves to an address listed in `--deny-net` is blocked.

Note that allowlisting a hostname alone is not enough when that name resolves to a private or special-use address (loopback, link-local, RFC1918 ranges, cloud metadata endpoints, etc.). SurrealDB blocks those addresses unless you also list the IP or CIDR in `--allow-net`, or you pass `--allow-net` with no target filter. This reduces SSRF risk from DNS rebinding or HTTP redirects into the local network.

Example for an in-cluster JWKS issuer whose service name resolves inside `10.0.0.0/8`:

```bash
surreal db start --deny-all --allow-net idp.namespace.svc,10.0.0.0/8
```

> [!WARNING]
> SurrealDB does not perform reverse DNS lookups. A client can still reach an IP address directly even when a hostname that resolves to that IP is listed in `--deny-net`. This matters when network access is allowed by default (for example `--allow-net --deny-net www.example.com`) or when an IP is allowlisted while its hostname is denylisted (for example `--allow-net 203.0.113.10 --deny-net www.example.com`).

Deny by default: list only the `--allow-net` targets you need, and keep additional network controls in your infrastructure.

## Arbitrary queries

The `--allow-arbitrary-query` and `--deny-arbitrary-query` allow database administrators to allow or deny arbitrary querying by either guest, record or system users, or a combination of those. This capability setting affects the following: [/sql endpoint](/docs/reference/rest-api/http-protocol.md#sql), [/key endpoints](/docs/reference/rest-api/http-protocol.md#get-table), [/graphql endpoint](/docs/reference/rest-api/http-protocol.md#graphql), [/gql endpoint](/docs/reference/rest-api/http-protocol.md#gql), the [Postgres wire protocol](/docs/reference/rest-api/postgres-protocol.md) listener, [RPC methods](/docs/reference/rest-api/rpc-protocol.md) `use`, `select`, `create`, `update`, `merge`, `patch`, `delete`, `relate`, `insert`, `insert_relation`, `query`, `gql`, and `graphql`, and the [`eval::*`](/docs/reference/query-language/functions/database-functions/eval.md) functions.

Endpoints that do not accept arbitrary queries such as [`/version`](/docs/reference/rest-api/http-protocol.md#version) and [authentication endpoints](/docs/reference/rest-api/http-protocol.md#signin) are not affected.

The `--deny-arbitrary-query` flag is often preceded with a [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) statement to set up certain endpoints that users can use to access database resources in place of arbitrary queries.

## Eval queries

_(since v3.2.0)_

The [`eval::surql`](/docs/reference/query-language/functions/database-functions/eval.md) and [`eval::gql`](/docs/reference/query-language/functions/database-functions/eval.md) functions evaluate query strings at runtime. They are controlled by **`--allow-eval-query`** and **`--deny-eval-query`**, with subject groups `guest`, `record`, and `system`.

Unlike most capabilities, **eval is denied for every subject by default**, including when you pass `--allow-all`. You must opt in explicitly:

```bash
surreal db start --allow-eval-query
```

Arbitrary queries are **allowed by default** on the server, so you do not need `--allow-arbitrary-query` for eval unless you have restricted arbitrary queries (for example with `--deny-arbitrary-query` or `--deny-all`). If a subject is denied arbitrary queries - a common pattern alongside [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) - **`eval` is denied for that subject too**, even when `--allow-eval-query` includes them. `eval` cannot bypass arbitrary-query lockdown.

Deny rules at the same specificity prevail over allow rules. A record user calling an owner-defined function that invokes `eval` is still checked as `record` - auth limiting never escalates the subject.

Configure `--allow-eval-query` on **`surreal db start`** when clients connect to a remote instance. It is not required on `surreal db sql` for remote connections - only for [embedded REPL sessions](/docs/reference/cli/db/commands/sql.md#capabilities-and-remote-connections).

`eval::gql` does not need an experimental capability from **3.3.0** (on **3.2.x**, also allow `gql`). See [Eval functions](/docs/reference/query-language/functions/database-functions/eval.md) and [Representations and codecs](/docs/learn/querying/concepts-and-guides/representations-and-codecs.md).

## HTTP routes and RPC methods

Two capabilities switch individual entry points off rather than whole classes of behaviour. `--allow-http` and `--deny-http` take HTTP route names, and `--allow-rpc` and `--deny-rpc` take RPC method names. Each takes a comma-separated list, or no value at all to mean every route or every method.

```bash
# Close the SQL and import endpoints, leave the rest open
surreal db start --deny-http sql,import

# Open only the RPC methods a read-only client needs
surreal db start --deny-rpc --allow-rpc use,select,query
```

These follow the same [priority rules](#priority) as every other capability, so a specific deny beats a specific allow, and both beat the blanket form.

### HTTP route names

A route name covers every method and path under it, so `key` covers all ten [`/key`](/docs/reference/rest-api/http-protocol.md#get-table) endpoints and `ml` covers both [`/ml`](/docs/reference/rest-api/http-protocol.md#ml-import) endpoints.

| Name | Covers |
| --- | --- |
| `health` | [`GET /health`](/docs/reference/rest-api/http-protocol.md#health) and the gRPC [`Health`](/docs/reference/rest-api/rpc-protocol.md#grpc-transport) call, but not the `/status` and `/ready` probes |
| `version` | [`GET /version`](/docs/reference/rest-api/http-protocol.md#version) |
| `import` | [`POST /import`](/docs/reference/rest-api/http-protocol.md#import) |
| `export` | [`POST /export`](/docs/reference/rest-api/http-protocol.md#export) |
| `signin` | [`POST /signin`](/docs/reference/rest-api/http-protocol.md#signin) |
| `signup` | [`POST /signup`](/docs/reference/rest-api/http-protocol.md#signup) |
| `key` | The [`/key`](/docs/reference/rest-api/http-protocol.md#get-table) table and record endpoints |
| `sql` | [`POST /sql`](/docs/reference/rest-api/http-protocol.md#sql) |
| `gql` | [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql), and the Postgres `gql` dialect |
| `graphql` | [`POST /graphql`](/docs/reference/rest-api/http-protocol.md#graphql) |
| `ml` | The [`/ml`](/docs/reference/rest-api/http-protocol.md#ml-import) import and export endpoints |
| `api` | Endpoints defined with [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) |
| `mcp` | The [MCP server](/docs/reference/cli/db/commands/mcp.md) endpoint |
| `rpc` | The HTTP [`/rpc`](/docs/reference/rest-api/rpc-protocol.md) endpoint |
| `sync` | The internal sync endpoint |
| `postgres` _(since v3.3.0)_ | The [Postgres wire protocol](/docs/reference/rest-api/postgres-protocol.md) listener |

`rpc` closes the HTTP `/rpc` endpoint as a whole. To leave it open and restrict what can be called through it, use the RPC method names below, which apply to the WebSocket and gRPC transports as well.

### RPC method names

| Group | Names |
| --- | --- |
| Session | `use`, `info`, `ping`, `version`, `reset` |
| Authentication | `signup`, `signin`, `authenticate`, `invalidate`, `refresh`, `revoke` |
| Querying | `query`, `run`, `gql`, `graphql` |
| Reading and writing | `select`, `create`, `insert`, `insert_relation`, `relate`, `update`, `upsert`, `merge`, `patch`, `delete` |
| Parameters | `set`, `unset` |
| Live queries | `live`, `kill` |
| Transactions | `begin`, `commit`, `cancel` |
| Streaming | `query_stream`, `query_cancel` |
| Attached sessions | `attach`, `detach`, `sessions` |

Names are matched case-insensitively, so `--deny-rpc GraphQL` and `--deny-rpc graphql` are the same. `gql` and `graphql` are separate methods.

## GQL entry surfaces

ISO GQL (Cypher Query Language) is on by default from **3.3.0**, and the GQL executor is reachable from four entry surfaces. Each has its own switch, so a deployment that serves no GQL closes each surface it exposes:

| Entry surface | Switch |
| --- | --- |
| The `/gql` endpoint | `--deny-http gql` |
| The `gql` [RPC method](/docs/reference/rest-api/rpc-protocol.md), over WebSocket, HTTP `/rpc` and gRPC | `--deny-rpc gql` |
| [`eval::gql()`](/docs/reference/query-language/functions/database-functions/eval.md) inside any SurrealQL | `--deny-eval-query`, which is also the default state. `--deny-funcs` reaches it as well. |
| The [Postgres wire protocol](/docs/reference/rest-api/postgres-protocol.md) `dialect=gql` _(since v3.3.0)_ | `--deny-http gql` |

`--deny-http gql` covers the `/gql` endpoint and the Postgres dialect together, because both are checked against the same `gql` route target. The `gql` RPC method answers to `--deny-rpc gql` instead, so the HTTP route and the RPC method are closed separately. The Postgres listener has a route name of its own, so `--deny-http postgres` closes the port whatever dialect a client asks for, and `--deny-http gql` leaves the port open while refusing the GQL dialect on it.

### Selecting the Postgres dialect

_(since v3.3.0)_

A Postgres client selects the dialect in two places, and both are checked from 3.3.0. A connection that asks for GQL at connect time with `options=-c dialect=gql` is refused and closed. A session that runs `SET dialect = 'gql'` receives `42501 insufficient_privilege` and stays usable for SurrealQL.

The check runs where the dialect is selected rather than where a query runs, so a client learns the reason at that point instead of receiving parse errors from a connection left in SurrealQL.

[`--deny-arbitrary-query`](#arbitrary-queries) reaches all four surfaces as well, since each of them accepts a query string.

---

Source: https://surrealdb.com/docs/learn/security/authorization/permissions-and-row-level-security

# Permissions & row-level security

How SurrealDB's PERMISSIONS clause on tables and fields controls create, select, update, and delete, including per-record and field-level rules using $auth.

SurrealDB lets you declare permissions alongside your schema so access is enforced in the database, not only in application code. You attach a `PERMISSIONS` clause to [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) and [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) to describe which operations are allowed under which conditions.

These clauses apply to [record users](/docs/learn/security/authentication/users.md#record-users) and to [guests](/docs/learn/security/authorization/capabilities.md#guest-access) when guest access is enabled. [System users](/docs/learn/security/authentication/users.md#system-users) are not restricted by table or field `PERMISSIONS`, using roles instead.

## Defaults: `NONE` and `FULL`

On a table, omitting `PERMISSIONS` results in a `PERMISSIONS NONE` in the actual statement passed to the database: record users cannot `SELECT`, `CREATE`, `UPDATE`, or `DELETE` records in that table until you grant access. `PERMISSIONS FULL` allows all four operations.

Field permissions default the other way. A field without an explicit clause is stored as `PERMISSIONS FULL`. The table is the main access gate, while field permissions only narrow further when you need to (for example hiding a password). With `FULL`, a field follows the table's rules without adding its own.

## Record and field rules

For each table, you set independent rules for **create**, **select**, **update**, and **delete**. Each clause is a SurrealQL expression that must succeed for that operation to proceed; if it fails, the operation is rejected for the affected records. **Field-level permissions** refine this: you can constrain **select**, **create**, and **update** on individual fields - useful for sensitive data that should not be readable or writable under the same rules as the rest of the record.

What the wider industry calls **row-level security** is implemented here by writing expressions that depend on the authenticated context. The **`$auth`** variable holds the current identity and claims after sign-in, so you can compare it to fields on the record (for example `owner = $auth.id`) and ensure each user only sees or changes their own data.

### Example: users read only their own records

```surql
DEFINE TABLE order SCHEMALESS
	PERMISSIONS
		FOR select
			WHERE customer = $auth.id
		FOR create
			WHERE customer = $auth.id
		FOR update, delete
			WHERE customer = $auth.id
;
```

### Example: a field only administrators may update

```surql
DEFINE FIELD internal_note ON order TYPE string
	PERMISSIONS
		FOR select FULL
		FOR update WHERE $auth.role = 'admin'
;
```

Together, table- and field-level `PERMISSIONS` give you flexible authorisation without duplicating policy in every client. For full syntax and options, see [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md#defining-permissions) and [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md#setting-permissions-on-fields).

---

Source: https://surrealdb.com/docs/learn/security/authorization/tokens-and-jwts

# Tokens & JWTs

How SurrealDB authenticates clients with JWTs, DEFINE ACCESS TYPE JWT, symmetric and asymmetric verification, claims as session variables, and RECORD access for database-managed sign-in.

SurrealDB can authenticate clients using **JSON Web Tokens (JWTs)**. Your application or identity provider issues a signed JWT; SurrealDB verifies the signature and builds a **session** from the token’s claims so subsequent queries run with the correct identity and scope. The same mechanism applies whether the client connects over HTTP, WebSocket, or other supported interfaces: the token establishes **who** is calling before **authorisation** runs.

You register JWT-based access with **`DEFINE ACCESS … TYPE JWT`** (see [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md)). SurrealDB validates the token’s signature and standard claims (such as issuer and audience) according to your definition. Verification supports **symmetric** algorithms such as **HS256** with a shared secret, and **asymmetric** algorithms such as **RS256** and **ES256** using a configured public key or JWKS, which is how major identity providers sign their tokens. Choose the model that matches your key management: shared secrets for internal services, public-key verification when keys are rotated by an external IdP.

A typical JWT access definition names:

- The signing method (for example HS256 with a secret, or RS256/ES256 with a public key or JWKS URL).
- Expected **issuer** and **audience** values so only tokens minted for your deployment are accepted.
- How claim names map into session data (so **`$auth`** exposes fields your [`PERMISSIONS`](/docs/learn/security/authorization/permissions-and-row-level-security.md) rules can use).

**Claims** from the JWT are mapped into SurrealDB **session variables** (including **`$auth`**) so [permissions](/docs/learn/security/authorization/permissions-and-row-level-security.md) and queries see a consistent view of the user whether authentication came from SurrealDB or from elsewhere. Typical mappings include subject, roles, and custom namespaces your application relies on for **authorisation**. The exact claim names depend on your issuer; configure the mapping in `DEFINE ACCESS` so your policies read stable fields regardless of whether the upstream token uses flat or nested JSON.

That design works well with **third-party identity providers** - Auth0, AWS Cognito, Okta, Azure AD, and similar systems that issue OIDC/OAuth2 JWTs. You configure issuer, audience, signing keys, and claim bindings once; users authenticate with the provider, receive a JWT, and present it to SurrealDB without storing end-user passwords in your database.

### Firebase and Supabase

Firebase Authentication and Supabase Auth also issue JWTs that SurrealDB can verify, in the same way as the [Auth0](/docs/explore/tutorials/tutorials/auth0-integration.md) and [AWS Cognito](/docs/explore/tutorials/tutorials/aws-cognito-integration.md) guides show. Two things have to be in place:

- **SurrealDB claims in the token.** SurrealDB uses the `ns`, `db` and `ac` claims to find the access method that verifies the token, and refuses a token without them. Add them as custom claims: in Firebase with the Admin SDK's `setCustomUserClaims()`, and in Supabase with a Custom Access Token Hook. The namespaced form (`https://surrealdb.com/ns` and so on) is also accepted.
- **The provider's signing keys.** Point the access method at the provider's JWKS URL, and check the issuer and audience in `AUTHENTICATE`.

```surql
-- Firebase: tokens are signed with Google's rotating keys
DEFINE ACCESS firebase ON DATABASE TYPE RECORD
    WITH JWT URL "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com"
    AUTHENTICATE {
        IF $token.iss != "https://securetoken.google.com/<YOUR_PROJECT_ID>"
            OR $token.aud != "<YOUR_PROJECT_ID>" {
            THROW "Invalid token";
        };
        RETURN type::record("user", $token.sub);
    };

-- Supabase: projects with asymmetric signing keys publish a JWKS
DEFINE ACCESS supabase ON DATABASE TYPE RECORD
    WITH JWT URL "https://<YOUR_PROJECT_REF>.supabase.co/auth/v1/.well-known/jwks.json"
    AUTHENTICATE {
        IF $token.iss != "https://<YOUR_PROJECT_REF>.supabase.co/auth/v1" {
            THROW "Invalid token";
        };
        RETURN type::record("user", $token.sub);
    };
```

A Supabase project that still signs tokens with the legacy shared JWT secret uses `WITH JWT ALGORITHM HS256 KEY "<YOUR_JWT_SECRET>"` instead of the URL.

Keep token lifetimes and key rotation in mind: short-lived access tokens and explicit issuer/audience checks reduce the impact of a leaked JWT, and asymmetric verification lets SurrealDB trust keys published via JWKS when your provider rotates them.

When you need **database-managed authentication** (for example sign-up and credential checks implemented as part of your data model), use **`DEFINE ACCESS … TYPE RECORD`**. That path issues and validates access in coordination with records in your database while still producing a session compatible with the same permission model as JWT access. Many deployments combine both: system operators use **root** or scoped database users, end users authenticate via **RECORD** or brokered **JWT** access, and **`PERMISSIONS`** enforce row- and field-level rules for everyone.

In practice, teams often start with **HS256** and a single secret for development or private networks, then move to **RS256** or **ES256** with JWKS in production so verification does not depend on long-lived shared secrets crossing service boundaries. The [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md) reference lists the exact clauses for each approach.

For how authentication fits the wider security model, read the [authentication overview](/docs/learn/security/authentication/overview.md). For every option on JWT and record access, use the reference for [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md).

---

Source: https://surrealdb.com/docs/learn/security/best-practices/security-best-practices

# Security best practices

This guide outlines some key security best practices for using SurrealDB 3.0. While SurrealDB offers powerful and flexible features to support you in meeting your desired security standards, the use that you make of those features will ultimately determine whether or not you meet them.

The following is a non-exhaustive list of security best practices you should consider when building services and applications with SurrealDB to help you address common security challenges while preventing frequent pitfalls.

## Query safety

When using SurrealDB as a traditional backend database, your application will usually build SurrealQL queries that may need to contain some untrusted input, such as that provided by the users of your application. To do so, SurrealDB offers [`bind`](/docs/reference/rust/methods/query.md) as a method to `query` (implemented in other SDKs as [the `vars` argument to `query`](/docs/reference/javascript/concepts/executing-queries.md)), which should always be used when including untrusted input into queries. Otherwise, SurrealDB will be unable to separate the actual query syntax from the user input, resulting in the well-known [SQL injection](https://en.wikipedia.org/w/index.php?title=SQL_injection&oldid=1234729055) vulnerabilities. This practice is known as [prepared statements or parameterised queries](https://en.wikipedia.org/w/index.php?title=Prepared_statement&oldid=1195122133).

Binding parameters ensures that untrusted data is passed to SurrealDB as SurrealQL parameters, which are independent of the query syntax, preventing SQL injection attacks.

Bound parameters are SurrealDB's equivalent of prepared statements. A value such as `He said ", name = 'Gandalf';"` is stored exactly as written when it is passed as `$name`, because it is never read as part of the query. Building the same query by joining strings would let those characters end the string and add a statement of their own.

### Example: Bind parameters in the provided sdks

**Rust**

```rust
// Do this:
let name = "tobie"; // User-controlled input.
let mut result = db
    .query("CREATE person CONTENT name = $name;")
    .bind(("name", name))
    .await?;
```

```rust
// highlight-next-line
// Do NOT do this:
let name = "tobie"; // User-controlled input.
let mut result = db
    .query(format!("CREATE person CONTENT name = {name};"))
    .await?;
```

**JavaScript**

```jsx
// Do this:
const name = "tobie"; // User-controlled input.
const result = await db.query(
	'CREATE person CONTENT name = $name;',
	{ name }
);
```

```jsx
// highlight-next-line
// Do NOT do this:
const name = "tobie"; // User-controlled input.
const result = await db.query(`CREATE person CONTENT name = \
  "${name}";`);
```

**.NET (C#)**

```csharp
// Do this:
string name = "tobie"; // User-controlled input.
var result = await db.Query($"CREATE person CONTENT name = {name};");

// Translated as "CREATE person CONTENT name = $p0;"
// with the parameter $p0 having the value "tobie"
```

```csharp
// highlight-next-line
// Do NOT do this:
string name = "tobie"; // User-controlled input.
var result = await db.RawQuery($"CREATE person CONTENT name = "{name}";");
```

### Example: Bind parameters in the HTTP REST API

```bash title="Request"
curl -X POST -u "root:secret" -H "surreal-ns: main" -H \
  "surreal-db: main" -H "Accept: application/json" \
-d 'SELECT * FROM person WHERE age > $age' \
  http://localhost:8000/sql?age=18
```

## Passwords

If you require storing passwords for your users, do not rely on table or field permissions to keep them private. In the event that your application or database is compromised, these passwords would become known by the attacker. Instead, use the [password hashing functions](/docs/reference/query-language/functions/database-functions/crypto.md) provided by SurrealDB such as `crypto::argon2::*`, `crypto::bcrypt::*`, `crypto::pbkdf2::*` and `crypto::scrypt::*`. These functions ensure that irreversible cryptographic hashes are stored instead of the original passwords, so that the passwords from your users remain safe even in the event of a compromise.

Do not use other cryptographic hash functions (e.g. `crypto::md5`, `crypto::sha1`, `crypto::sha512`) for hashing passwords, even if you do use an additional salt. These functions are designed to be efficient in computing, which will benefit an attacker that sets out to crack any hashes that they may have obtained from the compromise of your application. Hash functions intended for password hashing already incorporate a salt as well as other mechanisms to prevent hash cracking by making the computation of such hashes less efficient. This mitigates password cracking at scale at the small cost of adding a few milliseconds delay while checking credentials for legitimate users.

Even if you only store password hashes, it is a good practice to additionally use field permissions to prevent unauthorised access to the password hashes, which could allow an attacker to perform inefficient but potentially effective attacks such as testing candidate passwords against a specific hash. For even better security, you may store passwords in a separate table and use table permissions to disallow all access to that table. Due to their internal implementation, table permissions provide additional security compared to field permissions.

### Example: Securely hash user passwords

```surql
DEFINE TABLE user SCHEMAFULL
  -- Only allow users to query their own record, including their password.
	PERMISSIONS
		FOR select, update, delete WHERE id = $auth.id;

DEFINE FIELD name ON user TYPE string;
DEFINE FIELD email
  ON user TYPE string ASSERT string::is_email($value);
DEFINE FIELD password ON user TYPE string;

DEFINE INDEX email ON user FIELDS email UNIQUE;

DEFINE ACCESS user ON DATABASE TYPE RECORD
	SIGNUP (
		CREATE user CONTENT {
			name: $name,
			email: $email,
			password: crypto::argon2::generate($password) -- Use Argon2 to generate the hash.
		}
	)
	SIGNIN (
		SELECT * FROM user WHERE email = $email AND
		  crypto::argon2::compare(password,
		    $password) -- Use Argon2 to compare the hashes.
	);
```

## Least privilege

When defining [system users](/docs/learn/security/authentication/users.md#system-users) in SurrealDB, you may assign them [roles](/docs/reference/query-language/statements/define/user.md#roles) that will limit the actions they can perform inside the level where they are defined. Ensure that you employ the principle of least privilege and create users at the lowest level possible and with the minimum role in order to be able to perform their duties inside of SurrealDB. This will mitigate some of the risk in the case where credentials for that user are ever compromised.

### Example: Define users with specific roles

A user who only needs to query the database:

```surql
DEFINE USER db_viewer ON DATABASE PASSWORD 'CHANGE_THIS' ROLES VIEWER;
```

A user who only needs to manage content in any databases on the same namespace:

```surql
DEFINE USER ns_editor
  ON NAMESPACE PASSWORD 'CHANGE_THIS' ROLES EDITOR;
```

## Capabilities

When running a SurrealDB server, you can configure the [capabilities](/docs/learn/security/authorization/capabilities.md) for your SurrealQL queries. Most of these capabilities are disabled by default to expose as little attack surface as possible to malicious actors.

For the strongest security, we recommend denying all capabilities by default and only allowing the specific capabilities necessary for your service, following an allowlisting approach. We strongly discourage running SurrealDB with all capabilities allowed.

### Example: Deny all capabilities with some exceptions

```bash
# Allow SurrealDB to call any functions from the array and string families, generate and compare Argon2 hashes
# and make HTTP GET requests over HTTPS to the address of a specific API.
surreal db start --deny-all --allow-funcs \
  "array, string, crypto::argon2, http::get" --allow-net \
  api.example.com:443
```

When you need to enable a capability, we recommend doing it specifically instead of generally. For example, suppose you know that your queries need to be able to parse emails using functions. In that case, we recommend you run SurrealDB with the `--allow-funcs "parse::email::*"` flag instead of allowing all functions with `--allow-funcs` without arguments. Doing this can help mitigate the performance impact that users can have when using certain functions and ensures that your SurrealDB instance will not be affected by vulnerabilities in the code of any other functions that a malicious actor could use to attack SurrealDB.

In the case where it is absolutely necessary to generally allow a capability, we recommend carefully reviewing the scope of that capability and denying any specific instances where it may introduce unacceptable risks. This is especially important in the case of the network capability, which allows SurrealDB to perform network requests such as those required by the [`http::*`](/docs/reference/query-language/functions/database-functions/http.md) functions and by [JWKS](/docs/reference/query-language/statements/define/access/jwt.md#json-web-key-set-jwks) downloads. Allowing untrusted users to perform network requests from your SurrealDB instance can allow them access to its local network or services that specifically allow network access from the SurrealDB server.

When you allow a hostname, also allow any private or special-use address that hostname resolves to, or use `--allow-net` without a filter. Hostname-only allowlists are not enough for in-cluster or RFC1918 targets. See [Network capabilities](/docs/learn/security/authorization/capabilities.md#network).

### Anti-pattern example: Allowing all outgoing network connections with some exceptions

```bash
// highlight-next-line
# Avoid doing this:
# Allow SurrealDB to make outgoing HTTP GET and POST request to any address except to some known private CIDR blocks.
surreal db start --deny-all --allow-funcs "http::get, http::post" \
  --allow-net --deny-net "10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16"
```

Following a denylisting approach as described above should only be used as a last resort, since it is common to miss some risky cases (e.g. [169.254.169.254](https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf)), which in that case would become allowed by default.

Additionally, SurrealDB currently does not perform reverse DNS lookups to prevent http functions directly accessing an IP address, even when the hostname that resolves to that IP address is listed within `--deny-net`. This is an issue when SurrealDB is configured to allow network access by default, e.g. `--allow-net --deny-net www.google.com`.

It is **strongly recommended** that you deny by default by defining specific `--allow-net` targets and using additional layers of network security within your infrastructure.

## Network exposure

When deploying SurrealDB, we recommend limiting the attack surface as much as possible in order to minimise the risk of attacks or information gathering from unauthorised parties. If your database should only be available to other internal services, we suggest that you expose SurrealDB exclusively to the internal network instead of deploying the service with a publicly addressable network interface that is accessible from the internet, regardless of whether or not allowlisting has been applied at the networking or application level.

If you must publish SurrealDB to the internet (e.g. if your users directly connect to SurrealDB), you may want to monitor and prevent unwanted connections using tools such as a network [intrusion prevention system](https://en.wikipedia.org/w/index.php?title=Intrusion_detection_system&oldid=1223972754#Intrusion_prevention) or a [web application firewall](https://en.wikipedia.org/w/index.php?title=Web_application_firewall&oldid=1234730173). If you do so, ensure that these systems are appropriately tuned and do not interfere with the regular use of SurrealDB.

In cases where SurrealDB is publicly exposed in environments where any sort of information leakage is unacceptable, the `--no-identification-headers` flag can be enabled, which will result in the SurrealDB server no longer responding to HTTP requests with headers that identify the product or its current version to prevent passive fingerprinting and metadata indexing. Note that this will not prevent active fingerprinting such as directly querying the `/version` endpoint if available or directly attempting to exploit a known security vulnerability without regard for compatibility. On the other hand, consider whether or not enabling this feature is compatible with your clients, which may rely on these headers in order to identify the version of SurrealDB running on the server.

### Example: Start SurrealDB with identification headers

```bash
$ surreal db start &
$ curl -vvv "127.0.0.1:8000"
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 307 Temporary Redirect
< location: https://surrealdb.com/app
< access-control-allow-origin: *
< vary: origin
< vary: access-control-request-method
< vary: access-control-request-headers
# highlight-start
< surreal-version: surrealdb-2.0.0+20240612.2184e80f
< server: SurrealDB
# highlight-end
< x-request-id: 157413ce-7cc4-41a1-a93b-0940bf87874c
< content-length: 0
< date: Mon, 17 Jun 2024 15:47:29 GMT
<
* Connection #0 to host 127.0.0.1 left intact
```

### Example: Start SurrealDB without identification headers

```bash
$ surreal db start --no-identification-headers &
$ curl -vvv "127.0.0.1:8000"
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 307 Temporary Redirect
< location: https://surrealdb.com/app
< access-control-allow-origin: *
< vary: origin
< vary: access-control-request-method
< vary: access-control-request-headers
< x-request-id: deec3301-e930-4389-a0da-b2a336bd2631
< content-length: 0
< date: Mon, 17 Jun 2024 15:49:43 GMT
<
* Connection #0 to host 127.0.0.1 left intact
```

## Encryption in transit

Encryption in transit is recommended, especially when deploying SurrealDB on a server in a different network than its clients. This mitigates the impact of man-in-the-middle attacks and provides confidentiality and integrity guarantees with regard to the data being exchanged. Encryption in transit can be achieved by using the SurrealDB server to serve its interfaces through HTTPS by providing the `--web-crt` and `--web-key` arguments when calling [the `start` subcommand in the CLI](/docs/reference/cli/db/commands/start.md#command-help). For production deployments, we recommend that TLS termination be performed by a load balancer or reverse proxy, which will often provide additional guarantees to the process.

### Example: Start SurrealDB with TLS

```bash
# If you want to serve TLS directly with SurrealDB:
surreal db start --web-crt "cert.pem" --web-key "key.pem"
```

### Example: A certificate for development

A certificate from a public certificate authority is trusted by every client without further setup. For development or an internal network, you can create your own certificate authority with `openssl` and sign a certificate for the server with it. The `subjectAltName` lists the names that clients use to reach the server:

```bash
# A certificate authority
openssl req -x509 -newkey rsa:4096 -nodes -days 365 \
  -keyout ca.key -out ca.crt -subj "/CN=Local SurrealDB CA"

# A certificate for the server, signed by that authority
openssl req -newkey rsa:4096 -nodes \
  -keyout server.key -out server.csr -subj "/CN=localhost"
printf 'subjectAltName=DNS:localhost,IP:127.0.0.1\n' > san.ext
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 365 -out server.crt -extfile san.ext

surreal db start --user root --pass secret --web-crt server.crt --web-key server.key
```

Each client then has to trust `ca.crt`, and keep `ca.key` private. A client that does not trust it refuses the connection, as `surreal db sql` does with `invalid peer certificate: UnknownIssuer`:

| Client | How to trust `ca.crt` |
| --- | --- |
| `curl` | `curl --cacert ca.crt https://localhost:8000/health` |
| JavaScript SDK on Node.js | Set `NODE_EXTRA_CA_CERTS=ca.crt` in the environment of the process |
| Rust SDK | Pass a `rustls::ClientConfig` that trusts `ca.crt` with `Config::new().rustls(...)`, as shown below |
| `surreal db sql` and other `surreal` commands | Not possible. They trust only the public certificate authorities built into the binary, so use a certificate from a public authority, or connect over plain HTTP inside a trusted network |

```rust
use rustls::RootCertStore;
use rustls_pki_types::CertificateDer;
use rustls_pki_types::pem::PemObject;
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Wss;
use surrealdb::opt::Config;

// rustls needs one cryptography provider chosen for the process
rustls::crypto::aws_lc_rs::default_provider().install_default().ok();

// Trust the certificate authority that signed the server's certificate
let mut roots = RootCertStore::empty();
for cert in CertificateDer::pem_file_iter("ca.crt")? {
    roots.add(cert?)?;
}
let tls = rustls::ClientConfig::builder()
    .with_root_certificates(roots)
    .with_no_client_auth();

let db = Surreal::new::<Wss>(("localhost:8000", Config::new().rustls(tls))).await?;
```

## Expiration

When defining [users](/docs/reference/query-language/statements/define/user.md) and [access methods](/docs/reference/query-language/statements/define/access.md), ensure that you set a specific [session and token duration](/docs/learn/security/authentication/users.md#expiration) whenever possible using the `DURATION` clause.

Default values provided by SurrealDB are intended to support cases where SurrealDB is used as a traditional backend database, which is why sessions do not expire by default. Suppose you build an application where your end users directly connect with SurrealDB. In that case, we strongly encourage setting a session expiration that is as short as possible (typically a few hours) to provide a good experience to your users without compromising security.

Expiring user sessions ensures that a user cannot remain authenticated long after their access has been revoked. This cannot be done on demand, as user sessions do not persist in the database. However, unlike tokens, user sessions are not typically susceptible to being stolen, as they exist only in the context of an established WebSocket connection.

### Example: Set a session duration

```surql
DEFINE USER username
  ON DATABASE PASSWORD 'CHANGE_THIS' DURATION FOR SESSION 5d;
```

```surql
DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET email = $email,
	  pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE email = $email
	  AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR SESSION 12h
;
```

Tokens, however, are usually stored in the client (e.g. a web browser) and may be stolen by client-side attacks such as a cross-site scripting vulnerability in your application. For this reason, we strongly recommend reducing the token duration from the default one hour to the minimum amount of time that your use case can tolerate. Ideally, a token should only be valid for as long as the client needs to use it to establish a session, which can be as little as a few seconds.

### Example: Set a token duration

```surql
DEFINE USER username
  ON DATABASE PASSWORD 'CHANGE_THIS' DURATION FOR TOKEN 15m;
```

```surql
DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET email = $email,
	  pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE email = $email
	  AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 5s
;
```

## JSON Web Tokens

When configuring how [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519) are verified before authenticating a [system](/docs/learn/security/authentication/users.md#system-users) or [record](/docs/learn/security/authentication/users.md#record-users) user with [`DEFINE ACCESS ... TYPE JWT`](/docs/reference/query-language/statements/define/access/jwt.md) or [`DEFINE ACCESS ... TYPE RECORD ... WITH JWT`](/docs/reference/query-language/statements/define/access/record.md#with-json-web-token), we recommend using an asymmetric algorithm (i.e. `PSXXX`,  `RSXXX`, `ECXXX`) when only a mechanism for token verification is being defined. This ensures that the only key stored by SurrealDB is a public key that does not represent a threat in the event of a compromise.

On the other hand, symmetric algorithms (i.e., HSXXX) use the same key for signature and verification, which the attacker could use to issue tokens that SurrealDB would trust.

### Example: Define a JWT access method

```surql
DEFINE ACCESS token ON DATABASE TYPE RECORD WITH JWT
ALGORITHM RS256 KEY "-----BEGIN PUBLIC KEY-----
MUO52Me9HEB4ZyU+7xmDpnixzA/CUE7kyUuE0b7t38oCh+sQouREqIjLwgHhFdhh3cQAwr6GH07D
ThioYrZL8xATJ3Youyj8C45QnZcGUif5PkpWXDi0HJSoMFekbW6Pr4xuqIqb2LGxGDVJcLZwJ2AS
Gtu2UAfPXbBD3ffiad393M22g1iHM80YaNi+xgswG7qtXE4lR/Lt4s0MeKKX7stdWI1VIsoB+y3i
r/OWUvJPjjDNbAsyy8tQmxydv+FUnLEP9TNT4AhN4DXcJ+XsDtW7OWt4EdSVDeKpGbIMvIrh1Pe+
Nilj8UHNyNDHa2AjK3seMo6CMvaIQJKj5o4xGFblFGwvvPD03SbuQLs1FdRjsZCeWLdYeQ3JDHE9
sFG7DCXlpMJcaYT1mf4XHJ0gPekNLQyewTY3Vxf7FgV3GCNjV20kcDFgJA2+iVW2wSrb+txD1ycE
kbi8jh0pedWwE40VQWaTh/8eAvX7IHWya/AEro25mq+m6vktNZLbvLphhp586kJK3Tdt3YjpkPre
M3nkFWOWurIyKbtIV9JemfwCgt89sNV45dTlnEDEZFFGnIgDnWgx3CUo4XmhICEQU8+tklw9jJYx
iCTjhbIDEBHySSSc/pQ4ftHQmhToTlQeOdEy4LYiaEIgl1X+hzRH1hBYvWlNKe4EY1nMCKcjgt0=
-----END PUBLIC KEY-----";
```

Additionally, we recommend using [JSON Web Key Sets](https://datatracker.ietf.org/doc/html/rfc7517) to configure the verification algorithm and key from a remote authoritative source using the `URL` clause instead of providing them directly to SurrealDB using the `ALGORITHM` and `KEY` clauses. This ensures that the original token issuer will be able to rotate keys in the event of a compromise to prevent potentially compromised tokens from being used with your application without affecting the availability of your service.

### Example: Define a JWT access method with JWKS

```surql
DEFINE ACCESS token ON DATABASE TYPE RECORD WITH JWT
URL "https://example.com/.well-known/jwks.json";
```

## Token storage

In some instances, applications may need to store some of the authentication tokens issued by SurrealDB. Even when token expiration has been configured to be as low as possible, tokens may potentially be stolen as a result of attacks against the application. To mitigate this risk, take steps to protect tokens in storage from being stolen as a result of these attacks. This is especially relevant in web applications, which usually expose additional attack vectors compared to other client applications.

The best way to protect tokens against stealing is to not store them at all. If your use case supports it, use the token in memory to [`authenticate`](/docs/reference/javascript/api/core/surreal-session.md) a persistent session using the WebSocket protocol and destroy the token from memory after the session is established. When the session expires, ask your users to sign in again with their credentials and establish a new authenticated session with SurrealDB. Your use case may even support not using a token at all by directly authenticating the session with user credentials using [`signin`](/docs/reference/javascript/api/core/surreal-session.md#signin).

However, if you must store the authentication token (e.g. you want authentication to persist across browser tabs or restarts), our recommendation for most use cases is that you store tokens using browser storage primitives such as local storage and that you take steps to protect your web application from script injection attacks by taking measures including the following:

- Encode or at least sanitize all [untrusted input](#content-safety) before showing it on the page.
- Implement a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to prevent unauthorized scripts from executing.
- Implement [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) to verify authorised external scripts.
- Use modern frontend frameworks that are designed to prevent content injection.

Understand that an attacker who is ultimately able to inject scripts into your web application or compromise the devices of your users will still be able to steal their tokens. These recommendations are intended to prevent this script injection from taking place. There is very little you can do to protect your users if your application is vulnerable to script injection attacks regardless of storage method. The impact of this actually happening can be mitigated by ensuring that token expiration is short to minimize the chance of an attacker capturing a valid token and reduce the window of opportunity to exploit it otherwise.

### Why not cookies?

SurrealDB does not support authenticating via cookies. Although cookies with the `secure` and `HttpOnly` flags are often cited as the superior choice for token storage, this is [not always the case](https://portswigger.net/research/web-storage-the-lesser-evil-for-session-tokens). This is especially untrue in the case of generic backend services such as SurrealDB, where protecting against [Cross-Site Request Forgery (CSRF)](https://owasp.org/www-community/attacks/csrf) attacks is not trivial without additional control of the frontend application. These attacks are possible because of how cookies work and would allow attackers to force users to make unauthorized requests to SurrealDB using their own valid cookies. Additionally, cookies are limited to a 4KB size, making them unsuitable for storing certain JWT payloads.

The proposed benefits of using cookies would be that [Cross-Site Scripting (XSS)](https://owasp.org/www-community/attacks/xss/) attacks could not be used to directly read the contents of the token as long as cookies were configured with the `HttpOnly` flag. Although this is true, XSS attacks could still be used to take control of the browser session and impersonate the user using their own cookies to perform any authenticated actions that the token could be used for. This is essentially as bad as the token being stolen because an attacker is not interested in the token itself but rather in what the token can be used for. Most modern attacks against cookies with `HttpOnly` will lead to essentially the same results as those against cookies without it.

In our opinion, the CSRF attacks made possible by cookies would be an unmitigated threat to applications built on SurrealDB. Additionally, XSS attacks are still a threat when using cookies with the `HttpOnly` flag. On the other hand, significant advances have been made by modern browsers and frontend frameworks to prevent XSS attacks, whereas CSRF attacks are not possible to mitigate without the frontend and backend services working together in a way that would not be trivial to implement between SurrealDB and self-developed frontend applications.

## Session isolation

One of the interfaces to SurrealDB is [RPC through WebSockets](/docs/reference/rest-api/rpc-protocol.md). This interface is usually used by the official [SDKs](/docs/languages.md) and offers performance benefits over the [HTTP REST API](/docs/reference/rest-api/http-protocol.md), which requires establishing a new connection for every operation. The RPC interface can either be directly exposed to end users or used internally from your backend to communicate with SurrealDB.

In the latter scenario, some developers may choose to still authenticate each user individually (e.g. using [`signin`](/docs/reference/rest-api/rpc-protocol.md#signin) or [`authenticate`](/docs/reference/rest-api/rpc-protocol.md#authenticate) in the WebSockets [session](/docs/learn/security/authentication/users.md#sessions) as opposed to using a single service user for their backend. This could be done by calling [`invalidate`](/docs/reference/rest-api/rpc-protocol.md#invalidate) or just authenticating a new user in the same connection and may provide some performance benefits over establishing a new WebSocket connection for each user. However, we recommend using separate WebSocket sessions or connections for different users. Consider terminating the connection and establishing a new one for every individual user.

WebSocket connections offer an additional degree of isolation between users that may become relevant in the event where some session information for previous users who were using the same connection was not properly cleared. Additionally, even if successfully isolated from the security perspective, some resources associated with users are freed by SurrealDB only when the connection is terminated. Sharing the same WebSockets connection between several users may cause these unused resources to grow indefinitely.

## Limiting expensive queries

A single query can ask for a lot of work, such as a graph path that fans out through every record several times. These settings limit what one query can cost the server:

- **[`--query-timeout`](/docs/reference/cli/db/environment-variables.md#command-environment-variables)** (`SURREAL_QUERY_TIMEOUT`) stops any query that runs longer than the given duration. A query can also set its own limit with the [`TIMEOUT` clause](/docs/reference/query-language/statements/select.md#the-timeout-clause).
- **[`SURREAL_MEMORY_THRESHOLD`](/docs/reference/cli/db/environment-variables.md#limits-config)** refuses new queries while the server's tracked memory is above the given size. From 3.3.1 it also stops a running query that reads records once memory passes that size.
- **`SURREAL_MAX_COMPUTATION_DEPTH`** limits how deeply nested computations can go, and a recursive path stops at a depth of 256.
- **[`--deny-arbitrary-query`](/docs/learn/security/authorization/capabilities.md#arbitrary-queries)** with a value such as `--deny-arbitrary-query=record,guest` stops record users and guests from sending queries of their own, so they can only call the [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) endpoints and functions you define. This is the strongest protection for a database that is reachable from the public internet, because the shape of every query is then decided by you.

## Untrusted queries

Because SurrealQL includes functions, scripting and network capabilities, running untrusted queries in SurrealDB as a [system user](/docs/learn/security/authentication/users.md#system-users) should be treated similarly to running untrusted software in any system. When copying queries or importing datasets from sources that you do not trust, make sure to review their contents to ensure that they do not contain any malicious code intended to perform unauthorized changes, computations or network requests.

## Content safety

Content generated by users and other untrusted parties will often be stored in SurrealDB and later rendered in an HTML page to be displayed. Regardless of SurrealDB, rendering untrusted content is the source of some dangerous pitfalls which can lead to [cross-site scripting](https://en.wikipedia.org/w/index.php?title=Cross-site_scripting&oldid=1232455342) attacks and other client-side code injection issues like [site defacement](https://en.wikipedia.org/w/index.php?title=Website_defacement&oldid=1231310592) or [clickjacking](https://en.wikipedia.org/w/index.php?title=Clickjacking&oldid=1227193298).

When retrieving content that may be rendered in an HTML document, we strongly recommend that you use the [`string::html::encode`](/docs/reference/query-language/functions/database-functions/string.md#stringhtmlencode) function, which will encode any characters that have special meaning in HTML syntax (e.g. `<`, `>`, `&`...) into HTML entities (e.g. `&lt;`, `&gt;`, `&amp;`...) that will be rendered as the actual original character instead of interpreted as HTML syntax.

### Example: Encode HTML content

```surql
string::html::encode("<h1>Safe Title</h1><script>alert('XSS')</script><p>Safe paragraph. Not safe <span onload='logout()'>event</span>.</p>");

['&lt;h1&gt;Safe&#32;Title&lt;&#47;h1&gt;&lt;script&gt;alert(&apos;XSS&apos;)&lt;&#47;script&gt;&lt;p&gt;Safe&#32;paragraph.&#32;Not&#32;safe&#32;&lt;span&#32;onload&#61;&apos;logout()&apos;&gt;event&lt;&#47;span&gt;.&lt;&#47;p&gt;']
```

If you absolutely require user-generated content to be rendered as HTML but still want to prevent users from injecting dangerous HTML into your page, you can use the [`string::html::sanitize`](/docs/reference/query-language/functions/database-functions/string.md#stringhtmlsanitize) function instead, which will keep all characters intact, so that the content can be interpreted as HTML syntax, while removing the specific syntax that is deemed dangerous. Although the set of accepted syntax is very conservative, sanitization is less safe than encoding and could potentially be bypassed due to a flaw in the function.

### Example: Sanitize HTML content

```surql
string::html::sanitize("<h1>Safe Title</h1><script>alert( 'XSS'
  )</script><p>Safe paragraph. Not safe <span onload= 'logout()'
  >event</span>.</p>");

['<h1>Safe Title</h1><p>Safe paragraph. Not safe <span>event</span>.</p>']
```

## Encryption at rest

Encryption at rest is recommended especially when storing sensitive data in a location where you cannot guarantee the security of the storage media. If encryption at rest is not used, physical access to the storage media may result in the complete compromise of the data stored. Most kinds of encryption at rest will not prevent logical attacks from resulting in compromise of the data, as such attacks will often access the data using the compromised system as a [confused deputy](https://en.wikipedia.org/w/index.php?title=Confused_deputy_problem&oldid=1230222963) in order to use its ability to access data after it is already decrypted.

Encryption at rest can be achieved by ensuring that the data is stored encrypted using a disk encryption solution such as [LUKS](https://en.wikipedia.org/w/index.php?title=Linux_Unified_Key_Setup&oldid=1225491340) or [BitLocker](https://en.wikipedia.org/w/index.php?title=BitLocker&oldid=1232782210) in Linux and Windows systems respectively and, in the case where you are hosting SurrealDB in a cloud provider, by using their storage encryption solutions in the volume or disk that will store your data.

You might consider additional encryption for your datastore in some specific scenarios. This can provide increased security when your database servers, storage media and their corresponding encryption keys are managed in different security contexts, where the storage media and its keys may be compromised without also compromising the datastore servers. Encryption at rest at the datastore level can be achieved by using a datastore backend that offers transparent encryption. This encryption is independent from SurrealDB.

Even in this scenario, physical or logical access to the SurrealDB server will result in access to the data, as SurrealDB must receive decrypted data from the datastore in order to perform any sort of queries.

## Vulnerabilities

When SurrealDB is part of your service or application, vulnerabilities that affect SurrealDB may also impact your environment. Due to this fact, we highly recommend that you track [vulnerabilities published for SurrealDB](https://github.com/surrealdb/surrealdb/security/advisories) so that you become aware of any updates that address vulnerabilities that you may be affected by. This can be done most effectively by using automation tools that will consume the [GitHub Advisory Database](https://github.com/advisories?query=surrealdb). These automations will usually also warn of vulnerabilities in dependencies used by SurrealDB, which may also have an impact in your environment. Keeping up to date with the latest releases of SurrealDB is, in general, a good practice.

If you identify a vulnerability in SurrealDB that has not been published yet, we encourage you to [create a security advisory report](https://github.com/surrealdb/surrealdb/security/advisories/new) on GitHub so that the SurrealDB team can privately look into it in order to identify and work on a solution that can benefit you as well as the rest of the users.

---

Source: https://surrealdb.com/docs/learn/security/best-practices/troubleshooting

# Troubleshooting

This page provides some troubleshooting advice to support users in addressing issues either caused by or involved in the usage of specific security features provided by SurrealDB.

This page collects the errors that come up most often when using SurrealDB's security features, with the cause of each and how to resolve it.

## Authentication

### Invalid authentication error

The most common error that users may receive when authenticating to SurrealDB is `InvalidAuth`, which results in a generic message such as `There was a problem with authentication`. The reason that this message is so vague and yet so common is that it is a placeholder for other more specific internal errors related to authentication. These errors are not returned to the client due to their potential to leak internal information about the database (e.g. whether or not a user or access method exists, whether a token failed to verify because of its signature or a specific claim...) to unauthenticated users.

Although the internal cause for these errors is not revealed to clients, it can be identified by the SurrealDB administrator through its server logs. These logs will usually be prefixed by `surrealdb::core::iam`. Most helpful messages will be displayed when [starting SurrealDB with `--log debug`](/docs/reference/cli/db/commands/start.md#command-help). If those logs are not enough to diagnose the problem, starting the server with `--log trace` will provide additional messages describing the authentication process.

In situations where debugging must be performed on the client, starting the SurrealDB server with the environment variable `SURREAL_INSECURE_FORWARD_ACCESS_ERRORS` set to `true` will forward errors resulting from [`SIGNIN`, `SIGNUP`](/docs/reference/query-language/statements/define/access/record.md#example-usage) and [`AUTHENTICATE`](/docs/reference/query-language/statements/define/access/record.md#with-authenticate-clause) clauses directly to clients. Since these errors may expose the internal logic of such clauses, this variable should only be used for debugging in controlled environments with trusted clients.

#### Common causes

The following are the most common causes for a generic authentication error in SurrealDB.

##### Incorrect user credentials

This is the simplest reason why authentication may fail. Ensure that the credentials that you are using to authenticate (e.g. username and password) match the ones that you have defined.

For system users, ensure that any special characters (e.g. quotes) have been interpreted correctly as part of the username or password rather than as SurrealQL syntax. Use the `INFO` statements to display the defined users and ensure that their password hash matches the hash of the password that you are providing. You can use the included [compare hash functions in SurrealQL](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) to perform this verification.

For record users, ensure that the `SIGNIN` and `SIGNUP` queries will return a user. You can do that by running those queries by themselves in the SurrealQL interpreter (`surreal db sql`) or in SurrealDB Studio. You will just need to replace any parameters from those queries by the parameters that you are providing during authentication. Ensure that you are in fact providing all the expected parameters during authentication in your actual code.

##### The referenced user or access method does not exist

Ensure that the user and access method that you are authenticating with exists on the level that you are trying to authenticate. Check that you are authenticating against the namespace or database where the resource is defined and that you have correctly spelled the resource everywhere.

When authenticating with the SurrealDB CLI or with the HTTP REST API, ensure that you specify the namespace and database that your user exists in (i.e. `--auth-level ns` and `--auth-level db` or `surreal-auth-ns` and `surreal-auth-db`), in addition to the namespace and database that you want to use for the connection (i.e. `--namespace` and `--database` or `surreal-ns` and `surreal-db`). Otherwise, SurrealDB will default to authenticating at the root level.

##### The token could not be cryptographically verified

When using a token to authenticate, SurrealDB will reject tokens that fail the verification of its cryptographic signature. If the token has been issued by SurrealDB, this is most likely not the cause for your issue. If the token has not been issued by SurrealDB, this usually means that the provided token is signed using a different algorithm than defined or that the key used to sign the token is incorrect.

For tokens that are [verified from a URL hosting a JWKS object](/docs/reference/query-language/statements/define/access/jwt.md#json-web-key-set-jwks), failure to verify the token may be caused by the URL being incorrect, unreachable by the SurrealDB server, or outbound connections to the address not being allowed by the [network capability](/docs/learn/security/authorization/capabilities.md#network). If the JWKS hostname resolves to a private or special-use address, allow that IP or CIDR in addition to the hostname. Relevant logs appear on the SurrealDB server when you run with `--log debug` or `--log trace`.

##### The token is missing a required claim

Tokens that are missing any of the claims required by SurrealDB will be rejected with a generic error. Ensure that tokens issued outside of SurrealDB contain all the required claims. Depending on whether the token is for a system user, a record access method or a JWT access method, different claims will be expected. In all cases, the `exp` claim is required. Consult the [Using Tokens](/docs/reference/query-language/statements/define/access/jwt.md#using-tokens) section of the `DEFINE ACCESS ... TYPE JWT` documentation for more information about the required claims for system and record users.

### Other authentication errors

The following is a non-comprehensive list of authentication errors which may sometimes be challenging to understand.

#### Unexpected authentication error

Like `InvalidAuth`, `UnexpectedAuth` is an error that acts as a placeholder for a different group of errors. This error results in a generic message such as `There was an unexpected error while performing authentication`. Unlike errors specific to authentication, this error is returned when the original error is not directly related with authentication, but rather with a failure in some of the components necessary for authentication.

This distinction is made because, unlike `InvalidAuth` errors, `UnexpectedAuth` errors may be retried by clients, as it is possible that the internal failure that resulted in the error is ephemeral. Clients may want to handle these two errors differently; for example, by returning `InvalidAuth` errors to user but internally attempting to retry on `UnexpectedAuth` errors, only returning an internal error to the user if the error persists.

A specific example for this error is the `AUTHENTICATE`, `SIGNIN` or `SIGNUP` clauses failing due to a transaction error, such as those which can arise from a write conflict during the transaction. These conflicts may appear for some datastores in any query when two transactions simultaneously access the same document.

#### Token expired error

This error is returned when the token that is being used to authenticate a session has [expired](/docs/learn/security/authentication/users.md#expiration). If this token was issued by SurrealDB, this expiration defaults to an hour and can be changed using the `DURATION FOR TOKEN` clause in [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) and [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md). If the token was not issued by SurrealDB, this expiration will be set by the `exp` claim.

Note that this error will only appear when trying to authenticate the [session](/docs/learn/security/authentication/users.md#sessions). After authentication, the session will not expire when the token does, but rather after the independent session duration that has been defined in the `DURATION FOR SESSION` clause. By default, sessions will not expire. When using the HTTP REST API, a persistent session is not established as each request will be individually authenticated. As a consequence, requests made using an expired token will be rejected with this error.

Although tokens accepted by SurrealDB must have some expiration, you can configure any amount of time that fits your security and usability requirements with the `DURATION FOR TOKEN` clause or by configuring the `exp` claim if you are using an external token issuer.

To address token expiration errors you will either have to ask your end user to authenticate again with credentials to obtain a new token, rely on a persistent authenticated WebSocket session, rely on an external identity provider like [Auth0](/docs/explore/tutorials/tutorials/auth0-integration.md) or [AWS Cognito](/docs/explore/tutorials/tutorials/aws-cognito-integration.md) or, for development purposes, test the [experimental refresh token feature](/docs/reference/query-language/statements/define/access/record.md#with-refresh-token).

### Other authentication issues

#### Slow / expensive authentication

In SurrealDB, clients can authenticate using different methods, such as credentials (e.g. username and password), a token (i.e. JWT) and other access methods like bearer keys. These access methods provide different benefits and have different performance implications. Different options are provided precisely to allow flexibility for different use cases.

If you observe that authentication requests are slow or computationally (e.g. CPU, memory...) expensive, consider switching from authentication with credentials to token authentication. By default, SurrealDB verifies passwords for system users with [Argon2id](https://en.wikipedia.org/wiki/Argon2), which is a password hashing algorithm that ensures that the password verification process is slow and expensive to prevent password cracking. If you are using the [HTTP REST API](/docs/reference/rest-api/http-protocol.md) to authenticate using credentials (i.e. with `Authorization: Basic ...` rather than `Authorization: Bearer ...`), this password verification process will be performed for every request. Instead, consider [authenticating once with credentials](/docs/reference/rest-api/http-protocol.md#signin) to obtain a token and performing subsequent requests using that token in the `Authorization` header prefixed by `Bearer`. Token verification is a different cryptographic process that is designed to be orders of magnitude faster and cheaper.

When using credentials to authenticate record users, you are able to choose the password hashing algorithm that you use in your `SIGNIN` query. If you must repeatedly authenticate using credentials and the performance impact of using Argon2id is unacceptable to your use case, you may consider using different password hashing algorithms (e.g. [Bcrypt](/docs/reference/query-language/functions/database-functions/crypto.md#cryptobcryptcompare), [Scrypt](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoscryptcompare) or [PBKDF2](/docs/reference/query-language/functions/database-functions/crypto.md#cryptopbkdf2compare)) that may be more efficient at the cost of diminished security against certain types of password cracking.

Authentication logic that is implemented by users via the `SIGNIN`, `SIGNUP` and `AUTHENTICATION` clauses may also be costly depending on the logic implemented therein. If you are using any custom authentication logic, verify that it is not the cause for the performance cost by manually executing the query in the SurrealQL interpreter (`surreal db sql`) or in SurrealDB Studio and ensuring that the time that it takes to execute is acceptable.

### Requesting authentication support

If you are not able to diagnose a specific authentication issue with the information provided on this page, you may consider asking support from the SurrealDB community. If you do so, this section describes some information that you should provide in order to help others help you with your issue. Please, only ask support directly from the developers of SurrealDB if you believe your issue may be related to a bug in SurrealDB.

Before sharing any information with other SurrealDB users or the SurrealDB team, please ensure that it does not contain any sensitive data, including secrets which may be used to access sensitive data. We recommend setting up a separate SurrealDB environment with dummy data for the purposes of debugging.

In that environment, you can add [`.expect()`](/docs/reference/query-language/functions/database-functions/value.md#valueexpect) (from SurrealDB 3.1) on intermediate results in a SurrealQL reproduction to see which step first diverges from what you expect, without changing schema `ASSERT` rules or production `THROW` handlers.

When requesting support with authentication, we recommend that you provide the following information:

- The version of SurrealDB that you are using, which you can obtain with the `surreal db version` command.
- The command line arguments that you are using to start the SurrealDB server with the `surreal db start` command.
- The error message that your client receives when attempting to authenticate against SurrealDB.
- The logs that are displayed on the server, running with `--log debug`, when authentication fails.
- A description of the resources you are using to authenticate. These resources will most likely be included in the output of `INFO FOR ROOT`, `INFO FOR NAMESPACE` and `INFO FOR DATABASE`. If your authentication logic relies on data from specific records through `SIGNIN`, `SIGNUP` or `AUTHENTICATE` clauses, include the contents of those records as well.
- A simplified proof of concept that reproduces your issue. Please, do not share the complete code that you are using to authenticate with SurrealDB. Instead, provide a minimal example that only contains the relevant elements and that does not require a specific SDK to reproduce. If possible, provide a simple request that uses the [HTTP REST API](/docs/reference/rest-api/http-protocol.md#signin) (e.g. with `curl` or similar) to reproduce the error that you are encountering.
- If you are using a token to authenticate, provide an example of a token that triggers the issue. In the case of a JWT, please include the full encoded contents of the token, not just the values of its claims.

---

Source: https://surrealdb.com/docs/manage/enterprise

# Enterprises

A SurrealDB enterprise sits above organisations and accounts, adding single sign-on, centralised member management, and custom roles.

A SurrealDB enterprise sits above SurrealDB organisations and accounts and adds features such as single sign-on and fine-grained, centralised member management.

A standard [organisation](/docs/manage/organisations.md) groups instances, members, usage, and billing, and gives each member one of three fixed roles. An enterprise can be used to manage these organisations and adds a second layer of administration over them, with its own members, roles, and an optional connection to your identity provider.

> [!NOTE]
> An enterprise is a different product from [SurrealDB Enterprise Edition](/docs/manage/enterprise/product/database-enterprise.md). Enterprise Edition is the SurrealDB server sold with a licence key, which adds capabilities such as audit logging and FIPS mode to the database itself. An enterprise changes how people sign in to SurrealDB Studio and what they may administer there.

## What an enterprise adds

| Capability | What it gives you |
| --- | --- |
| **[Single sign-on](/docs/manage/enterprise/single-sign-on.md)** | People may sign in through your own identity provider instead of creating a SurrealDB account. |
| **[SCIM provisioning](/docs/manage/enterprise/scim-provisioning.md)** | Your identity provider creates, updates and deactivates accounts in SurrealDB. |
| **[Central member management](/docs/manage/enterprise/members.md)** | One member list for the whole company, with invitations sent one at a time, from a file, or an invite link. |
| **[Fine-grained user management](/docs/manage/enterprise/members.md)** | Users who are managed through a single sign-on connection can be managed by authorised members of the enterprise in ways that are not possible with managed accounts or standard organisations. |
| **Brand personalisation** | Your logos and colours can be seen throughout SurrealDB products and pages, giving a more personalised experience. |

## Getting started with SurrealDB for Enterprises

Enterprise creation is currently handled by request. Please contact us at [sales@surrealdb.com](mailto:sales@surrealdb.com) to get started, or create a request direct from SurrealDB Studio by opening [SurrealDB Studio's enterprises page](https://studio.surrealdb.com/enterprises) and selecting **Request an enterprise**.

## Where enterprises are managed

An enterprise can be managed from the `/enterprises` page in SurrealDB Studio or accessed by clicking the `My Enterprises` button in the account menu in the top right. This is a separate portal from the rest of the app, so you will need to click **Return to Studio** when done to go back to your organisations and instances.

| Page | What you can do there |
| --- | --- |
| **Overview** | Get an overview of your enterprise and view the onboarding steps to get you set up and ready to use your enterprise. |
| **[Members](/docs/manage/enterprise/members.md)** | See who belongs to the enterprise, invite people, and assign roles. |
| **[Roles](/docs/manage/enterprise/roles.md)** | Compose roles out of the permissions an enterprise can grant. |
| **[Single sign-on](/docs/manage/enterprise/single-sign-on.md)** | Connect your identity provider to the enterprise. |
| **[SCIM provisioning](/docs/manage/enterprise/scim-provisioning.md)** | Let your identity provider manage the accounts of a single sign-on connection. |
| **Settings** | Change your enterprise's display name and branding. |

## Related pages

- **[Organisations](/docs/manage/organisations.md):** Instances, usage, and billing.
- **[Members and roles](/docs/manage/organisations/members-and-roles.md):** Learn more about role management in standard organisations.
- **[Accounts and sign-in](/docs/manage/organisations/sign-in.md):** How sign-in works without an enterprise.
- **[Enterprise Edition](/docs/manage/enterprise/product/database-enterprise.md):** Self-hosted database features unlocked with an Enterprise licence.

---

Source: https://surrealdb.com/docs/manage/enterprise/members

# Enterprise members

Invite people to an enterprise and assign them roles. Send one invitation, import a member list from a file, and track what is pending.

The **Members** page lists everyone who exists inside of an enterprise, along with the roles each of them holds and their sign-in method.

> [!NOTE]
> Enterprise memberships are not passed down to sub-organisations currently, but this is planned for a future release. For now, each member will need to be invited to each sub-organisation manually.

Two sections divide the list. **Members** holds the people who have already accepted an invitation and are in the enterprise. **Pending invitations** holds the invitations you have sent that nobody has accepted yet.

![A page showing the members of an enterprise, with a list of members and a list of pending invitations.](~/assets/img/surrealdb/manage/enterprise-members.webp)

Where a member came from is shown beside their name. There are three types of members:

| Type | Description |
| --- | --- |
| **Managed** | A standard user account that is managed by SurrealDB's authentication system. |
| **Internal** | A user who was created by signing in through an identity provider in your enterprise. |
| **External** | A user who was created and managed by another enterprise but has access to your enterprise. |

> [!NOTE]
> Any user who signs in through an identity provider in your enterprise is automatically considered a member. To restrict access to your enterprise, manage access externally at the identity provider level.

## Invite one member

1. Open **Members** in your enterprise.
2. Select **Invite member**.
3. Enter the email address of the person you invite.
4. Choose the [roles](/docs/manage/enterprise/roles.md) they hold.
5. Send the invitation.

The invitation appears under **Pending** until it is accepted. Select **Revoke invitation** to withdraw one you no longer want.

Invite the address of a named person rather than a shared mailbox. A shared mailbox hides which person acted, and you cannot withdraw it from one reader who leaves.

![A page showing a modal with a single user invite email filled and the admin role selected.](~/assets/img/surrealdb/manage/enterprise-member-invite-single.webp)

## Invite a list of members

Use an import when you have many people to invite at once.

1. Open **Members**, then start an import.
2. Choose the file that holds the addresses.
3. Choose the **Default roles**, which apply to any row that names no roles of its own.
4. Check the rows Studio has read.
5. Send the invitations.

Studio reads the whole file and shows you every row before anything is sent, with the roles it resolved for each one. A row it cannot invite is listed with the reason, so you can correct the file and import it again.

### The file format

The file holds one member per line, as an email address followed by the roles that member holds.

```text title="members.csv"
email,roles
alex@example.com,Engineering
sam@example.com,Engineering;Billing
jo@example.com,
```

Three rules govern how the file is read:

- A **header row** is optional. With one, the columns are named. Without one, each line is read as the address first and the roles second.
- **Roles in one cell are separated by semicolons**, because the comma already separates the columns.
- A **row that names no roles** takes the default roles you chose.

A row is reported rather than sent when the address is not valid, when it names a role the enterprise does not hold, or when that person is already a member or already invited. Both role names and role ids are valid in the file.

## Change what a member holds

1. Find the member in the list.
2. Open the member actions.
3. Select the roles they hold, then save.

Selecting roles replaces the whole set that member held, rather than adding to it. Include every role you want them to keep.

## Remove a member

Open the member actions to remove the membership. The person keeps their account and their access to anything outside this enterprise.

Remove access when a person leaves the project, and review the member list at regular intervals. A member who has left keeps every role you granted them until you remove the membership.

## Disable an internal member's login

You cannot remove an **Internal** member, because they would join again the next time they sign in through your identity provider. Disable their login instead.

1. Find the member in the list.
2. Open the member actions.
3. Select **Disable login**, then confirm.

The member stays on the list, marked **Disabled**, and can no longer sign in. A session they already hold can stay active for a short time. Select **Enable login** to let them sign in again.

With [SCIM provisioning](/docs/manage/enterprise/scim-provisioning.md), your identity provider disables and enables logins for you when it deactivates or activates a person.

## Related pages

- **[Roles](/docs/manage/enterprise/roles.md):** What each role lets a member administer.
- **[Single sign-on](/docs/manage/enterprise/single-sign-on.md):** Connecting the identity provider members authenticate against.
- **[SCIM provisioning](/docs/manage/enterprise/scim-provisioning.md):** Letting your identity provider create and deactivate member accounts.

---

Source: https://surrealdb.com/docs/manage/enterprise/product/database-enterprise

# Enterprise Edition Database

What Enterprise Edition adds over Community: fault-tolerant multi-zone clusters, storage and compute that scale independently, distributed live queries, audit logging, FIPS-validated cryptography, trusted execution and contractual support, plus the licence key it needs to start, the SURREAL_LICENSE_* variables and the Docker Hub image.

SurrealDB Enterprise Edition uses the same core SurrealDB server, with additional capabilities enabled by a licence key. Enterprise and Community use different binaries, so Enterprise-only features are available only in the Enterprise build. Moving between Community and Enterprise does not require changes to your schema, queries, or SDK code.

## What the edition adds

| Capability | What it is | Documented |
| --- | --- | --- |
| **Fault tolerance** | Always-on clusters that run across different availability zones, so the database keeps serving through the loss of a node or a whole zone. | Contact sales |
| **Storage scalability** | A distributed storage architecture backed by object storage, so storage grows with your data without you having to shard the database. | Contact sales |
| **Compute scalability** | Horizontal scaling from 3 nodes to tens of nodes, to raise concurrency and the compute capacity available to your workload. | Contact sales |
| **Audit logging** | An identity-bound record of every authenticated action - statements, queries, transactions, RPC calls, sign-ins, sessions and HTTP requests - written to a durable NDJSON sink, with optional SHA-256 hash chaining and PII redaction. | [Audit logging](/docs/manage/observability/audit-logging.md) |
| **Distributed live queries** | Live queries that resolve across every node in a cluster rather than the node holding the subscription. | Contact sales |
| **FIPS mode** | FIPS 140-2 validated cryptographic modules, and the switch that enforces their use. | Contact sales |
| **Trusted execution** | Running the server inside a trusted execution environment, with attestation. | Contact sales |
| **Contractual support** | Support tiers with defined response times by severity, and uptime commitments. | Contact sales |

S3-compatible bucket backends were available in Enterprise builds only before 3.3.0. From 3.3.0 the Community build includes them, alongside Google Cloud Storage and Azure Blob Storage, and Enterprise uses the same backends. See [`DEFINE BUCKET`](/docs/reference/query-language/statements/define/bucket.md#cloud-object-storage-backends).

## Licence key

_(since v3.3.0)_

An Enterprise build needs a valid licence key to start. Three environment variables configure licensing:

| Variable | Value |
| --- | --- |
| `SURREAL_LICENSE_KEY` | The licence key. |
| `SURREAL_LICENSE_KEY_FILE` | The absolute path to a file containing the key, such as a mounted Kubernetes Secret. |
| `SURREAL_LICENSE_STATE_PATH` | Optional. The absolute path of the attestation cache file, which lets a node start while the licence service is unreachable. |

Setting both `SURREAL_LICENSE_KEY` and `SURREAL_LICENSE_KEY_FILE` is a startup error, and so is a relative `SURREAL_LICENSE_KEY_FILE` path or a key file that is unreadable or empty. The key is read once at startup, so replacing it means updating the variable or file and restarting the node. Everything else about licensing, including the validation service, the grace periods and the revalidation interval, is fixed in the binary.

The node validates the key online against keygen.sh (`api.keygen.sh`) and checks the signature on each response against a key built into the binary. A first start therefore needs network access to `api.keygen.sh`. While it runs, the node revalidates every 30 minutes, with 10% jitter.

With `SURREAL_LICENSE_STATE_PATH` set, the node stores the last signed response at that path and verifies it again whenever it reads it back. The file is created with mode `0600`, because it contains the licence key. Put it on a persistent volume, because a cache in a container's writable layer is lost when the container is recreated. Without the variable there is no cache, and every start needs keygen.sh to be reachable. A relative path, or one that is not writable, stops the node from starting.

| Licence state | At startup | On a running node |
| --- | --- | --- |
| Valid | Starts. | Keeps serving. |
| No key configured | Refuses to start. | - |
| Expired | Refuses to start. There is no expiry grace period. | Keeps serving and logs a warning. The next start is refused. |
| Suspended, banned or not found | Refuses to start. | Once two consecutive checks two minutes apart confirm it, `/ready` returns `503`, and the process exits with status `3` 24 hours later. Reinstating the licence within that time restores service without a restart. |
| keygen.sh unreachable | Starts if the cache holds a valid attestation whose signed response is less than 24 hours old, and refuses otherwise. | Keeps serving for 24 hours from the last verified response. Then `/ready` returns `503`, and the process exits with status `3` three hours later unless keygen.sh answers in the meantime. |

A refused start exits with status `1`. The 24-hour offline allowance is one budget measured from keygen.sh's signed timestamp, so a restart during an outage continues it rather than starting a new one. A `503` from `/ready` takes the node out of a readiness-gated load balancer, while clients that connect to it directly are still served until the process exits.

An attestation cached by v3.3.0-beta.4 is not accepted by v3.3.0, so the first start after that upgrade needs keygen.sh to be reachable.

Licence state is reported only through the `surrealdb_license_*` gauges on `/metrics`, which an anonymous scrape does not return. See [Licence metrics](/docs/manage/observability/metrics.md#licence---surrealdblicense).

The Enterprise build is published on Docker Hub as [`surrealdb/surrealdb-enterprise`](https://hub.docker.com/r/surrealdb/surrealdb-enterprise). [Run with Docker](/docs/manage/self-hosted/docker.md#running-surrealdb-enterprise) has an example that passes the licence key.

## Observability is where most of it is written down

The Enterprise observability surface is documented in full alongside the Community one, with the Enterprise-only parts marked. Start there rather than here:

- [Audit logging](/docs/manage/observability/audit-logging.md) - events captured, record shape, rotation, hash chaining, redaction and overflow semantics.
- [Configuration](/docs/manage/observability/configuration.md#audit-log-knobs) - every audit-log environment variable, and the compliance checklist for a tamper-evident deployment.
- [Enterprise observability](/docs/manage/observability/enterprise-observability.md) - the pipelines that exist only in an Enterprise build.
- [Slow-query logging](/docs/manage/observability/slow-query-logging.md) - the sister pipeline for the long tail of slow queries.

## Licensing and getting in touch

Licence models, pricing, support tiers and the specifics of FIPS, trusted execution and distributed live queries are handled by the SurrealDB team rather than published here. [Contact us](https://surrealdb.com/contact) to discuss an evaluation, a licence, or an upgrade from an existing Community cluster.

> [!NOTE]
> Moving an existing deployment to Enterprise does not migrate data. The same storage backend and the same schema are read by the Enterprise binary; the licence key is what changes.

---

Source: https://surrealdb.com/docs/manage/enterprise/roles

# Enterprise roles & permissions

Compose custom roles from the permissions an enterprise can grant. How enterprise roles differ from the fixed organisation roles.

A role is a named set of permissions. You compose the roles your enterprise needs, then assign them to [members](/docs/manage/enterprise/members.md).

> [!NOTE]
> Enterprise roles do not currently pass down permissions to sub-organisations, but this is planned for a future release. For now, roles are purely used to manage your **enterprise** itself.

## Creating a role

1. Open **Roles** tab in your enterprise.
2. Select 'Create role'.
3. Enter a **Name**, for example `Engineering`.
4. Enter a **Description**, which identifies what a role does and who is assigned to it without reading the permissions.
5. Select the **Permissions** the role grants.
6. Select 'Create role' to finish creating the role.

A role grants nothing until it holds at least one permission, so an empty role cannot be saved. You can always go back and edit a role at any time after creating it.

## What a role can grant

The role editor lists the permissions your enterprise can grant, grouped by the area each one applies to. Read the editor for the current list, and use the table below to plan.

| Area | Permissions | What they cover |
| --- | --- | --- |
| **Members** | Read, add, update, remove | Seeing the member list, inviting people, changing the roles a member holds, and ending a membership. |
| **Roles** | Read, create, update, delete | Seeing roles, and composing or removing them. |
| **Domains** | Read, create, verify, delete | Seeing domains, adding one, verifying ownership, and removing one. |
| **Single sign-on** | Read, create, update, delete, route domains, set applications | Seeing connections, adding or removing one, choosing which domains route to it, and choosing which applications it may be used with. |
| **Settings** | Update | Changing the display name and the branding. |

Permission to read an area and permission to change it are granted separately. A role can therefore let someone audit single sign-on without letting them alter a connection.

## The owner role

By default, every enterprise comes with an owner role which grants all permissions that is not shown in the roles list and cannot be modified or removed. Only one owner can be assigned at a given time, however, you can transfer ownership to a different member by using the dots menu in the member list.

## Related pages

- **[Members](/docs/manage/enterprise/members.md):** Assigning roles to people.
- **[Single sign-on](/docs/manage/enterprise/single-sign-on.md):** The configuration these permissions govern.
- **[Members and roles](/docs/manage/organisations/members-and-roles.md):** The fixed roles inside an organisation.

---

Source: https://surrealdb.com/docs/manage/enterprise/scim-provisioning

# SCIM provisioning

Let your identity provider create, update and deactivate the accounts of a single sign-on connection in SurrealDB with SCIM 2.0.

Use SCIM provisioning to let your identity provider create, update and deactivate the SurrealDB accounts of a single sign-on connection. SCIM (System for Cross-domain Identity Management) is a standard protocol that identity providers use to keep user accounts in other apps up to date.

Without SCIM, SurrealDB makes an account the first time a person signs in, and the account stays until you disable it. With SCIM, your identity provider tells SurrealDB when a person joins, changes or leaves, so an account that you disable in your identity provider stops working in SurrealDB too.

## Before you start

You need:

- A [single sign-on connection](/docs/manage/enterprise/single-sign-on.md) that works.
- Permission to update identity providers in your enterprise.
- An identity provider that supports SCIM 2.0, and access to its admin console.

SCIM is available for these connection types:

| Connection type | SCIM |
| --- | --- |
| Okta | Available |
| Microsoft Entra ID | Available |
| SAML | Available |
| OpenID Connect | Available |
| Google Workspace, ADFS, PingFederate, Keycloak | Not available |

## Step 1: Turn on SCIM for the connection

1. Open **Single sign-on** in your enterprise.
2. Open the connection.
3. Open the **Provisioning** tab.
4. Turn on **SCIM provisioning**.

Studio then shows the **SCIM base URL** for the connection. Each connection has its own URL.

## Step 2: Create a token

Your identity provider uses a bearer token to prove that it may change accounts.

1. On the **Provisioning** tab, select **Create token**.
2. Copy the token.
3. Select **Done**.

> [!WARNING]
> Studio shows the token one time only. Copy it before you select **Done** or leave the page. Anyone who has the token can create and deactivate accounts on this connection, so keep it in a secure place.

## Step 3: Connect your identity provider

Each identity provider sets up SCIM in its own way, and each one must send the user ID that matches the identity people sign in with. Follow the instructions for your provider:

| Identity provider | Instructions |
| --- | --- |
| Okta | [Provision users from Okta](/docs/manage/enterprise/single-sign-on/okta.md#step-7-provision-users-with-scim) |
| Microsoft Entra ID | [Provision users from Entra ID](/docs/manage/enterprise/single-sign-on/microsoft-entra-id.md#step-6-provision-users-with-scim) |
| Another SAML provider | [Provision users over a SAML connection](/docs/manage/enterprise/single-sign-on/saml.md#step-4-provision-users-with-scim) |
| Another OpenID Connect provider | [Provision users over an OpenID Connect connection](/docs/manage/enterprise/single-sign-on/openid-connect.md#step-3-provision-users-with-scim) |

> [!IMPORTANT]
> If the user ID that your identity provider sends over SCIM is different from the one it sends at sign-in, the person gets a second account the first time they sign in. Each provider's instructions say which attribute to send.

## What SurrealDB does with each change

| Change in your identity provider | Result in SurrealDB |
| --- | --- |
| A person is assigned to the app | SurrealDB makes their account. They join the enterprise the first time they sign in. |
| A person's name or email changes | SurrealDB updates their account. |
| A person is deactivated or unassigned | SurrealDB disables their login and ends their sessions. The **Members** page shows them as **Disabled**. |
| A person is activated again | SurrealDB enables their login again. |
| A person is deleted | SurrealDB deletes their account. |

SCIM does not assign roles. A new member holds no roles until you [grant them](/docs/manage/enterprise/roles.md).

## Rotate a token

A connection holds up to two tokens at a time. To replace a token without a gap in provisioning:

1. Create a new token.
2. Give the new token to your identity provider.
3. Beside the old token, select **Revoke token**.

The **Provisioning** tab shows when each token was last used, which helps you find a token that nothing uses any more.

## Turn off SCIM

Turn off **SCIM provisioning** on the **Provisioning** tab. SurrealDB revokes every token of the connection, and your identity provider can no longer change accounts. The accounts that SCIM made stay, and people can still sign in through the connection.

## Related pages

- **[Single sign-on](/docs/manage/enterprise/single-sign-on.md):** Connect your identity provider and route people to it.
- **[Members](/docs/manage/enterprise/members.md):** Who belongs to the enterprise, and how to disable a login.
- **[Roles](/docs/manage/enterprise/roles.md):** What a member may administer once they have signed in.

---

Source: https://surrealdb.com/docs/manage/enterprise/single-sign-on

# Single sign-on

Connect your company identity provider to SurrealDB Studio. Verify an email domain, add a connection, and route people to it.

Single sign-on lets people at your company sign in to SurrealDB with the credentials they already hold, instead of creating a SurrealDB account of their own.

Sign-in is routed by email domain. A person enters their email address, SurrealDB matches the domain against the connections you have configured, and sends them to your identity provider to authenticate. Your identity provider decides whether they may sign in, and returns them to SurrealDB.

Three things must be in place before anyone is routed, and each has a step below:

1. A **domain** you have verified, which proves your company owns the email addresses.
2. A **connection** to your identity provider, which holds the credentials and endpoints.
3. A **route** from the domain to the connection, which ties the two together.

> [!IMPORTANT]
> A connection without a routing domain does nothing. You must specify which domain(s) you want to have routed to each identity provider connection under each of their settings.

> [!WARNING]
> Signing in through a single sign-on connection creates a new SurrealDB account for each connection, separate from any account the person already has with the same email address. Organisation memberships stay with the original account, so a person who was already a member of an organisation must be invited again before their single sign-on account can access it.

## Step 1: Verify an email domain

A verified domain is what lets single sign-on recognise your company's email addresses. Add the domain your company's addresses use, then prove you own it with a DNS record.

1. Open **Domains** in your enterprise.
2. Select **Add domain**.
3. Enter the domain, for example `example.com`.
4. Select **Add domain** to save it.

![A page in SurrealDB Studio showing the Domains page with a domain that needs to be verified listed.](~/assets/img/surrealdb/manage/enterprise-verify-domain.webp)

Studio then shows a DNS record for the domain. Add that record to your DNS, with the name and value exactly as shown, and then:

1. Wait for the record to propagate.
2. Select **Verify** beside the domain.

The domain is marked as verified when the record is found. If it is not found yet, Studio says so and you can try again. DNS changes can take a while to reach every resolver, so a verification that fails immediately after you add the record is worth repeating later.

Removing a domain stops any connection routing it from matching that domain and people will no longer be routed to your identity provider.

## Step 2: Add a connection

A connection holds the credentials and endpoints of your identity provider. Select your identity provider from the grid below for detailed instructions on how to set it up.

- [Google Workspace](/docs/manage/enterprise/single-sign-on/google-workspace.md) - Google Apps

- [Microsoft Entra ID](/docs/manage/enterprise/single-sign-on/microsoft-entra-id.md) - Azure Active Directory

- [Okta](/docs/manage/enterprise/single-sign-on/okta.md) - Okta Workforce

- [ADFS](/docs/manage/enterprise/single-sign-on/adfs.md) - Active Directory Federation Services

- [PingFederate](/docs/manage/enterprise/single-sign-on/ping-federate.md) - Ping Identity

- [Keycloak](/docs/manage/enterprise/single-sign-on/keycloak.md) - Keycloak SAML

- [SAML](/docs/manage/enterprise/single-sign-on/saml.md) - Generic SAML 2.0

- [OpenID Connect](/docs/manage/enterprise/single-sign-on/openid-connect.md) - Generic OIDC

Use **SAML** or **OpenID Connect** when your identity provider is not listed by name. Most identity providers speak one of the two, and a generic connection reaches the same result as a named one. The named entries exist to label the fields in the words your provider uses.

## Step 3: Route domains to the connection

Routing decides who is sent to the connection.

1. Open the connection from the **Single sign-on** list.
2. Under **Sign-in domains** on the **Sign-on** tab, select the verified domains to route.
3. Select **Save changes**.

People with an email address at those domains are then sent to this connection when they sign in. Only verified domains can be selected, so complete step 1 first.

You can route more than one domain to each connection, which suits a company that holds several email domains. Route each domain to the connection that authenticates it.

## Step 4: Choose the applications

> [!NOTE]
> All applications are disabled by default. You must enable each application you want your users to be able to use before they can be redirected to your identity provider page to sign in there.

A connection can be limited per SurrealDB application rather than all of them. Trying to sign in to an application that doesn't have any identity providers enabled for their domain will push them to sign in with a normal SurrealDB account and **will not** be redirected to your identity provider.

## After single sign-on is working

People at a routed domain are sent to your identity provider whenever they sign in. Their membership then comes from your identity provider, and you grant their access inside SurrealDB with [roles](/docs/manage/enterprise/roles.md).

The [Members](/docs/manage/enterprise/members.md) page will automatically show members that have signed in through an identity provider that you own but will not have any permissions unless they are granted to them. To prevent users from being added automatically, you must manage access externally via your identity provider.

To let your identity provider create, update and deactivate these accounts for you, turn on [SCIM provisioning](/docs/manage/enterprise/scim-provisioning.md) for the connection. A person you deactivate in your identity provider then loses access to SurrealDB too.

## Related pages

- **[Members](/docs/manage/enterprise/members.md):** Who belongs to the enterprise, and how they get there.
- **[Roles](/docs/manage/enterprise/roles.md):** What a member may administer once they have signed in.
- **[SCIM provisioning](/docs/manage/enterprise/scim-provisioning.md):** Keep accounts in step with your identity provider.
- **[Accounts and sign-in](/docs/manage/organisations/sign-in.md):** Signing in without an enterprise.

---

Source: https://surrealdb.com/docs/manage/enterprise/single-sign-on/adfs

# ADFS

Configure Active Directory Federation Services (ADFS) as an identity provider for single sign-on in SurrealDB. Add a WS-Federation relying party trust in ADFS and an ADFS connection in SurrealDB Studio.

Connect Active Directory Federation Services (ADFS) to SurrealDB to allow members of your organisation to sign in with their Active Directory credentials. You add a relying party trust in ADFS Management, then enter your ADFS server's URL in a new connection in SurrealDB Studio.

Before you start, [verify the email domain](/docs/manage/enterprise/single-sign-on.md#step-1-verify-an-email-domain) your company uses. You also need administrator access to your ADFS server.

## Values for ADFS

The relying party trust takes two values from SurrealDB. Studio shows both in the **Set up ADFS** section of the connection form, with a copy button beside each.

| Value                          | Enter in ADFS                                   |
| ------------------------------ | ------------------------------------------------ |
| Relying party trust identifier | `urn:auth0:auth0surrealdb`                       |
| Endpoint URL                   | `https://auth.surrealdb.com/login/callback`      |

## Step 1: Add a relying party trust

1. In ADFS Management, open **Relying Party Trusts** and select **Add Relying Party Trust**.
2. Select **Claims aware**.
3. Select **Enter data about the relying party manually**.
4. Enter a display name, for example `SurrealDB`.
5. Skip the token encryption certificate.
6. Select **Enable support for the WS-Federation Passive protocol**, and enter the endpoint URL from the table above.
7. Add the relying party trust identifier from the table above.
8. Choose an access control policy, for example **Permit everyone**, and finish the wizard.

## Step 2: Add claim rules

SurrealDB needs each person's email address and name.

1. Select the new relying party trust, then **Edit Claim Issuance Policy**.
2. Select **Add Rule**, then **Send LDAP Attributes as Claims**.
3. Set **Attribute store** to **Active Directory**.
4. Map the attributes:

| LDAP attribute          | Outgoing claim type |
| ----------------------- | ------------------- |
| `E-Mail-Addresses`      | E-Mail Address      |
| `Display-Name`          | Name                |
| `User-Principal-Name`   | Name ID             |

5. Select **Finish**, then **OK**.

## Step 3: Expose the federation metadata

SurrealDB reads your server's configuration from its federation metadata endpoint, `/FederationMetadata/2007-06/FederationMetadata.xml`. Make sure this endpoint can be reached from the internet, for example by opening `https://adfs.example.com/FederationMetadata/2007-06/FederationMetadata.xml` from outside your network.

## Step 4: Add the connection in SurrealDB

1. Open **Single sign-on** in your enterprise and add a new connection.
2. Select **ADFS**.
3. Under **Name the connection**, enter a **Display name**. Members see this name on the sign-in screen.
4. Under **Enter the ADFS details**, enter the **ADFS server URL**, for example `https://adfs.example.com`.
5. Under **Route sign-in domains**, select the verified domains to send to this connection. This is optional, and you can change it later.
6. Select **Create connection**.

An ADFS connection has no client secret or certificate to enter. SurrealDB takes the signing certificate from the federation metadata.

## Next steps

If you did not route a domain when you created the connection, [route one now](/docs/manage/enterprise/single-sign-on.md#step-3-route-domains-to-the-connection). Then [choose the applications](/docs/manage/enterprise/single-sign-on.md#step-4-choose-the-applications) that people can sign in to with this connection.

---

Source: https://surrealdb.com/docs/manage/enterprise/single-sign-on/google-workspace

# Google Workspace

Configure Google Workspace as an identity provider for single sign-on in SurrealDB. Create an OAuth client in Google Cloud and add a Google Workspace connection in SurrealDB Studio.

Connect Google Workspace to SurrealDB to allow members of your organisation to sign in with their Google accounts. You create an OAuth client in the Google Cloud console, then enter its details in a new connection in SurrealDB Studio.

Before you start, [verify the email domain](/docs/manage/enterprise/single-sign-on.md#step-1-verify-an-email-domain) your company uses. You also need access to the Google Cloud console for a project in your Google Workspace organisation.

## Step 1: Prepare the Google Cloud project

1. In the Google Cloud console, enable the **Admin SDK API** for your project.
2. Open **Google Auth Platform** and configure the OAuth consent screen with the audience set to **Internal**.
3. Add `surrealdb.com` as an authorised domain.

An internal consent screen limits sign-in to accounts in your Google Workspace organisation.

## Step 2: Create an OAuth client

1. Open **APIs and services**, then **Credentials**.
2. Select **Create credentials**, then **OAuth client ID**.

![The Credentials page in the Google Cloud console, with the Create credentials menu at the top.](~/assets/img/surrealdb/manage/enterprise-sso-google-1.webp)

3. Set **Application type** to **Web application**.
4. Enter a name, for example `SurrealDB`. Only you see this name in the console.
5. Under **Authorised JavaScript origins**, add `https://auth.surrealdb.com`.
6. Under **Authorised redirect URIs**, add `https://auth.surrealdb.com/login/callback`.
7. Select **Create**.

![The Create OAuth client ID form in the Google Cloud console, with the application type, name, JavaScript origin and redirect URI filled in.](~/assets/img/surrealdb/manage/enterprise-sso-google-2.webp)

Google then shows the client ID and the client secret. Copy both, or select **Download JSON** to save them.

> [!IMPORTANT]
> Google shows the client secret only once. After you close the dialogue, you cannot view or download it again, and you must create a new secret.

![The OAuth client created dialogue in the Google Cloud console, showing the client ID and the client secret.](~/assets/img/surrealdb/manage/enterprise-sso-google-3.webp)

It can take from five minutes to a few hours for the new client to take effect.

## Step 3: Add the connection in SurrealDB

1. Open **Single sign-on** in your enterprise and add a new connection.
2. Select **Google Workspace**.
3. Under **Name the connection**, enter a **Display name**. Members see this name on the sign-in screen.
4. Under **Enter the Google Workspace details**, enter:
    - **Google Workspace domain**: the primary domain of your Workspace, for example `example.com`.
    - **Client ID**: the client ID from step 2.
    - **Client secret**: the client secret from step 2.
5. Under **Route sign-in domains**, select the verified domains to send to this connection. This is optional, and you can change it later.
6. Select **Create connection**.

The **Set up Google Workspace** section of the form shows the JavaScript origin, the authorised domain and the redirect URI, with a copy button beside each, if you need them again.

![The New Google Workspace connection form in SurrealDB Studio, with the display name, Google Workspace domain, client ID, client secret and a routed domain filled in.](~/assets/img/surrealdb/manage/enterprise-sso-google-4.webp)

SurrealDB stores the client secret securely and does not show it again.

## Next steps

If you did not route a domain when you created the connection, [route one now](/docs/manage/enterprise/single-sign-on.md#step-3-route-domains-to-the-connection). Then [choose the applications](/docs/manage/enterprise/single-sign-on.md#step-4-choose-the-applications) that people can sign in to with this connection.

---

Source: https://surrealdb.com/docs/manage/enterprise/single-sign-on/keycloak

# Keycloak

Configure Keycloak as an identity provider for single sign-on in SurrealDB. Create a SAML client in a Keycloak realm and add a Keycloak connection in SurrealDB Studio.

Connect Keycloak to SurrealDB to allow members of your organisation to sign in with the accounts in a Keycloak realm. The connection uses SAML. You create a SAML client in your realm, add a connection in SurrealDB Studio, and then finish the client with values from Studio.

Before you start, [verify the email domain](/docs/manage/enterprise/single-sign-on.md#step-1-verify-an-email-domain) your company uses. You also need administrator access to the Keycloak realm.

The Keycloak client ID must be the connection's entity ID, which includes the connection's name. That name exists only after you create the connection, so the setup finishes in Keycloak.

## Step 1: Collect the realm's SAML details

1. In the Keycloak admin console, select your realm.
2. Open **Realm settings**, then **Keys**, and copy the **Certificate** of the `RS256` key.
3. Note the realm's SAML endpoint, which has the form `https://keycloak.example.com/realms/<realm>/protocol/saml`.

The certificate in Keycloak has no header or footer. Add `-----BEGIN CERTIFICATE-----` on the line before it and `-----END CERTIFICATE-----` on the line after it to make it PEM-encoded.

## Step 2: Add the connection in SurrealDB

1. Open **Single sign-on** in your enterprise and add a new connection.
2. Select **Keycloak**.
3. Under **Name the connection**, enter a **Display name**. Members see this name on the sign-in screen.
4. Under **Enter the Keycloak details**, enter:
    - **Sign-in URL**: the realm's SAML endpoint from step 1.
    - **Sign-out URL**: optional. The realm's SAML endpoint also handles sign-out, so you can enter the same URL.
    - **X.509 signing certificate**: the PEM-encoded certificate from step 1.
5. Under **Route sign-in domains**, select the verified domains to send to this connection. This is optional, and you can change it later.
6. Select **Create connection**.

Studio opens the **Sign-on** tab of the new connection.

## Step 3: Create the SAML client

The **Set up Keycloak** section of the **Sign-on** tab shows the values for the client, with a copy button beside each. `<connection>` is the connection's name.

| Value                                 | Form                                                                 |
| ------------------------------------- | -------------------------------------------------------------------- |
| Entity ID                             | `urn:auth0:auth0surrealdb:<connection>`                              |
| Assertion Consumer Service (ACS) URL  | `https://auth.surrealdb.com/login/callback?connection=<connection>`   |

1. In your realm, open **Clients** and select **Create client**.
2. Set **Client type** to **SAML**.
3. Set **Client ID** to the entity ID.
4. Add the ACS URL under **Valid redirect URIs**.
5. Save the client.
6. Under **Client scopes**, map each person's email address to the NameID or to an email attribute. Setting **Name ID format** to **email** on the client's settings does this for the NameID.

## Next steps

If you did not route a domain when you created the connection, [route one now](/docs/manage/enterprise/single-sign-on.md#step-3-route-domains-to-the-connection). Then [choose the applications](/docs/manage/enterprise/single-sign-on.md#step-4-choose-the-applications) that people can sign in to with this connection.

---

Source: https://surrealdb.com/docs/manage/enterprise/single-sign-on/microsoft-entra-id

# Microsoft Entra ID

Configure Microsoft Entra ID (Azure Active Directory) as an identity provider for single sign-on in SurrealDB. Register an application in Entra and add a Microsoft Entra ID connection in SurrealDB Studio.

Connect Microsoft Entra ID (formerly Azure Active Directory) to SurrealDB to allow members of your organisation to sign in with their Microsoft accounts. You register an application in the Microsoft Entra admin center, then enter its details in a new connection in SurrealDB Studio.

Before you start, [verify the email domain](/docs/manage/enterprise/single-sign-on.md#step-1-verify-an-email-domain) your company uses. You also need an Entra account that can register applications.

If your company buys software through Azure, SurrealDB Cloud can be billed on your Azure account as well. See [Azure Marketplace](/docs/manage/organisations/azure-marketplace.md).

## Step 1: Register an application

1. In the Microsoft Entra admin center, open **Entra ID**, then **App registrations**.
2. Select **New registration**.

![The App registrations page in the Microsoft Entra admin center, with App registrations and New registration highlighted.](~/assets/img/surrealdb/manage/enterprise-sso-entra-1.webp)

3. Enter a **Name**, for example `SurrealDB`.
4. Under **Supported account types**, select **Single tenant only**, so that only accounts in your directory can sign in.
5. Under **Redirect URI**, select **Web** as the platform and enter `https://auth.surrealdb.com/login/callback`.
6. Select **Register**.

![The Register an application form in Entra, with the Web redirect URI and the Register button highlighted.](~/assets/img/surrealdb/manage/enterprise-sso-entra-2.webp)

## Step 2: Copy the application ID

Entra opens the **Overview** of the new application. Copy the **Application (client) ID**.

![The Overview page of the SurrealDB application in Entra, with the application (client) ID highlighted.](~/assets/img/surrealdb/manage/enterprise-sso-entra-3.webp)

## Step 3: Check the API permissions

Open **API permissions** and make sure the application has the delegated Microsoft Graph permission `User.Read`. A new registration usually has it already. If it is missing, select **Add a permission**, then **Microsoft Graph**, then **Delegated permissions**, and add `User.Read`.

## Step 4: Create a client secret

1. Open **Certificates & secrets**.
2. On the **Client secrets** tab, select **New client secret**.
3. Enter a **Description**, for example `SurrealDB`, and choose when it **Expires**.
4. Select **Add**.

![The Certificates & secrets page in Entra, with the Add a client secret panel open and the steps numbered.](~/assets/img/surrealdb/manage/enterprise-sso-entra-4.webp)

Copy the secret's **Value**, not its **Secret ID**. Entra shows the value only once, while you are still on the page.

> [!WARNING]
> Sign-in through this connection stops working on the date the client secret expires. Note the date, and create a new secret and update the connection in SurrealDB before then.

## Step 5: Add the connection in SurrealDB

1. Open **Single sign-on** in your enterprise and add a new connection.
2. Select **Microsoft Entra ID**.
3. Under **Name the connection**, enter a **Display name**. Members see this name on the sign-in screen.
4. Under **Enter the Microsoft Entra ID details**, enter:
    - **Directory domain**: a domain of your directory, for example `example.onmicrosoft.com`.
    - **Client ID**: the application (client) ID from step 2.
    - **Client secret**: the secret value from step 4.
5. Under **Route sign-in domains**, select the verified domains to send to this connection. This is optional, and you can change it later.
6. Select **Create connection**.

The **Set up Microsoft Entra ID** section of the form shows the redirect URI, with a copy button beside it, if you need it again.

![The New Microsoft Entra ID connection form in SurrealDB Studio, with the display name, directory domain, client ID, client secret and a routed domain filled in.](~/assets/img/surrealdb/manage/enterprise-sso-entra-5.webp)

SurrealDB stores the client secret securely and does not show it again.

## Step 6: Provision users with SCIM

With SCIM, Entra ID creates, updates and deactivates people's SurrealDB accounts. Skip this step if you do not want Entra ID to manage accounts.

Entra ID provisions users through an enterprise application. This is separate from the app registration that you made in step 1.

In Studio, [turn on SCIM and create a token](/docs/manage/enterprise/scim-provisioning.md) for this connection. Keep the **SCIM base URL** and the token ready.

1. In the Microsoft Entra admin center, open **Enterprise applications**, then select **New application**.
2. Select **Create your own application**.
3. Enter a name, for example `SurrealDB provisioning`.
4. Choose **Integrate any other application you don't find in the gallery**, then select **Create**.
5. Open **Provisioning**, and set **Provisioning Mode** to **Automatic**.
6. Set **Tenant URL** to the SCIM base URL, followed by `?aadOptscim062020`.
7. Set **Secret Token** to the token.
8. Select **Test Connection**, then **Save**.
9. Under **Mappings**, open the mapping for users.
10. Change the source attribute of `externalId` to `objectId`, then save.
11. Assign the same people or groups as the app registration.
12. Set **Provisioning Status** to **On**, then save.

> [!IMPORTANT]
> Map `objectId` to `externalId` before you turn provisioning on. SurrealDB identifies an Entra ID user by their object ID at sign-in. The default mapping sends a different value, and each person then gets a second account the first time they sign in.

The `?aadOptscim062020` suffix makes Entra ID follow the SCIM 2.0 standard for updates. Without it, some changes do not reach SurrealDB.

To try one person before everyone, open **Provisioning** and select **Provision on demand**.

## Next steps

- If you did not route a domain when you created the connection, [route one now](/docs/manage/enterprise/single-sign-on.md#step-3-route-domains-to-the-connection).
- [Choose the applications](/docs/manage/enterprise/single-sign-on.md#step-4-choose-the-applications) that people can sign in to with this connection.
- Read [what SurrealDB does with each SCIM change](/docs/manage/enterprise/scim-provisioning.md#what-surrealdb-does-with-each-change), and how to rotate a token.

---

Source: https://surrealdb.com/docs/manage/enterprise/single-sign-on/okta

# Okta

Configure Okta Workforce as an identity provider for single sign-on in SurrealDB. Create an OIDC web app integration in Okta, add an Okta connection in SurrealDB Studio, open SurrealDB from the Okta dashboard, sign people out with Universal Logout, and provision users with SCIM.

Connect Okta to SurrealDB to allow members of your organisation to sign in with their Okta credentials. You create an OpenID Connect app integration in the Okta Admin Console, then enter its details in a new connection in SurrealDB Studio.

When you finish, people can sign in to SurrealDB through Okta. You can also let them open SurrealDB from their Okta dashboard, sign them out with Universal Logout, and let Okta create and deactivate their accounts with SCIM.

Before you start, [verify the email domain](/docs/manage/enterprise/single-sign-on.md#step-1-verify-an-email-domain) your company uses. You also need an Okta account that can create app integrations.

## Step 1: Create an app integration

1. In the Okta Admin Console, open **Applications and Resources**, then **Applications**.
2. Select **Create App Integration**.

![The Applications page in the Okta Admin Console, with the Create App Integration button highlighted.](~/assets/img/surrealdb/manage/enterprise-sso-okta-1.webp)

3. Under **Sign-in method**, select **OIDC - OpenID Connect**.
4. Under **Application type**, select **Web Application**.
5. Select **Next**.

![The Create and deploy private app integrations dialogue in Okta, with OIDC - OpenID Connect and Web Application selected.](~/assets/img/surrealdb/manage/enterprise-sso-okta-2.webp)

## Step 2: Configure the integration

1. Enter an **App integration name**, for example `SurrealDB`.
2. Under **Grant type**, make sure **Authorization Code** is selected.
3. Under **Sign-in redirect URIs**, enter `https://auth.surrealdb.com/login/callback`.
4. Under **Assignments**, choose who can use the integration. You can also assign people or groups later, from the integration's **Assignments** tab.
5. Select **Save**.

![The New Web App Integration form in Okta, with the sign-in redirect URI highlighted.](~/assets/img/surrealdb/manage/enterprise-sso-okta-3.webp)

Only the people and groups assigned to the integration can sign in to SurrealDB through it.

## Step 3: Copy the client credentials

Okta opens the **General** tab of the new integration.

1. Under **Client Credentials**, copy the **Client ID**.
2. Under **Client secrets**, copy the secret. Select the eye icon to show it, or the copy icon beside it.

![The General tab of the SurrealDB app integration in Okta, with the client ID and the client secret highlighted.](~/assets/img/surrealdb/manage/enterprise-sso-okta-4.webp)

You also need your Okta domain, for example `example.okta.com`. It is the domain of your Okta sign-in page, without `-admin`.

## Step 4: Add the connection in SurrealDB

1. Open **Single sign-on** in your enterprise and add a new connection.
2. Select **Okta**.
3. Under **Name the connection**, enter a **Display name**. Members see this name on the sign-in screen.
4. Under **Enter the Okta details**, enter:
    - **Okta domain**: your Okta domain, for example `example.okta.com`.
    - **Client ID**: the client ID from step 3.
    - **Scopes**: optional. Leave it empty to request `openid profile email`.
    - **Client secret**: the client secret from step 3.
5. Under **Route sign-in domains**, select the verified domains to send to this connection. This is optional, and you can change it later.
6. Select **Create connection**.

![The New Okta connection form in SurrealDB Studio, with the display name, Okta domain, client ID, client secret and a routed domain filled in.](~/assets/img/surrealdb/manage/enterprise-sso-okta-5.webp)

SurrealDB stores the client secret securely and does not show it again.

The connection's **Sign-on** tab, under **Set up Okta**, shows the values for the next steps, each with a copy button.

## Step 5: Show SurrealDB on the Okta dashboard

The Okta dashboard shows a tile for each app a person is assigned. For the tile to open SurrealDB, Okta needs the address that starts a sign-in.

1. In Studio, copy the **Initiate login URI** from the connection's **Sign-on** tab.
2. In Okta, open the `SurrealDB` app integration, then the **General** tab.
3. Edit **General Settings**.
4. Set **Login initiated by** to **Either Okta or App**.
5. Turn on **Display application icon to users**.
6. Paste the value into **Initiate login URI**.
7. Select **Save**.

When a person selects the tile, Okta sends them to SurrealDB, and SurrealDB signs them in through this connection. They do not have to enter their email address.

## Step 6: Sign people out with Universal Logout

Okta Universal Logout signs a person out of every app at once, for example when Okta detects a risk or an admin ends their sessions. SurrealDB supports it through Global Token Revocation (GTR). When Okta calls the revocation URL, SurrealDB ends that person's sessions and revokes their refresh tokens.

Universal Logout needs Okta Identity Threat Protection in your Okta org. Skip this step if you do not use it.

1. In Studio, copy the **Global token revocation URL** from the connection's **Sign-on** tab.
2. In Okta, open the `SurrealDB` app integration, and edit its logout settings.
3. Select **Okta system or admin initiates logout**.
4. Paste the value as the logout endpoint.
5. Keep the authentication type as **Signed JWT**.
6. Set the **Subject format** to **Issuer and Subject Identifier**.
7. Save the settings.

SurrealDB needs no other configuration. It checks each request with the same keys that it uses to check sign-ins from your Okta org.

## Step 7: Provision users with SCIM

With SCIM, Okta creates, updates and deactivates people's SurrealDB accounts. Skip this step if you do not want Okta to manage accounts.

Okta does not allow SCIM on an OpenID Connect app integration, so SCIM uses a second app integration. People sign in with the first app, and Okta provisions their accounts with the second. The second app does not sign anyone in, and you hide it from the dashboard.

In Studio, [turn on SCIM and create a token](/docs/manage/enterprise/scim-provisioning.md) for the connection. Keep the **SCIM base URL** and the token ready.

### Create the provisioning app

1. In the Okta Admin Console, open **Applications**, then select **Create App Integration**.
2. Select **SWA - Secure Web Authentication**, then **Next**.
3. Enter an **App name**, for example `SurrealDB provisioning`.
4. Set **App's login page URL** to `https://studio.surrealdb.com`.
5. Turn on **Do not display application icon to users**.
6. Select **Finish**.
7. On the **General** tab, edit **App Settings**, set **Provisioning** to **SCIM**, then select **Save**.

### Connect the provisioning app to SurrealDB

1. Open the **Provisioning** tab, then **Integration**, and select **Edit**.
2. Set **SCIM connector base URL** to the SCIM base URL.
3. Set **Unique identifier field for users** to `userName`.
4. Under **Supported provisioning actions**, select **Push New Users** and **Push Profile Updates**.
5. Set **Authentication Mode** to **HTTP Header**, and paste the token into **Authorization**.
6. Select **Test Connector Configuration**, then **Save**.
7. Open **Provisioning**, then **To App**, and select **Edit**.
8. Turn on **Create Users**, **Update User Attributes** and **Deactivate Users**, then select **Save**.

### Assign people

Assign the same people or groups to both app integrations. A person assigned only to the provisioning app gets an account but cannot sign in. A person assigned only to the sign-in app signs in, but Okta does not manage their account.

Assign one Okta group to both apps, so the two assignments stay the same.

Okta sends the Okta user ID as the SCIM `externalId`, which is the same ID that the sign-in app sends. You do not have to change any attribute mapping.

## Next steps

- If you did not route a domain when you created the connection, [route one now](/docs/manage/enterprise/single-sign-on.md#step-3-route-domains-to-the-connection).
- [Choose the applications](/docs/manage/enterprise/single-sign-on.md#step-4-choose-the-applications) that people can sign in to with this connection.
- Read [what SurrealDB does with each SCIM change](/docs/manage/enterprise/scim-provisioning.md#what-surrealdb-does-with-each-change), and how to rotate a token.

---

Source: https://surrealdb.com/docs/manage/enterprise/single-sign-on/openid-connect

# OpenID Connect

Configure a generic OpenID Connect (OIDC) identity provider for single sign-on in SurrealDB. Register a web application with the authorization code flow and add an OpenID Connect connection in SurrealDB Studio.

Connect any identity provider that supports OpenID Connect (OIDC) to SurrealDB. Use this connection when your provider is not listed by name. You register a web application in your identity provider, then enter its details in a new connection in SurrealDB Studio.

Before you start, [verify the email domain](/docs/manage/enterprise/single-sign-on.md#step-1-verify-an-email-domain) your company uses.

## Step 1: Register a web application

1. In your identity provider, register a web application that uses the **authorization code** flow and has a client secret.
2. Add `https://auth.surrealdb.com/login/callback` as a redirect URI. Some providers call it a callback URL or a sign-in redirect URI.
3. Make sure the ID token includes each person's email address and name. Most providers include them when the `email` and `profile` scopes are requested.
4. Copy the client ID and the client secret.

You also need the provider's discovery URL. It ends in `/.well-known/openid-configuration`, for example `https://idp.example.com/.well-known/openid-configuration`. SurrealDB reads the provider's endpoints and signing keys from it.

## Step 2: Add the connection in SurrealDB

1. Open **Single sign-on** in your enterprise and add a new connection.
2. Select **OpenID Connect**.
3. Under **Name the connection**, enter a **Display name**. Members see this name on the sign-in screen.
4. Under **Enter the OpenID Connect details**, enter:
    - **Discovery URL**: the provider's discovery URL from step 1.
    - **Client ID**: the client ID from step 1.
    - **Scopes**: optional. Leave it empty to request `openid profile email`.
    - **Client secret**: the client secret from step 1.
5. Under **Route sign-in domains**, select the verified domains to send to this connection. This is optional, and you can change it later.
6. Select **Create connection**.

The **Set up OpenID Connect** section of the form shows the redirect URI, with a copy button beside it, if you need it again. SurrealDB stores the client secret securely and does not show it again.

## Step 3: Provision users with SCIM

If your identity provider supports SCIM 2.0, it can create, update and deactivate people's SurrealDB accounts. Skip this step if you do not want it to manage accounts.

In Studio, [turn on SCIM and create a token](/docs/manage/enterprise/scim-provisioning.md) for this connection. Keep the **SCIM base URL** and the token ready.

Give your provider's SCIM or provisioning settings these values:

| Setting | Value |
| --- | --- |
| Base URL, or tenant URL | The SCIM base URL |
| Authentication | Bearer token |
| Token | The token from Studio |
| Objects to manage | Users |
| `externalId` | The same value that your provider sends as the `sub` claim of the ID token |

> [!IMPORTANT]
> SurrealDB identifies a person on an OpenID Connect connection by the SCIM `externalId`. It must be the same as the `sub` claim that your provider sends at sign-in. If the two are different, the person gets a second account the first time they sign in.

## Next steps

If you did not route a domain when you created the connection, [route one now](/docs/manage/enterprise/single-sign-on.md#step-3-route-domains-to-the-connection). Then [choose the applications](/docs/manage/enterprise/single-sign-on.md#step-4-choose-the-applications) that people can sign in to with this connection.

---

Source: https://surrealdb.com/docs/manage/enterprise/single-sign-on/ping-federate

# PingFederate

Configure PingFederate as an identity provider for single sign-on in SurrealDB. Create a SAML 2.0 SP connection in PingFederate and add a PingFederate connection in SurrealDB Studio.

Connect PingFederate to SurrealDB to allow members of your organisation to sign in with their Ping Identity credentials. The connection uses SAML 2.0. You start an SP connection in PingFederate, add a connection in SurrealDB Studio, and then finish the SP connection with values from Studio.

Before you start, [verify the email domain](/docs/manage/enterprise/single-sign-on.md#step-1-verify-an-email-domain) your company uses. You also need administrator access to your PingFederate server.

The values PingFederate needs from SurrealDB include the connection's name, which exists only after you create the connection. That is why the setup finishes in PingFederate.

## Step 1: Start an SP connection

1. In PingFederate, create an SP connection that uses **Browser SSO** with the **SAML 2.0** protocol.
2. Send each person's email address as the NameID, or as an email attribute.
3. Export the signing certificate that PingFederate uses for this connection, in PEM format.

If PingFederate asks for the ACS URL or the entity ID before you can save, enter a temporary value such as `https://auth.surrealdb.com/login/callback` and replace it in step 3.

## Step 2: Add the connection in SurrealDB

1. Open **Single sign-on** in your enterprise and add a new connection.
2. Select **PingFederate**.
3. Under **Name the connection**, enter a **Display name**. Members see this name on the sign-in screen.
4. Under **Enter the PingFederate details**, enter:
    - **PingFederate server URL**: the base URL of your server, for example `https://ping.example.com`.
    - **X.509 signing certificate**: the PEM-encoded certificate, starting with `-----BEGIN CERTIFICATE-----`.
5. Under **Route sign-in domains**, select the verified domains to send to this connection. This is optional, and you can change it later.
6. Select **Create connection**.

Studio opens the **Sign-on** tab of the new connection.

## Step 3: Finish the SP connection

The **Set up PingFederate** section of the **Sign-on** tab shows the values for the SP connection, with a copy button beside each. `<connection>` is the connection's name.

| Value                                 | Form                                                                 |
| ------------------------------------- | -------------------------------------------------------------------- |
| Assertion Consumer Service (ACS) URL  | `https://auth.surrealdb.com/login/callback?connection=<connection>`   |
| Entity ID                             | `urn:auth0:auth0surrealdb:<connection>`                              |
| Metadata URL                          | `https://auth.surrealdb.com/samlp/metadata?connection=<connection>`   |

In the SP connection, set the partner's entity ID to the entity ID and the ACS endpoint to the ACS URL, or import both from the metadata URL. Then activate the SP connection.

## Next steps

If you did not route a domain when you created the connection, [route one now](/docs/manage/enterprise/single-sign-on.md#step-3-route-domains-to-the-connection). Then [choose the applications](/docs/manage/enterprise/single-sign-on.md#step-4-choose-the-applications) that people can sign in to with this connection.

---

Source: https://surrealdb.com/docs/manage/enterprise/single-sign-on/saml

# SAML

Configure a generic SAML 2.0 identity provider for single sign-on in SurrealDB. Create a SAML application, add a SAML connection in SurrealDB Studio, then enter its ACS URL and entity ID.

Connect any identity provider that supports SAML 2.0 to SurrealDB. Use this connection when your provider is not listed by name. You create a SAML application in your identity provider, add a connection in SurrealDB Studio, and then finish the application with values from the connection.

Before you start, [verify the email domain](/docs/manage/enterprise/single-sign-on.md#step-1-verify-an-email-domain) your company uses.

A SAML connection is set up in this order because the values your identity provider needs from SurrealDB include the connection's name, which exists only after you create the connection.

## Step 1: Create a SAML application

1. In your identity provider, create a SAML 2.0 application.
2. Send each person's email address as the NameID, or as an email attribute.
3. Copy the provider's single sign-on URL and its X.509 signing certificate.

Some providers ask for an ACS URL and an entity ID before they save the application. If yours does, enter a temporary value such as `https://auth.surrealdb.com/login/callback` and replace it in step 3.

## Step 2: Add the connection in SurrealDB

1. Open **Single sign-on** in your enterprise and add a new connection.
2. Select **SAML**.
3. Under **Name the connection**, enter a **Display name**. Members see this name on the sign-in screen.
4. Under **Enter the SAML details**, enter:
    - **Sign-in URL**: the provider's single sign-on URL, for example `https://idp.example.com/sso/saml`.
    - **Sign-out URL**: optional. The provider's single logout URL.
    - **X.509 signing certificate**: the PEM-encoded certificate, starting with `-----BEGIN CERTIFICATE-----`.
5. Under **Route sign-in domains**, select the verified domains to send to this connection. This is optional, and you can change it later.
6. Select **Create connection**.

Studio opens the **Sign-on** tab of the new connection.

## Step 3: Finish the SAML application

The **Set up SAML** section of the **Sign-on** tab shows three values, with a copy button beside each. `<connection>` is the connection's name, which is also shown on the **General** tab.

| Value                                 | Also called                                  | Form                                                               |
| ------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------ |
| Assertion Consumer Service (ACS) URL  | Reply URL, single sign-on URL                | `https://auth.surrealdb.com/login/callback?connection=<connection>` |
| Entity ID                             | Audience, SP entity ID                       | `urn:auth0:auth0surrealdb:<connection>`                            |
| Metadata URL                          |                                              | `https://auth.surrealdb.com/samlp/metadata?connection=<connection>` |

Enter the ACS URL and the entity ID in the SAML application. If your provider can import service provider metadata, give it the metadata URL instead, which contains both.

Copy the values from Studio rather than building them by hand, so the connection name is exact.

## Step 4: Provision users with SCIM

If your identity provider supports SCIM 2.0, it can create, update and deactivate people's SurrealDB accounts. Skip this step if you do not want it to manage accounts.

In Studio, [turn on SCIM and create a token](/docs/manage/enterprise/scim-provisioning.md) for this connection. Keep the **SCIM base URL** and the token ready.

Most providers set up SCIM on the same app as SAML, in a section called **Provisioning**. Give it these values:

| Setting | Value |
| --- | --- |
| Base URL, or tenant URL | The SCIM base URL |
| Authentication | Bearer token |
| Token | The token from Studio |
| Objects to manage | Users |
| `userName` | The same value that your provider sends as the name ID in the SAML assertion |

> [!IMPORTANT]
> SurrealDB identifies a person on a SAML connection by the SCIM `userName`. It must be the same as the name ID that your provider sends at sign-in. If the two are different, the person gets a second account the first time they sign in.

## Next steps

If you did not route a domain when you created the connection, [route one now](/docs/manage/enterprise/single-sign-on.md#step-3-route-domains-to-the-connection). Then [choose the applications](/docs/manage/enterprise/single-sign-on.md#step-4-choose-the-applications) that people can sign in to with this connection.

---

Source: https://surrealdb.com/docs/manage/instances

# Instances

What a SurrealDB Cloud instance is. How the Start and Scale plans differ, and where each operational task is documented.

An instance is a SurrealDB deployment that SurrealDB runs for you.

That covers provisioning, high availability, patching, backups, and version upgrades. You choose a plan, a size, and a region, then connect your application.

Every instance belongs to an [organisation](/docs/manage/organisations.md), which also holds members, usage, and billing. You operate instances from [SurrealDB Studio](https://studio.surrealdb.com), or from a terminal with [`surreal cloud`](/docs/explore/cli/cloud/instances.md). To run and operate the server yourself, see [Self-hosted](/docs/manage/self-hosted.md).

[Open SurrealDB Studio](https://studio.surrealdb.com)

## Plans

Two plans are available. The plan you choose at deploy time fixes the topology of the instance.

| Plan | Topology | How it grows | Storage | Suited to |
| --- | --- | --- | --- | --- |
| **Start** | A single node | Vertically, by moving to a larger instance type | A dedicated disk per instance | Development, staging, and workloads that fit on one node |
| **Scale** | A cluster of three nodes or more | Vertically and horizontally, by using larger nodes or more nodes | Distributed storage shared across the cluster | Production that must survive the loss of a node |

Start offers three families of instance type. **Free** is for trying SurrealDB out, **Burstable** is for low-traffic and intermittent workloads, and **General purpose** is for steady production traffic. Scale uses General purpose nodes.

The plan also gates some options. Configurable [backup frequency](/docs/manage/instances/backups.md) is available on Scale instances only.

Current capacity ceilings and prices are on the [pricing page](https://surrealdb.com/pricing). The plan cards in the deploy flow show what applies to your organisation. For the topology behind each plan, see [Architecture](/docs/manage/instances/architecture.md).

## Where instances live

The **Instances** section of SurrealDB Studio lists every instance in the organisation you are viewing. Each card shows the SurrealDB version and the region. Use the search box and the **Version** and **Type** filters to narrow a long list.

![The Instances page of SurrealDB Studio for the Acme Corp organisation, listing three instance cards: api-production on SurrealDB 3.2.4, and api-staging and analytics-eu on SurrealDB 3.2.1. All three are in AWS Europe (Ireland). The page also has a search box, Version and Type filters, and a Deploy new instance button.](~/assets/img/surrealdb/manage/instances-list.webp)

Selecting an instance opens its own workspace. That workspace holds a [dashboard](/docs/manage/instances/monitoring.md) of resource use, the schema and query views, and **Settings**. Settings is where [configuration](/docs/manage/instances/configure.md), [capabilities](/docs/manage/instances/configure.md#capabilities), [versions](/docs/manage/instances/versions-and-upgrades.md), compute, and [backups](/docs/manage/instances/backups.md) live.

`surreal cloud instance list` prints the same list in a terminal, and `--json` makes it scriptable. See [surreal cloud instances](/docs/explore/cli/cloud/instances.md).

## Lifecycle

You control the lifecycle of an instance with four actions.

| Action | Effect | Where |
| --- | --- | --- |
| **Deploy** | Provisions the instance on the chosen plan, type, and region | [Create an instance](/docs/manage/instances/create.md) |
| **Resize** | Changes the instance type or the storage capacity in place | [Scaling](/docs/manage/instances/scaling.md) |
| **Pause** | Stops compute and usage billing. Data and configuration are kept, and the instance is unreachable until you resume it | [Configure an instance](/docs/manage/instances/configure.md#pause-an-instance) |
| **Delete** | Destroys the instance and everything stored in it, with no way to recover it | [Configure an instance](/docs/manage/instances/configure.md#delete-an-instance) |

You cannot change an instance name after deployment. Pick a name a colleague will still recognise in six months, such as `api-production` rather than `db1`.

## Topics

- **[Create an instance](/docs/manage/instances/create.md):** plan, instance type, region, version, name, and starting data.
- **[Configure an instance](/docs/manage/instances/configure.md):** capabilities, compute, storage, pausing, and deletion.
- **[Connect to an instance](/docs/manage/instances/connect.md):** SurrealDB Studio, the CLI, SDKs, and HTTP.
- **[Architecture](/docs/manage/instances/architecture.md):** single-node and multi-node topologies.
- **[Scaling](/docs/manage/instances/scaling.md):** matching compute and storage to the workload.
- **[High availability](/docs/manage/instances/high-availability.md):** what survives a node failure on each plan.
- **[Backups and recovery](/docs/manage/instances/backups.md):** automated snapshots, retention, and restore.
- **[Import and export](/docs/manage/instances/import-and-export.md):** moving data with `surreal db export` and `surreal db import`.
- **[Versions and upgrades](/docs/manage/instances/versions-and-upgrades.md):** changing the SurrealDB version.
- **[Monitoring](/docs/manage/instances/monitoring.md):** the instance dashboard, metrics, and logs.
- **[Network access](/docs/manage/instances/network-access.md):** which outbound destinations queries may reach.
- **[Private connectivity](/docs/manage/instances/private-connectivity.md):** AWS PrivateLink and instance access modes.

## Related sections

- **[Organisations](/docs/manage/organisations.md):** accounts, members, roles, usage, billing, and support.
- **[surreal cloud](/docs/explore/cli/cloud.md):** the management-plane command-line tool.
- **[Observability](/docs/manage/observability.md):** the full metric, audit-log, and slow-query reference.
- **[Self-hosted](/docs/manage/self-hosted.md):** running SurrealDB on your own infrastructure instead.

---

Source: https://surrealdb.com/docs/manage/instances/architecture

# Architecture

The topologies behind the Start and Scale plans. Single-node and multi-node, and what each means for growth and recovery.

This page explains the two topologies an instance can run on, and what each one means for growth and recovery.

The plan you choose when you [create an instance](/docs/manage/instances/create.md) selects the topology.

Instances build on [the layered architecture of SurrealDB](/docs/learn/data-models/architecture.md), which separates storage from compute. That separation lets an instance grow, replicate, and recover without you sharding or resharding the database yourself.

## Start: single node

The **Start** plan provides one compute node backed by a dedicated disk. Compute and storage are sized together and resized together, and the node holds the only copy of the working data.

<img src="~/assets/img/image/cloud/light/start-single-node-light.png" darkSrc="~/assets/img/image/cloud/start-single-node.png" alt="Diagram of the single-node topology: one SurrealDB compute node paired with its own dedicated disk-based storage volume." />

This topology suits development, staging, and production workloads that fit on one node and can tolerate a short recovery window. It scales vertically: as load grows, you move to a larger instance type. See [Scaling](/docs/manage/instances/scaling.md).

Because there is a single node, there is no failover inside the instance. [Backups](/docs/manage/instances/backups.md) are the recovery mechanism. See [High availability](/docs/manage/instances/high-availability.md) for what that means in practice.

## Scale: multi-node cluster

The **Scale** plan runs a cluster of three compute nodes or more over distributed storage. Each node runs in a different availability zone within the region, backed by its own disk.

<img src="~/assets/img/image/cloud/light/enterprise-multi-node-light.png" darkSrc="~/assets/img/image/cloud/enterprise-multi-node.png" alt="Diagram of the multi-node topology: three SurrealDB compute nodes, each in its own availability zone and backed by its own storage disk, split into a compute layer and a storage layer." />

Data is replicated continuously between nodes, and every node serves reads, writes, and queries. The storage layer handles replication, quorum consensus, and distributed transactions. A query therefore does not need to know which node holds which data.

The practical consequences are:

- **Node loss is survivable.** A quorum of the remaining nodes keeps serving. Three nodes is the minimum, because three nodes leave a majority when one node is out for a failure, an upgrade, or maintenance.
- **Query capacity scales horizontally.** Adding nodes adds throughput. Vertical scaling is bounded by the largest available instance type.
- **Self-hosting the same topology is more work.** Running it yourself means operating Kubernetes, storage replication, patching, backups, and upgrade orchestration. Scale covers that work for you. Compare [Self-hosted deployment models](/docs/manage/self-hosted/deployment-models.md).

> [!NOTE]
> Several Scale capabilities are still on the roadmap: object-storage backing, cross-region replication, automatic sharding, and read replicas. Object-storage backing keeps hot data on disk and cold data in object storage, which in turn enables instant database branching. See [Pricing](https://surrealdb.com/pricing) and [Roadmap](https://surrealdb.com/roadmap) for current availability.

## Related pages

- **[Scaling](/docs/manage/instances/scaling.md):** resizing nodes and adding them.
- **[High availability](/docs/manage/instances/high-availability.md):** what each topology survives.
- **[Self-hosted deployment models](/docs/manage/self-hosted/deployment-models.md):** running the same topologies on your own infrastructure.

---

Source: https://surrealdb.com/docs/manage/instances/backups

# Backups and recovery

Automated snapshots and retention tiers. Restoring a snapshot into a new instance.

Instances are backed up for you: snapshots run on a schedule and are kept under a retention policy.

There is nothing to install or script, and the same mechanism applies on Start and Scale. A restore creates a new instance from a chosen snapshot.

Snapshots live inside the platform. They are not files you can download, copy to your own object storage, or open locally. For a portable copy, see [Import and export](/docs/manage/instances/import-and-export.md).

## Where backups are managed

Open the instance in [SurrealDB Studio](https://studio.surrealdb.com), then go to **Instance settings → Backups**.

![The Backups tab of instance settings in SurrealDB Studio, listing one automatic backup taken on 17 August 2026 at 5:58 PM with a Create from selected button, a Backup frequency section set to once a day with a note that configurable frequency is available on Scale instances, and a Retention policy section listing daily, weekly, and monthly snapshot tiers.](~/assets/img/surrealdb/manage/instance-backups.webp)

The tab has three parts:

- **Available backups:** the list of snapshots you can restore from.
- **Backup frequency:** how often a new snapshot is taken.
- **Retention policy:** how long each snapshot is kept.

## Frequency and retention

Automated snapshots run daily by default. Configurable backup frequency is available on Scale instances. On other instance types the schedule is fixed.

Retention is tiered, and each tier is kept for its own period.

| Tier | When the snapshot is taken | Retention setting |
| --- | --- | --- |
| **Daily** | Every day | The number of days of daily snapshots kept |
| **Weekly** | Each Sunday | The number of weeks of weekly snapshots kept |
| **Monthly** | The first of each month | The number of months of monthly snapshots kept |

Which tiers you can change depends on the instance type. Tiers that are fixed on your plan are shown but not editable.

Longer retention lets you recover from further back and increases storage cost. Set it against your actual recovery objectives rather than taking the maximum. If you need more retention than the plan allows, [contact support](/docs/manage/organisations/support.md).

## Taking a backup before a risky change

Take an on-demand snapshot before anything you might want to undo, such as a major version upgrade, a schema migration, or a bulk delete. On-demand snapshots appear in the same list as scheduled ones and follow the same retention.

`surreal cloud instance backup` triggers one from a terminal, which is what you want in a deployment pipeline that runs a migration. See [surreal cloud instances](/docs/explore/cli/cloud/instances.md).

## Restoring from a snapshot

A restore produces a **new instance**. The original is left untouched, and there is no in-place restore.

1. Open **Instance settings → Backups** on the source instance.
2. Select the snapshot you want.
3. Select **Create from selected**.

That opens the [deploy flow](/docs/manage/instances/create.md) with the snapshot already chosen as the starting data. You can reach the same result from the other direction: start a new deploy and pick **Restore from backup** as the data source.

Because the result is a new instance, cutting over is an application change. The new instance has its own name and endpoint. Verify the data before you repoint traffic, then [delete](/docs/manage/instances/configure.md#delete-an-instance) or [pause](/docs/manage/instances/configure.md#pause-an-instance) the old instance.

Three constraints apply to a restore:

- The new instance must be in the **same region** as the source.
- Its **storage size** must be at least as large as the size of the source.
- Its **SurrealDB version** must be compatible with the snapshot. The deploy form only offers versions that are.

## What backups do not cover

- **They are not portable.** You cannot download a snapshot. It is not an archival format, not a way to seed a local development database, and not a route to another provider. Use [`surreal db export`](/docs/reference/cli/db/commands/export.md) for those cases. See [Import and export](/docs/manage/instances/import-and-export.md).
- **They are not cross-region disaster recovery.** A snapshot restores into the region it was taken in. Recovering into a different region needs a logical export and import.
- **They are not a substitute for testing a restore.** Restore into a throwaway instance periodically and check that the data is what you expect. That is the only way to know the recovery path works.

## Related pages

- **[Import and export](/docs/manage/instances/import-and-export.md):** portable data with `surreal db export` and `surreal db import`.
- **[Versions and upgrades](/docs/manage/instances/versions-and-upgrades.md):** snapshot first, then upgrade.
- **[High availability](/docs/manage/instances/high-availability.md):** what redundancy covers and what backups cover.

---

Source: https://surrealdb.com/docs/manage/instances/configure

# Configure an instance

Instance settings in SurrealDB Studio. Capabilities, compute and storage, pausing, and deletion.

Change the settings of a running instance from **Instance settings** in [SurrealDB Studio](https://studio.surrealdb.com).

To open the settings, select the instance, then select **Instance settings** on the dashboard or **Settings** in the sidebar.

Settings is organised into tabs.

| Tab | What it controls |
| --- | --- |
| **General** | The instance id, and the [pause](#pause-an-instance) and [delete](#delete-an-instance) actions |
| **Capabilities** | [What the instance permits at runtime](#capabilities): scripting, endpoints, functions, and network access |
| **Version** | The SurrealDB release the instance runs. See [Versions and upgrades](/docs/manage/instances/versions-and-upgrades.md) |
| **Compute** | [The instance type and the storage capacity](#compute-and-storage) |
| **Backups** | Snapshot frequency, retention, and restore. See [Backups and recovery](/docs/manage/instances/backups.md) |

The **Databases** and **Data** tabs work on the contents of the instance rather than on the instance itself.

## Capabilities

Capabilities decide what the database engine will do at runtime. They are the main security control on an instance: a capability that is switched off cannot be re-enabled by a query, a user, or an access method. The underlying model is described in [Capabilities](/docs/learn/security/authorization/capabilities.md).

![The Capabilities tab of instance settings in SurrealDB Studio, subtitled What this instance permits at runtime, with import and export buttons for the capability configuration as a JSON file, and a granular configuration list showing Scripting and Guest Access switched off, RPC methods, HTTP endpoints, Functions, and Arbitrary queries set to Allowed, Network access set to Denied, and Insecure storable closures switched off.](~/assets/img/surrealdb/manage/instance-capabilities.webp)

Two kinds of control appear in the list.

**Switches** turn a single capability on or off:

- **Scripting:** whether embedded JavaScript functions run.
- **Guest access:** whether unauthenticated clients can perform any operation.
- **Insecure storable closures:** whether closures can be stored in records.

**Allow and deny lists** apply to capabilities with many members: RPC methods, HTTP endpoints, functions, and [network access](/docs/manage/instances/network-access.md). Each list starts from a default and takes exceptions:

- **Allowed by default:** everything is permitted, and you list what to deny.
- **Denied by default:** nothing is permitted, and you list what to allow.

For a production instance, deny by default and allow only what the application uses. The cost is that you must extend the list whenever the application changes.

> [!IMPORTANT]
> **Network access** governs outbound HTTP requests made from inside queries, and is denied by default. It does not control who can reach the instance. See [Network access](/docs/manage/instances/network-access.md) for the pattern syntax, and [Private connectivity](/docs/manage/instances/private-connectivity.md) for inbound access modes.

The **Import** and **Export** buttons read and write the whole capability configuration as a JSON file. Use the export to keep staging and production aligned, or to hold the configuration in version control. `surreal cloud instance capabilities` does the same from a terminal. See [surreal cloud instances](/docs/explore/cli/cloud/instances.md).

## Compute and storage

The **Compute** tab holds the instance type and the storage capacity. The active type is marked, and the Free, Burstable, and General purpose families are on separate tabs.

![The Compute tab of instance settings in SurrealDB Studio, showing the Free instance type marked as active with 0.25 core vCPU and 512 MB memory, and a Storage capacity section explaining that storage expansion is unavailable for free instances with an Upgrade instance type button.](~/assets/img/surrealdb/manage/instance-compute.webp)

To change the instance type:

1. Open **Instance settings → Compute**.
2. Select the instance type you want.
3. Select **Save changes**.

To add more disk storage:

1. Open **Instance settings → Compute**.
2. Set the value under **Storage capacity**.
3. Select **Save changes**.

Changing the instance type moves the instance to a larger or smaller size and rebills accordingly. Storage can grow but never shrink, and you can increase the disk size once every six hours.

Change the type and the storage in separate saves. The storage slider goes up to the maximum of the type the instance is on now, so the maximum of a new type is only available after the type change is saved. A type change also sets the storage to the starting storage of the new type, and the six-hour wait starts again from that change. The [Storage limits on Start](/docs/manage/instances/scaling.md#storage-limits-on-start) page lists the starting and maximum storage of each type.

> [!IMPORTANT]
> Storage expansion is unavailable on free instances. Move to a paid instance type to change storage capacity. The move happens in place, and the instance keeps its data.

Expect a brief reconnect while the instance restarts on the new size. For when to resize and what to watch afterwards, see [Scaling](/docs/manage/instances/scaling.md).

## Pause an instance

Pausing stops compute without losing data. A paused instance is unreachable until you resume it, its data and configuration are kept, and usage is not billed while it is paused.

To pause an instance:

1. Open **Instance settings → General**.
2. Select **Pause instance**.
3. Confirm the action.

Pausing suits environments with predictable idle periods, such as a staging instance overnight or a demo between engagements.

Before you pause, check that nothing depends on the instance being reachable. Connection pools, scheduled jobs, and health checks fail against a paused instance rather than queue against it.

Unused free instances are paused automatically after seven days.

![The General tab of instance settings in SurrealDB Studio, showing the instance id with a copy button, a Pause instance panel explaining that a paused instance is entirely unreachable until resumed while its data is kept and usage is not billed, and a Delete instance panel warning that permanently removing an instance cannot be undone.](~/assets/img/surrealdb/manage/instance-settings.webp)

Resuming brings the instance back on the same configuration and the same endpoint.

## Delete an instance

Deleting destroys the instance and everything stored in it. You cannot undo it, and support cannot recover a deleted instance.

> [!WARNING]
> Do not treat the snapshots of an instance as the safety net for deleting it. If you might need the data later, [restore a backup into a new instance](/docs/manage/instances/backups.md) or take a [logical export](/docs/manage/instances/import-and-export.md) first. Confirm the export loads cleanly before you delete.

To delete an instance:

1. Open **Instance settings → General**.
2. Select **Delete instance**.
3. Confirm the action.

`surreal cloud instance pause`, `surreal cloud instance resume`, and `surreal cloud instance delete` are the command-line equivalents.

## The instance id

The **General** tab also shows the instance id. SurrealDB support may ask for it when investigating an issue. The id identifies the instance unambiguously where the name does not, because names are only unique within an organisation.

## Next steps

- **[Connect to an instance](/docs/manage/instances/connect.md):** SurrealDB Studio, the CLI, SDKs, and HTTP.
- **[Monitoring](/docs/manage/instances/monitoring.md):** confirm a configuration change had the effect you expected.
- **[Backups and recovery](/docs/manage/instances/backups.md):** snapshot frequency, retention, and restore.

---

Source: https://surrealdb.com/docs/manage/instances/connect

# Connect to an instance

The four routes to a SurrealDB Cloud instance. SurrealDB Studio, the CLI, a client SDK, and the HTTP API, plus where to find the connection details.

An instance exposes one endpoint, and every client reaches it the same way.

What differs is the tool you use and how you authenticate.

| Route | Use it for |
| --- | --- |
| [SurrealDB Studio](/docs/manage/instances/connect/via-studio.md) | Running queries and browsing data in a graphical interface. |
| [CLI](/docs/manage/instances/connect/via-cli.md) | An interactive SurrealQL shell, and one-off queries from a terminal or a script. |
| [SDK](/docs/manage/instances/connect/via-sdk.md) | Application code in Rust, JavaScript, Python, .NET, PHP, and the other supported languages. |
| [HTTP](/docs/manage/instances/connect/via-http.md) | cURL, Postman, and any HTTP client, including bulk import. |

## Before you begin

You need:

- An account. See [Accounts and sign-in](/docs/manage/organisations/sign-in.md).
- A running instance. See [Create an instance](/docs/manage/instances/create.md).
- A **namespace** and a **database** to work in. SurrealDB needs both to know where a query runs. See [system structure](/docs/learn/data-models/architecture.md#system-structure).
- Credentials, unless you connect from SurrealDB Studio, which authenticates with your own session.

## Finding the connection details

The **Connect** menu of the instance in [SurrealDB Studio](https://studio.surrealdb.com) holds the endpoint. It also produces a ready-made command or snippet for each route, with the details of your instance already filled in. Copying from there avoids transcription mistakes in the hostname.

`surreal cloud instance endpoint` prints the same endpoint, and `surreal cloud instance token` issues a token for it. Both are useful in scripts and in CI, where opening a browser is not an option. See [surreal cloud instances](/docs/explore/cli/cloud/instances.md).

---

Source: https://surrealdb.com/docs/manage/instances/connect/via-cli

# Via CLI

Open an interactive SurrealQL shell against an instance with surreal db sql, and where to get the token.

Open an interactive SurrealQL shell against an instance with the [SurrealDB CLI](/docs/reference/cli/db.md).

Use the shell for ad-hoc queries, for checking a schema definition, and for scripting a one-off change.

## Prerequisites

Install the CLI. See the [installation guide](/docs/running/installation.md). It is a single executable and does not need a local server.

## Open a shell

[`surreal db sql`](/docs/reference/cli/db/commands/sql.md) connects to the endpoint and opens a prompt:

```bash title="Connect to an instance"
surreal db sql \
  --endpoint wss://<endpoint> \
  --ns main --db main \
  --token <token>
```

The **Connect** menu of the instance in [SurrealDB Studio](https://studio.surrealdb.com) generates this command with your endpoint and token filled in.

![The Connect menu in SurrealDB Studio showing the ready-made surreal sql command for an instance, with the endpoint, namespace, database, and token already populated.](~/assets/img/image/cloud/open-in-cli.png)

The `--token` value is a JSON Web Token that authenticates the session. Treat it as a credential, because it grants whatever the authenticating user or access method grants. Keep it out of shell history and out of committed scripts. Where a script needs a token, mint one at runtime with `surreal cloud instance token` rather than store it.

You can authenticate with `--user` and `--pass` instead if the instance has a system user defined. See [DEFINE USER](/docs/reference/query-language/statements/define/user.md).

## Next steps

- **[SurrealDB CLI](/docs/reference/cli/db.md):** every command and flag.
- **[surreal cloud](/docs/explore/cli/cloud/instances.md):** the management-plane CLI. `surreal cloud instance sql` opens the same shell against a named instance without you supplying an endpoint, and runs `surreal db sql` to do it.

---

Source: https://surrealdb.com/docs/manage/instances/connect/via-http

# Via HTTP

Query an instance with cURL or any HTTP client, and the request size limits that apply per endpoint.

Instances serve the [HTTP API](/docs/reference/rest-api.md), so anything that speaks HTTP can query them.

That includes cURL, Postman, a serverless function, and a language without an SDK.

To get the URL, open the instance in [SurrealDB Studio](https://studio.surrealdb.com), select **Connect**, then select **HTTP cURL**.

![The Connect menu in SurrealDB Studio with HTTP cURL selected, showing a generated cURL command containing the instance endpoint and its namespace and database headers.](~/assets/img/image/cloud/open-in-http.png)

> [!NOTE]
> The generated command is cURL, but the URL and headers work in any HTTP client. Paste them into Postman or your own code unchanged.

## Send a query

Post SurrealQL to `/sql`. Name the namespace and database in headers, and authenticate with a bearer token:

```bash title="Run a query over HTTP"
curl -X POST "https://<endpoint>/sql" \
  -H "Surreal-NS: main" \
  -H "Surreal-DB: main" \
  -H "Authorization: Bearer <token>" \
  -H "Accept: application/json" \
  -d "SELECT * FROM person LIMIT 10;"
```

The response is a JSON array with one result object per statement in the request.

## Request size limits

Instances run the SurrealDB defaults for request size, and those defaults differ per endpoint.

| Endpoint | Limit |
| --- | --- |
| `/sql` | 1 MiB |
| `/rpc` | 4 MiB |
| `/import` | 4 GiB |
| WebSocket message | 128 MiB |

A request over the cap is rejected with `413 Payload Too Large`. The 1 MiB cap on `/sql` is the one large queries tend to reach first.

If a payload does not fit, you have three options:

- **Use an SDK.** Most, including [Rust](/docs/reference/rust.md) and [JavaScript](/docs/reference/javascript.md), default to the WebSocket engine and its 128 MiB per message.
- **Stay on HTTP but change endpoint.** Send the query through [`POST /rpc`](/docs/reference/rest-api/rpc-protocol.md), which accepts 4 MiB.
- **Load bulk data through import.** [`POST /import`](/docs/reference/rest-api/http-protocol.md#import) accepts up to 4 GiB per request. See [Import and export](/docs/manage/instances/import-and-export.md#size-limits).

The full table is in [request size limits](/docs/reference/rest-api/http-protocol.md#request-size-limits). A self-hosted server sets these caps with environment variables. Instances run the defaults.

---

Source: https://surrealdb.com/docs/manage/instances/connect/via-sdk

# Via SDK

Connect to an instance from application code, including the credentials each SDK needs and how to create them.

Reach an instance from application code through one of the [SurrealDB SDKs](/docs/languages.md).

This page covers what an SDK needs to connect and how to create it. The SDK reference for your language covers the API itself.

To get a snippet with the endpoint already filled in, select **Connect** on the instance in [SurrealDB Studio](https://studio.surrealdb.com), then select your language.

![The Connect menu in SurrealDB Studio with the SDK option selected, showing a generated connection snippet containing the instance endpoint.](~/assets/img/image/cloud/open-in-sdk.png)

## What an SDK needs

An SDK needs three things:

- **The endpoint** of the instance, from the **Connect** menu.
- **A namespace and a database**, which tell SurrealDB where the query runs. A prompt to create them appears at the top of the dashboard if the instance has none. See [system structure](/docs/learn/data-models/architecture.md#system-structure).
- **Credentials**, unless the connection is anonymous. Studio authenticates with your own session, but application code needs a user or an access method defined on the instance.

## Create credentials

> [!NOTE]
> This step applies when the SDK calls `signin` on connection. Skip it if the application authenticates some other way. See [access methods](/docs/reference/query-language/statements/define/access.md) and [system users](/docs/reference/query-language/statements/define/user.md).

1. Open the **Authentication** panel of the instance in SurrealDB Studio.
2. Select **+** in the **Root Authentication** section.
3. Choose the kind of credential you need.
4. Set the token duration and the session duration.

Two kinds of credential are offered:

- **New system user:** a username, a password, and a role that determines what the user may do. This is what a backend service typically uses.
- **New access method:** a named method whose type determines how clients authenticate through it. This is what record-level and end-user authentication uses.

Keep both durations short enough that a leaked token expires on its own.

![The Root Authentication dialog in SurrealDB Studio, offering a new system user with a username, password, and role, or a new access method with a name and type, each with configurable token and session durations.](~/assets/img/image/cloud/create-root-user.png)

Root credentials reach everything on the instance. For namespace-scoped or database-scoped authentication, and for record-level access, create the namespace and database first. Then define the user or the access method at that level.

## Connect

Every SDK follows the same shape. `connect` takes the endpoint, then you select the namespace and database, then you sign in. The examples below use root credentials.

> [!NOTE]
> With a non-root user, the sign-in call also needs the `access` details for the access method you defined. The [SDK reference](/docs/languages.md) for your language shows the exact call.

**Rust**

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::any;
use surrealdb::opt::auth::Root;
use tokio;
use chrono::{DateTime, Utc};

#[derive(Serialize, Deserialize)]
struct Project {
	name: String,
	description: String,
	status: String,
	priority: String,
	tags: Vec<String>,
	created_at: DateTime<Utc>,
}

// Open a connection
let db = any::connect("wss://<INSTANCE_ENDPOINT>").await?;

// Select namespace and database
db.use_ns("DEMO namespace").use_db("DEMO database").await?;

// Authenticate
db.signin(Root {
	username: "<username>",
	password: "<password>",
}).await?;

// Create a record
let project = Project {
	name: "SurrealDB Dashboard".to_string(),
	description: "Admin interface for SurrealDB".to_string(),
	status: "in_progress".to_string(),
	priority: "high".to_string(),
	tags: vec!["typescript".to_string(), "react".to_string(), "database".to_string()],
	created_at: Utc::now(),
};

db.create("project").content(project).await?;
```

**JavaScript**

```js
import { Surreal, Table } from "surrealdb";

const db = new Surreal();

// Open a connection and authenticate
await db.connect("wss://<INSTANCE_ENDPOINT>", {
	namespace: "DEMO namespace",
	database: "DEMO database",
	authentication: {
		username: "<username>",
		password: "<password>",
	}
});

// Create record
await db.create(new Table("project"), {
	name: "SurrealDB Dashboard",
	description: "Admin interface for SurrealDB",
	status: "in_progress",
	priority: "high",
	tags: ["typescript", "react", "database"],
	created_at: new Date(),
});

// Select all records in project table
console.log(await db.select(new Table("project")));

await db.close();
```

**Python**

```py
from surrealdb import Surreal
from datetime import datetime, timezone
from surrealdb import RecordID

# Open a connection
with Surreal("wss://<INSTANCE_ENDPOINT>") as db:

	# Select namespace and database
	db.use("DEMO namespace", "DEMO database")

	# Authenticate
	db.signin({
		"username": "<username>",
		"password": "<password>",
	})

	# Create a record
	db.create(RecordID("project", "1"), {
		"name": "SurrealDB Dashboard",
		"description": "Admin interface for SurrealDB",
		"status": "in_progress",
		"priority": "high",
		"tags": ["typescript", "react", "database"],
		"created_at": datetime.now(timezone.utc),
	})

	# Select a specific record
	print(db.select(RecordID("project", "1")))
```

**.NET**

```csharp
using SurrealDb.Net;
using SurrealDb.Net.Models.Auth;

const string TABLE = "project";

using var db = new SurrealDbClient("wss://<INSTANCE_ENDPOINT>/rpc");

// Select namespace and database
await db.Use("DEMO namespace", "DEMO database");

// Authenticate
await db.SignIn(new RootAuth { Username = "<username>", Password = "<password>" });

// Create record
var project = new Project
{
	Name = "SurrealDB Dashboard",
	Description = "Admin interface for SurrealDB",
	Status = "in_progress",
	Priority = "high",
	Tags = new[] { "typescript", "react", "database" },
	CreatedAt = DateTime.UtcNow,
};

await db.Create(TABLE, project);
```

**PHP**

```php
$db = new \Surreal\Surreal();

// Open a connection
$db->connect("wss://<INSTANCE_ENDPOINT>/rpc", [
	"namespace" => "DEMO namespace",
	"database" => "DEMO database",
]);

// Authenticate
$db->signin([
	"username" => "<username>",
	"password" => "<password>",
]);

// Create a record
$db->create("project", [
	"name" => "SurrealDB Dashboard",
	"description" => "Admin interface for SurrealDB",
	"status" => "in_progress",
	"priority" => "high",
	"tags" => ["typescript", "react", "database"],
	"created_at" => new DateTime(),
]);
```

## Next steps

- **[SDK reference](/docs/languages.md):** the full API for each language, including live queries and transactions.
- **[Connect via HTTP](/docs/manage/instances/connect/via-http.md):** for languages without an SDK, and the request size limits that apply.
- **[Authentication](/docs/learn/security/authentication/summary.md):** choosing between system users, access methods, and record-level access.

---

Source: https://surrealdb.com/docs/manage/instances/connect/via-studio

# Via SurrealDB Studio

Select a namespace and database, then query an instance from the SurrealDB Studio query view.

Query an instance from [SurrealDB Studio](https://studio.surrealdb.com) without setting up a client.

Studio is already connected to your instances, so signing in is the whole authentication step. Use it to explore data, write a query, and check the effect of a schema change.

## Select a namespace and database

Queries run against a specific database inside a specific namespace, so select both before you run anything.

1. Open the instance and go to the query view.
2. Select the **Namespace** control and choose a namespace. If the instance has none, the same control offers to create one.
3. Select the **Database** control and choose a database, or create one.

![The namespace and database selectors in the SurrealDB Studio query view, each showing a dropdown of the namespaces and databases available on the instance.](~/assets/img/image/cloud/namespace-database.png)

An instance created with the **Empty** starting-data option has neither, so this is the first thing to do after you deploy one.

## Run a query

With both selected, the editor runs SurrealQL against that database. Results appear beneath the query, and records created by a statement show up immediately in the explorer.

![The SurrealDB Studio query view with a SurrealQL statement in the editor and its result set displayed in the panel below.](~/assets/img/image/cloud/querying-instance.png)

## Next steps

- **[SurrealDB Studio](/docs/explore/studio.md):** the rest of the interface, including the schema designer, the explorer, and keyboard shortcuts.
- **[SurrealQL](/docs/reference/query-language.md):** the query language reference.
- **[Connect via SDK](/docs/manage/instances/connect/via-sdk.md):** moving from exploration to application code.

---

Source: https://surrealdb.com/docs/manage/instances/create

# Create an instance

Deploy an instance from SurrealDB Studio. Choose a plan, instance type, region, version, name, starting data, and storage.

Deploy an instance from [SurrealDB Studio](https://studio.surrealdb.com) in seven steps.

Deployment takes a few minutes. You can change everything afterwards except the instance name and the region.

## Before you begin

You need:

- An account. See [Accounts and sign-in](/docs/manage/organisations/sign-in.md).
- An organisation to hold the instance. See [Organisations](/docs/manage/organisations.md).

## 1. Select a plan

1. Open the organisation that will hold the instance.
2. Go to **Instances**.
3. Select **Deploy new instance**.

The first step of the flow asks for a plan.

| Plan | Topology | How it scales |
| --- | --- | --- |
| **Start** | A single node | Vertically only |
| **Scale** | A fault-tolerant cluster of three nodes or more | Vertically and horizontally |

Each card lists the capacity ceilings and the starting price for that plan.

![Step one of the deploy flow in SurrealDB Studio, titled Select a plan, showing two cards side by side: Start, designed for applications that require vertical scalability, with a single node, up to 512 GB storage, 16 vCPU, and 64 GB memory; and Scale, designed for applications that require fault tolerance and horizontal scalability, with multiple nodes, up to 1 PB cluster storage, 64 vCPU per node, and 256 GB memory per node.](~/assets/img/surrealdb/manage/instance-select-plan.webp)

The plan fixes the topology of the instance. To move a workload from Start to Scale, deploy a Scale instance, then [restore a backup](/docs/manage/instances/backups.md) or [import an export](/docs/manage/instances/import-and-export.md) into it. Choose **Scale** if the workload must survive the loss of a node. See [High availability](/docs/manage/instances/high-availability.md).

Select **Configure instance** on the plan you want.

## 2. Choose an instance type

Instance types set the vCPU, the memory, and the baseline storage for each node. Three families are available.

| Family | Behaviour | Suited to |
| --- | --- | --- |
| **Free** | A fixed, no-cost instance with a small storage allowance | Trying SurrealDB out |
| **Burstable** | Full CPU in short bursts, throttled under sustained load | Testing, starter projects, and low-traffic applications |
| **General purpose** | Sustained CPU and memory with no throttling | Production traffic and workloads at scale |

Select **View more configurations** to expand the shortlist to every size available on the plan.

![Step two of the deploy flow in SurrealDB Studio, titled Configure your single-node instance, showing three instance type cards: Burstable small with 0.5 core and 1 GB memory, General purpose medium with 1 core and 4 GB memory, and General purpose xlarge with 4 cores and 16 GB memory. Below the cards is an Instance details form with the name api-production, the region AWS Europe (Ireland), and the version SurrealDB 3.2.4.](~/assets/img/surrealdb/manage/instance-configure.webp)

## 3. Enter the instance details

Complete the three fields in the **Instance details** form.

**Name** identifies the instance in the organisation and in every connection string. You cannot change it later, so use a name that stays meaningful: `api-production`, `api-staging`, or `analytics-eu`.

**Region** determines the latency to your clients and where the data resides, and it sets which cloud provider runs the instance. You cannot change it later either. Deploy into the same region as your application stack where you can. Instances are currently available in:

| Provider | Region |
| --- | --- |
| AWS | US East (N. Virginia) |
| AWS | US West (Oregon) |
| AWS | Europe (Ireland) |
| AWS | AP South (Mumbai) |
| Azure | Europe (Germany) |
| Azure | US East 2 (Virginia) |
| Azure | South America (Brazil) |

The region selector shows what is available to your organisation, and that list is the authoritative one. [AWS PrivateLink](/docs/manage/instances/private-connectivity.md) is offered in a subset of AWS regions.

**Version** sets the SurrealDB release the instance runs. Take the latest stable release unless you have a reason not to. You can upgrade an older release later from **Settings → Version**. See [Versions and upgrades](/docs/manage/instances/versions-and-upgrades.md).

## 4. Choose the starting data

A new instance can start empty or with data already in place.

| Option | Result |
| --- | --- |
| **Empty** | An instance with no namespaces or databases |
| **Demo dataset** | A sample dataset and example queries to explore |
| **Upload from file** | Data loaded from a file you supply |
| **Restore from backup** | A copy of an existing instance, taken from one of its [backup snapshots](/docs/manage/instances/backups.md) |

**Restore from backup** is how a restore works. The snapshot becomes a new instance and never overwrites the original. The backup must come from the same region, and the storage size must be at least as large as the source.

## 5. Set the storage capacity

Storage is provisioned per instance, and the available range depends on the plan and the instance type. On Start, the range grows with the memory of the type: see the [Storage limits on Start](/docs/manage/instances/scaling.md#storage-limits-on-start) page for more details.

You can increase storage later but never decrease it. Start close to what you need rather than over-provisioning. See [Scaling](/docs/manage/instances/scaling.md).

## 6. Review and deploy

1. Select **Continue to checkout** to show the order for review.
2. Enter payment details if the organisation does not have them yet. See [Billing](/docs/manage/organisations/billing.md).
3. Confirm the order.

The instance is provisioned within a few minutes.

## 7. Verify the instance

1. Go to **Instances** in the organisation.
2. Confirm the new instance appears in the list.
3. Open the instance and check that the [dashboard](/docs/manage/instances/monitoring.md) reports the version, region, and instance type you chose.
4. [Connect to the instance](/docs/manage/instances/connect.md) and run a query.

If the instance started empty, it has no namespace or database yet. Create both before you run a query. See [Connect via SurrealDB Studio](/docs/manage/instances/connect/via-studio.md).

## Deploying from the command line

`surreal cloud instance create` performs the same deploy, which is what you want in CI or in a provisioning script. See [surreal cloud instances](/docs/explore/cli/cloud/instances.md).

## Next steps

- **[Connect to an instance](/docs/manage/instances/connect.md):** SurrealDB Studio, the CLI, SDKs, and HTTP.
- **[Configure an instance](/docs/manage/instances/configure.md):** capabilities, compute, and storage.
- **[Monitoring](/docs/manage/instances/monitoring.md):** confirm the instance is healthy.

---

Source: https://surrealdb.com/docs/manage/instances/high-availability

# High availability

What a Start instance and a Scale cluster each survive. How to plan for the failures they do not.

This page explains what each instance topology survives, and how to plan for the failures it does not.

High availability means the database stays reachable and consistent when a component fails or during planned maintenance. How much of that you get depends on the topology, which the plan sets when you [create the instance](/docs/manage/instances/create.md).

## Start: single node

A **Start** instance runs on one compute node with a dedicated disk. That is simple and inexpensive, and it means there is no failover inside the instance. If the node, the disk, or the host underneath fails, connections drop until the instance recovers.

What you get instead is managed recovery. Patching, snapshots, and host replacement are handled for you, and [backups](/docs/manage/instances/backups.md) are the mechanism for recovering from data loss or a corrupted state.

Plan around that:

- **Treat backups and a rehearsed restore as the recovery path**, not in-instance redundancy. Until you have restored a backup, you cannot be sure it works.
- **Give clients retry and reconnect logic.** Resizes, version upgrades, and host recovery all produce a short window where connections fail.
- **Accept a recovery window** in the service level you promise upstream, or move to Scale.

## Scale: multi-node cluster

A **Scale** cluster runs at least three SurrealDB nodes over distributed storage, with each node in a different availability zone. Availability is a property of the architecture rather than something added on top:

- **Node loss is survivable.** The storage layer coordinates replication and quorum consensus, so the cluster keeps serving while a node is unavailable. Three separate single-node instances would not do this, because they would be three independent databases.
- **Maintenance is not an outage.** Version upgrades roll through nodes one at a time. Query capacity dips while each node restarts, but the cluster stays reachable. See [Versions and upgrades](/docs/manage/instances/versions-and-upgrades.md).
- **Three nodes are the minimum.** Three nodes leave a majority when one is out for a failure, an upgrade, or maintenance. Two nodes would not.

Scale still needs backups. Quorum protects against infrastructure failure, not against a mistaken `DELETE` or a bad migration. Those replicate to every node exactly as intended.

## Comparing the two

| Requirement | Start | Scale |
| --- | --- | --- |
| Tolerate a recovery window during host failure | Yes | Yes |
| Survive the loss of a node without manual failover | No | Yes |
| Stay reachable through a version upgrade | No. Expect a reconnect | Yes, through a rolling upgrade |
| Scale query throughput horizontally | No. Vertical resize only | Yes, by adding nodes |
| Recover from an application-level mistake | Restore a backup | Restore a backup |

## Beyond a single region

Both topologies run within one region. Node-level redundancy does not cover an outage that affects a whole region.

If your recovery objectives extend that far, plan for it at the application layer and rehearse a restore into a second region. A [backup restores into the region it came from](/docs/manage/instances/backups.md), so cross-region recovery uses a [logical export](/docs/manage/instances/import-and-export.md). Cross-region replication is on the roadmap. See [Architecture](/docs/manage/instances/architecture.md).

## Related pages

- **[Architecture](/docs/manage/instances/architecture.md):** the topology behind each plan.
- **[Scaling](/docs/manage/instances/scaling.md):** moving between sizes and node counts.
- **[Backups and recovery](/docs/manage/instances/backups.md):** snapshots, retention, and restore.

---

Source: https://surrealdb.com/docs/manage/instances/import-and-export

# Import and export

Move data in and out with surreal db export and surreal db import. Covers size limits and partial-import behaviour.

Export and import move data as SurrealQL text, so you get a portable copy of a database.

Use them for four cases:

- Migrating from a self-hosted server.
- Seeding a local development database.
- Moving data between instances or regions.
- Keeping an archive outside the platform.

[Backups](/docs/manage/instances/backups.md) cover the recover-this-instance case instead, and you cannot download them.

Both commands come from the [SurrealDB CLI](/docs/reference/cli/db.md), which you install once and point at any instance. The connection URL, namespace, and database are on the **Connect** menu of the instance in [SurrealDB Studio](https://studio.surrealdb.com).

## Exporting

[`surreal db export`](/docs/reference/cli/db/commands/export.md) writes the contents of one database to a `.surql` file:

```bash title="Export a database to a file"
surreal db export \
  --endpoint wss://<endpoint> \
  --user root --pass <password> \
  --ns main --db main \
  ./api-production.surql
```

The file contains the SurrealQL statements needed to rebuild the schema and the records. It is therefore readable, diffable, and safe to store in an artefact repository.

An export covers a single namespace and database. Export each one you need separately.

By default an export contains everything in the database. To export only some tables or kinds of definition, such as the schema without its records, see [Choosing what to export](/docs/reference/cli/db/commands/export.md#choosing-what-to-export). In SurrealDB Studio, the **Export database** dialog offers the same choices with checkboxes and shows the matching `surreal db export` command.

## Importing

[`surreal db import`](/docs/reference/cli/db/commands/import.md) replays that file into a target database:

```bash title="Import a file into an instance"
surreal db import \
  --endpoint wss://<endpoint> \
  --user root --pass <password> \
  --ns main --db main \
  ./api-production.surql
```

The target namespace and database do not have to match the source. That is how you promote a staging dataset into a differently named database.

## Size limits

An import is accepted up to **4 GiB per request**. The limit applies to the whole request rather than to each statement, so you must split a larger file and import the parts in sequence.

The other endpoints have lower caps: 1 MiB on `/sql` and 4 MiB on `/rpc`. That is why bulk loading goes through import rather than through a large query. See [request size limits](/docs/reference/rest-api/http-protocol.md#request-size-limits) for the full table.

A self-hosted server sets these caps with environment variables. Instances run the defaults.

## Partial imports

An import is applied statement by statement as the file is parsed, and each statement commits as it goes. No single transaction wraps the file, so **a failure partway through leaves the statements that already ran in place**.

That matters for retries. Rerunning the same file against a half-populated database can produce duplicate records or conflicting definitions.

Import into a fresh namespace or database, verify the result, then switch the application over. If something fails, drop the target and start again from a known-empty state.

## Related pages

- **[Backups and recovery](/docs/manage/instances/backups.md):** snapshots and in-platform restore.
- **[Migrating to SurrealDB](/docs/build/migrating.md):** moving from another database.
- **[surreal cloud instances](/docs/explore/cli/cloud/instances.md):** `surreal cloud instance import` and `surreal cloud instance export` wrap the same operations against an instance.

---

Source: https://surrealdb.com/docs/manage/instances/monitoring

# Monitoring

The instance dashboard, metrics groups, and log filters. How each relates to the full observability surface in SurrealDB Studio.

Every instance reports its own resource use and activity, with no agent to install and no scrape target to configure.

The at-a-glance view is the instance **Dashboard**. The detail is under **Metrics & logs** in the instance sidebar of [SurrealDB Studio](https://studio.surrealdb.com).

## The instance dashboard

Opening an instance lands on its dashboard, which states what the instance is and what it is currently doing. The header reports the version, region, instance type, and organisation.

![The dashboard for the api-production instance in SurrealDB Studio, showing it running SurrealDB 3.2.4 in aws-euw1 on the Free type in the Acme Corp organisation, with cards for 512 MB memory, 0.25 vCPUs of single-node compute, 21.64 MB of 1 GB storage used, and version v3.2.4, above Activity charts plotting memory usage, compute usage, and network ingress and egress over the last hour.](~/assets/img/surrealdb/manage/instance-dashboard.webp)

The four cards across the top are the numbers to check first. **Storage** is the one to watch over time. It shows consumption against the provisioned limit, and a disk that reaches its limit stops accepting writes. See [Scaling](/docs/manage/instances/scaling.md) for raising it.

Below the cards, **Activity** plots resource use over a period you select. The default is one hour. Longer windows show trends rather than spikes.

## Metrics

**Metrics & logs** groups the signals into three.

| Group | What it reports |
| --- | --- |
| **System** | Compute and memory use of the instance |
| **Connections** | HTTP and RPC request volume |
| **Network traffic** | Network ingress and egress |

Each group takes a time range: the last hour, twelve hours, day, week, or month. Short ranges show what is happening now. Longer ranges are what you need before you decide to resize, because a resize should answer a trend rather than a single spike.

Read the groups together rather than separately. Rising connection counts with flat compute point at connection handling in the application. Rising compute with flat connections points at the queries themselves.

## Logs

The **Logs** view lists instance activity: startups and shutdowns, imports, version changes, and other lifecycle events. Filters narrow the list by:

- **Level:** the severity of the entry.
- **Source:** the component that emitted it.
- **Message:** free text within the entry.

Log entries stay queryable for a retention period. To keep them longer, ship them to your own log store rather than rely on the console view. That is what compliance requirements and correlation with other systems usually need.

> [!NOTE]
> These are activity logs for instance-level events. Audit logs, which record who ran which statement against which resource, are not yet available for instances. The record shape and the pipeline are documented under [audit logging](/docs/manage/observability/audit-logging.md) for deployments that have them.

## What to watch

These signals most often warrant action:

- **Storage** climbing towards the provisioned limit.
- **Compute or memory** sustained near the instance-type ceiling under ordinary traffic.
- **Connection counts** saturating, or clients being refused.
- **Error-rate spikes** in the logs that correlate with a deployment or a schema change.

Sustained pressure on the first three is a [scaling](/docs/manage/instances/scaling.md) signal. A spike that coincides with a change you made is usually a query or schema problem, and a resize hides it rather than fixes it.

## From the command line

`surreal cloud instance metrics` and `surreal cloud instance logs` print the same data for scripting or for feeding an external monitor. `surreal cloud instance watch` follows the state of an instance during a change. See [surreal cloud instances](/docs/explore/cli/cloud/instances.md).

## The wider observability surface

This view presents the same telemetry a self-hosted deployment scrapes directly. When you need the full vocabulary, see the [Observability](/docs/manage/observability.md) section, and the [metrics reference](/docs/manage/observability/metrics.md) in particular. Those pages cover every metric family and label, the environment variables that control them, the audit-log record shape, and the slow-query pipeline.

---

Source: https://surrealdb.com/docs/manage/instances/network-access

# Network access

Control which outbound destinations queries may reach. Uses the network access capability and its allow and deny patterns.

Network access controls which external destinations an instance may reach **from inside a query**.

The functions it governs are the [HTTP functions](/docs/reference/query-language/functions/database-functions/http.md) that call an API, fetch a document, or post to a webhook as part of a statement.

> [!IMPORTANT]
> This is outbound access. It has nothing to do with who can reach the instance. For inbound connectivity, see [Private connectivity](/docs/manage/instances/private-connectivity.md) and its `public`, `private`, and `dual` access modes.

The capability is **denied by default**. Until you allow something, `http::get` and its siblings fail rather than reach the network.

The default is strict because an instance with unrestricted outbound access lets a query author send data anywhere. Keep the allowed set to the shortest list that makes the application work.

> [!NOTE]
> Configurable network access requires SurrealDB `>=2.1.8, <2.2.0`, `>=2.2.6, <2.3.0`, or `>=2.3.6`. On an earlier release, [upgrade the instance](/docs/manage/instances/versions-and-upgrades.md) before you configure the rules.

## Configuring the rules

Network access is one of the [instance capabilities](/docs/manage/instances/configure.md#capabilities), so it lives with the rest of them.

1. Open the instance in [SurrealDB Studio](https://studio.surrealdb.com).
2. Go to **Instance settings → Capabilities**.
3. Find **Network access** in the granular configuration list.
4. Choose its default: **Allowed by default** or **Denied by default**.
5. Add the exceptions. Under a denied default, list the domains, IP addresses, and ranges to permit. Under an allowed default, list what to block.
6. Save the change to apply it to the instance.

![The Capabilities tab of instance settings in SurrealDB Studio, with Network access set to Denied in the granular configuration list, alongside the Scripting, Guest Access, RPC methods, HTTP endpoints, Functions, and Arbitrary queries controls.](~/assets/img/surrealdb/manage/instance-capabilities.webp)

At the same specificity, a deny rule prevails over an allow rule. A host you have denied therefore stays unreachable even when a broader allow rule would otherwise cover it. That is how you permit the public endpoints of an API while keeping an internal hostname on the same domain out of reach.

## Choosing the patterns

Prefer named destinations over ranges. `api.stripe.com` documents its own intent. A wide CIDR block does not, and it grants reachability to whatever moves into that block later.

Review the list when the application changes. Rules outlive the feature that needed them, so remove any allowance that no current feature uses.

You can export both the default and the exception lists as JSON from the same tab, which is how you keep staging and production consistent. `surreal cloud instance capabilities` reads and writes the same configuration from a terminal. See [surreal cloud instances](/docs/explore/cli/cloud/instances.md).

## Additional resources

- **[Capabilities](/docs/learn/security/authorization/capabilities.md):** the full capability model and every flag it covers.
- **[Configure an instance](/docs/manage/instances/configure.md#capabilities):** the other capabilities on the same tab.
- **[HTTP functions](/docs/reference/query-language/functions/database-functions/http.md):** the functions this capability governs.

---

Source: https://surrealdb.com/docs/manage/instances/private-connectivity

# Private connectivity

Reach an instance from your AWS VPC over AWS PrivateLink. Set the public, private, or dual access mode.

[AWS PrivateLink](https://aws.amazon.com/privatelink/) gives your AWS VPC a private network path to an instance that never crosses the public internet.

By default an instance is reachable over the public internet, protected by the authentication and authorisation defined inside it.

This page covers inbound connectivity, which is how clients reach the instance. For outbound requests made from queries, see [Network access](/docs/manage/instances/network-access.md).

> [!NOTE]
> PrivateLink is an enterprise feature. Onboarding is a manual process handled by the SurrealDB team and needs coordinated setup on both sides, so you cannot enable it from SurrealDB Studio. [Contact us](/contact) to start.

## Prerequisites

You need:

- An AWS-hosted instance in a supported region. PrivateLink is an AWS service, so it does not apply to instances deployed on Azure.
- An AWS account with a VPC.
- AWS IAM credentials, as a user, role, or policy, permitted to create interface VPC endpoints.

## Supported regions

PrivateLink is currently available in **AWS US West (Oregon)** only. More regions will be added over time.

## How it works

After your organisation is onboarded, you create an [interface VPC endpoint](https://docs.aws.amazon.com/vpc/latest/privatelink/create-interface-endpoint.html) in your AWS account. The endpoint receives a private IP address inside your VPC. Traffic between your application and the instance then stays on the AWS private network, with no internet gateway, no NAT gateway, and no public hop.

Public and private paths use separate DNS namespaces and separate infrastructure.

| Path | Hostname | Reachable from |
| --- | --- | --- |
| Public | `*.aws-usw2.surreal.cloud` | The internet |
| PrivateLink | `*.privatelink.aws-usw2.surreal.cloud` | Inside your VPC only |

The two paths are isolated at the network level rather than filtered, so a misconfiguration cannot expose a private instance publicly. The PrivateLink hostname does not resolve outside the VPC.

## Access modes

Each instance in a PrivateLink-enabled region has an access mode.

| Mode | Behaviour |
| --- | --- |
| `public` | Reachable over the public internet only. The default for every instance. |
| `private` | Reachable over PrivateLink only. No public hostname is assigned. |
| `dual` | Reachable over both paths. |

Use `dual` while you migrate: bring the private path up, move clients across, confirm that nothing still connects publicly, then switch to `private`.

> [!NOTE]
> Existing instances are `public`. The `private` and `dual` modes only appear after your organisation has been onboarded for a region.

Choose the access mode when you [create an instance](/docs/manage/instances/create.md), after you select a supported region. To change it afterwards, open the instance in SurrealDB Studio and use the network settings in **Instance settings**.

## Onboarding

Enabling PrivateLink involves both your team and SurrealDB.

1. **Request access** for your organisation and region through [support](/docs/manage/organisations/support.md).
2. **SurrealDB provisions the service side** and shares the details you need.
3. **You create the interface VPC endpoint** in your AWS account with those details.
4. **SurrealDB completes the setup** and enables PrivateLink for your organisation and region.

After that, you manage access modes per instance.

## Limitations

- AWS US West (Oregon) only.
- The SurrealDB team must enable PrivateLink for your organisation before any instance can use it.
- PrivateLink hostnames resolve only inside your VPC, so you cannot use them to connect from outside AWS. Keep `dual` mode, or a separate public instance, if you also need access from developer machines or from CI outside the VPC.

## Related pages

- **[Connect to an instance](/docs/manage/instances/connect.md):** the connection routes themselves.
- **[Network access](/docs/manage/instances/network-access.md):** outbound requests from queries.
- **[Configure an instance](/docs/manage/instances/configure.md):** the rest of instance settings.

---

Source: https://surrealdb.com/docs/manage/instances/scaling

# Scaling

When to resize an instance, and what scales on each plan. How to confirm a resize solved the bottleneck.

This page explains when to change the resources behind an instance, and what to expect from each kind of change.

What you can change depends on the plan. **Start** instances scale vertically on one node. **Scale** clusters scale vertically and horizontally.

The steps for making a change are in [Configure an instance](/docs/manage/instances/configure.md#compute-and-storage).

## What scales on each plan

| | **Start** | **Scale** |
| --- | --- | --- |
| Compute up | A larger instance type on one node | A larger node size, or more nodes |
| Compute down | A smaller instance type | A smaller node size, or fewer nodes |
| Storage | Increase only | Increase only |
| Fault tolerance while resizing | A brief reconnect while the node restarts | The cluster stays available. Capacity dips as each node restarts |

Storage can be increased but never decreased on either plan, and you can increase the disk size once every six hours. Over-provisioning storage is therefore a one-way decision. Start close to what the dataset needs and grow it as the dataset does.

## When to scale

The signals worth acting on are:

- **CPU pegged:** the instance sits near its instance-type limit under normal traffic, not only at peaks.
- **Memory pressure:** the working set no longer fits, so the engine reads from disk more often.
- **Storage growth:** consumption trends towards the provisioned limit. Act well before the limit, because a full disk stops writes.
- **Connection saturation:** clients queue or are refused.
- **Latency objectives breached:** queries that used to meet them no longer do.

Read those signals from the [instance dashboard, metrics, and logs](/docs/manage/instances/monitoring.md).

Rule out query-level causes first. A missing index or a full-table scan is cheaper to fix than a larger instance, and a resize hides the problem rather than removing it.

Schedule resizes outside peak traffic where you can. Some changes cause a brief reconnect, so clients need retry logic in any case.

## Scaling a Start instance

A Start instance has one node, so growth means a larger instance type and a larger disk. A larger type gives more vCPU, more memory, and more I/O.

A Free instance moves to a paid instance type in place, in the same way as any other type change. It does not need a new deployment or an export.

### Storage limits on Start

The storage range of a Start instance depends on the memory of its instance type. A paid type starts with 4 GB of storage for each GB of memory, and its storage can be raised to 8 GB for each GB of memory. The Free type has a fixed 1 GB disk.

| Instance type | Memory | Starting storage | Maximum storage |
| --- | --- | --- | --- |
| Free | 512 MB | 1 GB | 1 GB |
| Burstable small | 1 GB | 4 GB | 8 GB |
| Burstable medium | 2 GB | 8 GB | 16 GB |
| Burstable large | 4 GB | 16 GB | 32 GB |
| General purpose medium | 4 GB | 16 GB | 32 GB |
| General purpose large | 8 GB | 32 GB | 64 GB |
| General purpose xlarge | 16 GB | 64 GB | 128 GB |
| General purpose 2xlarge | 32 GB | 128 GB | 256 GB |
| General purpose 4xlarge | 64 GB | 256 GB | 512 GB |

To grow past the maximum of the current type, move to a larger type first. For example, 50 GB of storage needs at least General purpose large, whose maximum is 64 GB.

A type change sets the storage to the starting storage of the new type, or keeps the current size if that is larger. The type change also counts as a storage change, so the six-hour wait starts again before the storage can be raised on the new type. Where the starting storage of a type already covers what you need, choosing that type avoids the wait.

Move to **Scale** instead when a single node is no longer the right shape:

- The workload must survive the loss of a node.
- Query throughput needs to spread across nodes.
- You have reached the largest available Start instance type.

Moving from Start to Scale is not an in-place upgrade. Deploy a Scale instance, then [restore a backup](/docs/manage/instances/backups.md) or [import an export](/docs/manage/instances/import-and-export.md) into it.

## Scaling a Scale cluster

A Scale cluster grows in two directions. **Vertically**, each node moves to a larger size. **Horizontally**, you add nodes, so a three-node cluster becomes four, then five, and so on. The storage layer handles replication, consensus, and distributed transactions across whatever the node count becomes.

> [!NOTE]
> An even node count does not improve fault tolerance. A cluster of N nodes survives the same number of failures as one of N−1 nodes. The fast commit path also needs every node to agree, so one slow node forces the slower path. Odd sizes of three, five, or seven give the best ratio of resilience to cost.

After you scale out, check two things. Confirm that **replication lag** stays within what the application tolerates. Then check **per-node balance**, because an unbalanced cluster means the added nodes are not taking their share of the load.

See [Architecture](/docs/manage/instances/architecture.md) for the topologies, and [High availability](/docs/manage/instances/high-availability.md) for what each one survives.

## Confirming the resize worked

Compare the same metrics before and after the change. A resize that shifts the bottleneck rather than removing it is common: CPU headroom appears, and disk I/O becomes the new limit.

If utilisation stays low after a large resize, step back down. Instance type is billed on what is provisioned, not on what is used, so an oversized instance is a standing cost with no benefit.

`surreal cloud instance metrics` and `surreal cloud instance update` let you read the signals and apply the change from a terminal. See [surreal cloud instances](/docs/explore/cli/cloud/instances.md).

---

Source: https://surrealdb.com/docs/manage/instances/versions-and-upgrades

# Versions and upgrades

Change the SurrealDB release an instance runs. What happens to the instance during the upgrade.

Change the SurrealDB release an instance runs from **Instance settings → Version** in [SurrealDB Studio](https://studio.surrealdb.com).

You pick the release when you [create the instance](/docs/manage/instances/create.md). The running image is then replaced for you, so there are no binaries to swap and no rolling restart to orchestrate yourself.

## Changing the version

1. Open the instance in [SurrealDB Studio](https://studio.surrealdb.com).
2. Go to **Instance settings → Version**.
3. Select the release you want. Each entry links to its release notes.
4. Apply the change.

Only upgrades are offered. The flow will not move an instance to an older release. On-disk data written by a newer version is not readable by an older one.

On paid instance types a snapshot is taken before the change, and it appears in the [backup list](/docs/manage/instances/backups.md) of the instance. That snapshot is the way back if the new version behaves differently than you expected. Restoring it produces a new instance on a compatible version.

> [!NOTE]
> Pre-release builds, such as nightly or beta tags, are only offered when they have been enabled for your organisation. Keep production on stable releases unless you are deliberately testing a preview.

## What happens during the upgrade

| Plan | Behaviour |
| --- | --- |
| **Start** | The single node restarts on the new image. Expect a short window where connections fail and clients reconnect. |
| **Scale** | The new image rolls through nodes one at a time while the storage layer keeps data consistent. The cluster stays reachable, and query capacity dips while each node restarts. |

Timing depends on the instance size, the dataset size, and the cluster layout. Watch [metrics and logs](/docs/manage/instances/monitoring.md) during and after the change. Confirm that query latency and error rates return to their previous level.

## Planning an upgrade

1. **Read the [release notes](https://surrealdb.com/releases)** for the target version and for every version between it and your current one. Look for breaking changes, changed defaults, and removed options.
2. **Test against a non-production instance** on the target version, with your own application and SDK versions. SDK and server versions are released independently, so check both.
3. **Confirm you have a recent snapshot.** For a copy that lives outside the platform, take a [logical export](/docs/manage/instances/import-and-export.md) instead.
4. **Apply the change**, then run your smoke tests. Cover health checks, the queries your critical paths depend on, and anything that touches features named in the release notes.

For a major-version change that migrates data on disk, work through [Migrating from older SurrealDB versions](/docs/build/migrating/from-old-surrealdb-versions/overview.md) against a staging instance before you touch production.

`surreal cloud instance update` sets the version from a terminal, and `surreal cloud instance status` reports what an instance is running. The second command is useful for checking a fleet at once. See [surreal cloud instances](/docs/explore/cli/cloud/instances.md).

## Compared with self-hosting

Self-hosted operators replace the `surreal` binary, run `surreal db fix` where a release requires it, and coordinate the restart order across nodes themselves. On an instance, the control plane does that work.

The underlying mechanics, and the release-specific migration notes, are in [Self-hosted upgrades and patching](/docs/manage/self-hosted/upgrades-and-patching.md).

---

Source: https://surrealdb.com/docs/manage/observability

# Observability

Production visibility for SurrealDB Community and Enterprise. Logging, Prometheus pull, OTLP push, Enterprise pipelines, metric catalogues, and Tokio console.

SurrealDB exposes **logging**, **metrics**, **traces**, and (with **SurrealDB Enterprise**) durable **audit** and **slow-query** record pipelines through one OpenTelemetry-oriented surface. Signals can leave the process in two complementary ways:

- **Pull** - scrapers call `GET /metrics` (Prometheus text exposition).
- **Push** - the server exports metrics, logs, and traces over **OTLP** to a collector when `SURREAL_TELEMETRY_PROVIDER=otlp`.

Both paths can run together or independently; each is controlled with environment variables documented on the [configuration reference](/docs/manage/observability/configuration.md).

**Suggested reading order**

1. [Logging](/docs/manage/observability/logging.md) - stderr, JSON, files, sockets, and line-based slow-query logging (no metrics stack required).
2. [Observability (metrics and Prometheus)](/docs/manage/observability/observability.md) - `GET /metrics`, naming, migration from pre-3.1 series, the public allowlist, and behaviour common to **Community and Enterprise**.
3. [Telemetry (OTLP)](/docs/manage/observability/telemetry.md) - push export, intervals, and backward-compatible instruments on the wire.
4. [Enterprise observability](/docs/manage/observability/enterprise-observability.md) - cluster metrics, audit and slow-query file pipelines, pipeline self-metrics, and OTLP log export opt-ins.
5. [Metrics reference](/docs/manage/observability/metrics.md) - full catalogue, labels, alert hints, and the 3.0 → 3.1 migration table.
6. [Configuration](/docs/manage/observability/configuration.md) - every telemetry, audit, and slow-query environment variable.
7. [Audit logging](/docs/manage/observability/audit-logging.md) and [Slow-query logging](/docs/manage/observability/slow-query-logging.md) - Enterprise pipeline references.
8. [Tokio console](/docs/manage/observability/tokio-console.md) - optional async-runtime debugging alongside metrics and traces.

**Pull** means scrapers call your server. **Push** means the server opens an export connection to a collector. From SurrealDB 3.1 onward, both paths share the same instruments. Canonical prose also ships in-tree: [`doc/OBSERVABILITY.md`](https://github.com/surrealdb/surrealdb/blob/main/doc/OBSERVABILITY.md) and [`doc/TELEMETRY.md`](https://github.com/surrealdb/surrealdb/blob/main/doc/TELEMETRY.md); the Enterprise distribution extends `doc/OBSERVABILITY.md` with **[C]** / **[E]** catalogue markers.

The `surrealdb.*` metric namespace, the `PUBLIC_METRICS` allowlist, the audit-log pipeline, and the slow-query log pipeline are new in SurrealDB 3.1. Operators upgrading from 3.0 should read the [Migration from 3.0](/docs/manage/observability/metrics.md#migration-from-30) section in the metrics reference.

## Editions at a glance

The Community server publishes the full set of primary signal families. The Enterprise composer adds the distributed-storage cluster metrics, the audit and slow-query log pipelines (with their own self-metrics), and the reserved per-tenant rollup scope.

<table>
    <thead>
        <tr>
            <th scope="col">Capability</th>
            <th scope="col">Community</th>
            <th scope="col">Enterprise</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Capability">Prometheus <code>/metrics</code> endpoint</td>
            <td scope="row" data-label="Community">_(Community)_</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_</td>
        </tr>
        <tr>
            <td scope="row" data-label="Capability">OTLP push (metrics, logs, traces)</td>
            <td scope="row" data-label="Community">_(Community)_</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_</td>
        </tr>
        <tr>
            <td scope="row" data-label="Capability">Primary signal families (statement, query, transaction, RPC, auth, session, network, HTTP, live query, slow-query counter, GraphQL, MCP, storage)</td>
            <td scope="row" data-label="Community">_(Community)_</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_</td>
        </tr>
        <tr>
            <td scope="row" data-label="Capability">Public metrics allowlist for anonymous scrapers</td>
            <td scope="row" data-label="Community">_(Community)_</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_</td>
        </tr>
        <tr>
            <td scope="row" data-label="Capability">Cluster metrics (<code>surrealdb.ds.*</code>)</td>
            <td scope="row" data-label="Community">-</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_ (when the distributed storage runtime is deployed)</td>
        </tr>
        <tr>
            <td scope="row" data-label="Capability">Audit log records (file sink + optional OTel logs)</td>
            <td scope="row" data-label="Community">-</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_</td>
        </tr>
        <tr>
            <td scope="row" data-label="Capability">Slow-query log records (file sink + optional OTel logs)</td>
            <td scope="row" data-label="Community">-</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_</td>
        </tr>
        <tr>
            <td scope="row" data-label="Capability">Hash-chained, tamper-evident records</td>
            <td scope="row" data-label="Community">-</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_</td>
        </tr>
        <tr>
            <td scope="row" data-label="Capability">Three-pass redaction (literal, identifier, regex)</td>
            <td scope="row" data-label="Community">-</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_</td>
        </tr>
        <tr>
            <td scope="row" data-label="Capability">Per-tenant rollup scope (<code>surrealdb.tenant</code>) <em>(reserved)</em></td>
            <td scope="row" data-label="Community">-</td>
            <td scope="row" data-label="Enterprise">_(Enterprise)_</td>
        </tr>
    </tbody>
</table>

Edition is also carried on the OTel `Resource` via the `service.edition` attribute (`community` or `enterprise`), so dashboards can group or filter on it without hard-coding metric scopes.

## Quickstart

**Prometheus pull**

The `/metrics` endpoint is mounted by default. Anonymous scrapers receive only the six metrics on the [public allowlist](/docs/manage/observability/metrics.md#public-metrics-allowlist); root credentials unlock the full surface.

```bash
# Start the server (Community example)
surreal db start --user root --pass secret

# Anonymous scrape - public allowlist only
curl http://127.0.0.1:8000/metrics

# Operator scrape - full surface, including labelled families
curl -u root:secret http://127.0.0.1:8000/metrics
```

To turn the endpoint off entirely:

```bash
SURREAL_METRICS_ENABLED=false surreal db start ...
# /metrics now returns 404
```

**OTLP push**

OTLP push targets any OpenTelemetry collector. SurrealDB exports metrics, logs (including audit and slow-query records when opted in) and traces over gRPC, using native-TLS roots when the endpoint is HTTPS.

```bash
SURREAL_TELEMETRY_PROVIDER=otlp \
OTEL_EXPORTER_OTLP_ENDPOINT="http://my-collector.monitoring.svc.cluster.local:4317" \
surreal db start ...
```

To selectively disable parts of the OTLP pipeline:

```bash
SURREAL_TELEMETRY_DISABLE_METRICS=true   # OTLP metrics off; logs + traces still push
SURREAL_TELEMETRY_DISABLE_TRACING=true   # OTLP traces off; metrics + logs still push
```

Audit and slow-query records flow over OTLP **only when explicitly opted in** per pipeline via `SURREAL_AUDIT_OTEL_EXPORT=true` / `SURREAL_SLOW_QUERY_OTEL_EXPORT=true`. The local file sink remains the primary path for compliance ingestion.

## What is new in 3.1

_(since v3.1.0)_

- A reworked metric namespace - every instrument is now `surrealdb.*`, grouped by signal family (statement, query, transaction, RPC, …). Names from 3.0 are mapped in the [migration table](/docs/manage/observability/metrics.md#migration-from-30).
- **Dual access paths.** Prometheus pull on `/metrics` and OTLP push run side-by-side. Both pipelines can be toggled independently.
- **Public metrics allowlist (`PUBLIC_METRICS`).** Six low-sensitivity gauges are safe to expose anonymously; the rest require root credentials.
- **Audit log pipeline.** _(Enterprise)_ Durable NDJSON file sink with size-based rotation, tunable fsync cadence, optional hash chaining for tamper-evidence, and three-pass redaction.
- **Slow-query log pipeline.** _(Enterprise)_ Mirrors the audit pipeline, with a configurable duration threshold and the slow-query counter metric.
- **Cluster metrics (`surrealdb.ds.*`).** _(Enterprise)_ Around thirty instruments covering network, consensus, view changes, recovery and garbage collection when distributed storage is deployed (SurrealDB Cloud Scale or self-hosted Enterprise).
- **OpenTelemetry alignment.** Standard semantic-convention attribute keys (`http.request.method`, `http.route`, `http.response.status_code`, `db.namespace`, `db.user`) replace ad-hoc keys.

## Where to go next

- [Logging](/docs/manage/observability/logging.md) - Log level, text or JSON format, files, sockets, and line-based slow-query logging.

- [Metrics and Prometheus](/docs/manage/observability/observability.md) - Pull scraping, naming, multi-tenant guidance, and version-specific tabs before and after 3.1.

- [Telemetry (OTLP)](/docs/manage/observability/telemetry.md) - Push export, intervals, process gauges, and legacy instruments on the wire.

- [Enterprise observability](/docs/manage/observability/enterprise-observability.md) - Cluster metrics, audit and slow-query pipelines, and OTLP log export opt-ins.

- [Metrics reference](/docs/manage/observability/metrics.md) - Access paths, label catalogue, every metric grouped by signal family, and the 3.0 → 3.1 migration table.

- [Configuration reference](/docs/manage/observability/configuration.md) - All telemetry, audit log and slow-query log environment variables, plus recommended configurations for local, production and multi-tenant deployments.

- [Audit logging](/docs/manage/observability/audit-logging.md) - Enterprise audit log pipeline: events captured, record shape, rotation, hash chaining and redaction.

- [Slow-query logging](/docs/manage/observability/slow-query-logging.md) - Enterprise slow-query log pipeline: how a query qualifies, record shape and pipeline self-metrics.

- [Tokio console](/docs/manage/observability/tokio-console.md) - Optional Tokio runtime debugging - tasks, poll times, and scheduling alongside metrics and traces.

- [Instance monitoring](/docs/manage/instances/monitoring.md) - The built-in monitoring dashboard, log retention and metrics views for managed instances.

- [Self-hosted monitoring](/docs/manage/self-hosted/monitoring-and-observability.md) - Pairing the `/health` endpoint, Prometheus and Grafana with the observability surface for a self-hosted deployment.

## Structured logs versus audit and slow-query records

Server **structured logs** (levels, format, files, sockets, and the `--slow-log-*` line-based slow-query helpers) are configured on [`surreal db start`](/docs/reference/cli/db/commands/start.md) and in the [environment variables](/docs/reference/cli/db/environment-variables.md) catalogue - see [Logging](/docs/manage/observability/logging.md). That stream is separate from the **Enterprise audit** and **slow-query** NDJSON pipelines, which have their own sinks and optional OTel log export - see [Audit logging](/docs/manage/observability/audit-logging.md) and [Slow-query logging](/docs/manage/observability/slow-query-logging.md).

For async runtime introspection (tasks, poll histograms), use the [Tokio console](/docs/manage/observability/tokio-console.md) on trusted hosts only. It complements OTLP and `/metrics` rather than replacing them.

---

Source: https://surrealdb.com/docs/manage/observability/audit-logging

# Audit logging

The Enterprise audit log pipeline. Events captured, record shape, rotation, hash chaining, redaction, and pipeline self-metrics.

_(Enterprise)_
_(since v3.1.0)_

The audit log pipeline records authoritative, identity-bound events that operators and auditors need to reconstruct who did what, when, and with what outcome. It is part of SurrealDB Enterprise and is **off by default** - set `SURREAL_AUDIT_SINK=file` and `SURREAL_AUDIT_FILE_PATH` to enable it.

> [!WARNING]
> The `SURREAL_AUDIT_*` variables are registered only by the Enterprise binary. Community builds never read them, so the server starts normally, nothing is written to the configured path, and no audit file is created. The loud startup failures described below apply to Enterprise builds only, so do not rely on these settings for compliance on a Community build.

Records flow through two parallel paths:

1. **Durable file sink.** A bounded queue feeds a background worker that appends each record to an NDJSON file. Optional SHA-256 hash chaining provides tamper-evidence; size-based rotation and a tunable fsync cadence keep the file manageable. This is the primary path for compliance and SIEM ingestion.
2. **OpenTelemetry logs.** The same record can also be emitted as an OTel `LogRecord` on the SDK logger provider. This path is **off by default**; opt in per pipeline with `SURREAL_AUDIT_OTEL_EXPORT=true`. Compliance-sensitive deployments typically keep this off and rely on the file sink.

The observer hot path never blocks on I/O. [Redaction](#redaction) runs synchronously on the executor thread before the record reaches the queue, so the worker can write raw bytes straight to the sink and the OTel emit cannot leak unredacted content.

The full set of configuration variables lives on the [configuration reference](/docs/manage/observability/configuration.md#audit-log-knobs).

## Events captured

Each event surfaces as an OTel `LogRecord` with a specific event name and a severity that depends on the outcome:

<table>
    <thead>
        <tr>
            <th scope="col">Event name</th>
            <th scope="col">Severities</th>
            <th scope="col">Captures</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Event"><code>surrealdb.audit.statement</code></td>
            <td scope="row" data-label="Severities"><code>Info</code> / <code>Error</code></td>
            <td scope="row" data-label="Captures">Completion of an individual statement.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Event"><code>surrealdb.audit.query</code></td>
            <td scope="row" data-label="Severities"><code>Info</code> / <code>Error</code></td>
            <td scope="row" data-label="Captures">Completion of a multi-statement query.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Event"><code>surrealdb.audit.transaction</code></td>
            <td scope="row" data-label="Severities"><code>Info</code> / <code>Error</code></td>
            <td scope="row" data-label="Captures">Commit or rollback of a transaction.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Event"><code>surrealdb.audit.rpc</code></td>
            <td scope="row" data-label="Severities"><code>Info</code> / <code>Error</code></td>
            <td scope="row" data-label="Captures">Completion of an RPC call.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Event"><code>surrealdb.audit.auth</code></td>
            <td scope="row" data-label="Severities"><code>Info</code> (success) / <code>Warn</code> (any non-success) / <code>Error</code> (<code>outcome=error</code>)</td>
            <td scope="row" data-label="Captures">Sign-in, sign-up and authentication outcomes.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Event"><code>surrealdb.audit.session</code></td>
            <td scope="row" data-label="Severities"><code>Info</code></td>
            <td scope="row" data-label="Captures">Session connect and disconnect events.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Event"><code>surrealdb.audit.http</code></td>
            <td scope="row" data-label="Severities"><code>Info</code> / <code>Error</code> (typically 5xx responses)</td>
            <td scope="row" data-label="Captures">Completion of an HTTP request.</td>
        </tr>
    </tbody>
</table>

The OTel `LogRecord` body is a short human-readable string. The structured fields live on attributes (`db.namespace`, `db.name`, `db.user`, `db.statement`, `session.id`, `client.address`, `surrealdb.statement_type`, `surrealdb.outcome`, `surrealdb.duration_ms`, `surrealdb.error_class`).

## Record shape

Audit and slow-query files contain one JSON record per line, each terminated by a newline. Audit and slow-query records share a common envelope; audit records additionally carry the `event_type` that distinguishes the seven event variants above.

Audit `event_type` values: `statement`, `query`, `transaction`, `rpc`, `auth`, `session`, `http`.

Envelope fields (common to all records):

- `ts` - RFC 3339 timestamp.
- `event_type` - see above.
- `outcome` - `success`, `error`, `cancelled`, or (for auth events) `denied` / `failed`.
- `duration_ms` - wall-clock duration of the captured operation.
- Identity context - `namespace`, `database`, `user` resolved from the session.
- `sql` - captured statement text when `SURREAL_AUDIT_INCLUDE_SQL=true`; absent otherwise.
- `prev_hash` / `hash` - SHA-256 chain fields, present when [hash chaining](#hash-chaining) is enabled.

A captured `statement` event with hash chaining enabled looks like:

```json
{
  "ts": "2026-03-04T10:23:11.482Z",
  "event_type": "statement",
  "outcome": "success",
  "duration_ms": 14,
  "namespace": "acme",
  "database": "prod",
  "user": "svc_orders",
  "sql": "UPDATE orders:abc SET status = 'shipped'",
  "prev_hash": "f1a3…",
  "hash": "c92e…"
}
```

## Rotation and durability

- File mode `0600` on Unix. The parent directory must exist; the server refuses to start otherwise.
- Size-based rotation at `SURREAL_AUDIT_FILE_ROTATE_BYTES` (default 256 MiB).
- The oldest rotation is dropped once `SURREAL_AUDIT_FILE_ROTATE_KEEP` (default `8`) generations are present.
- Mid-stream fsync cadence is governed by `SURREAL_AUDIT_FSYNC_EVERY`. Rotation and graceful shutdown always flush and `sync_data` regardless of cadence.

## Hash chaining

When `SURREAL_AUDIT_HASH_CHAIN=true` every record carries:

- `prev_hash` - SHA-256 of the previous record in the same file. Absent on the genesis record at the start of a new file.
- `hash` - SHA-256 of this record's canonical serialisation (including `prev_hash`).

Rotation closes a chain and starts a new one with a fresh genesis record. A detector verifies the chain by recomputing each hash sequentially and comparing against the stored `hash`.

> **Hash chaining requires `SURREAL_AUDIT_FSYNC_EVERY=1`.** Without per-record fsync, the chain pointer could advance for records that are not durably on disk, leaving on-disk gaps the chain still references and silently weakening the guarantee. Startup fails loudly when the two knobs disagree.

## Redaction

Redaction is applied **synchronously on the executor thread** before the record reaches the queue, so the worker, the file sink, and the OTel logger all see the same already-scrubbed text. Three layered passes run in order:

1. **Literal pass** - when `SURREAL_AUDIT_REDACT_LITERALS=true` every single- or double-quoted span in the SQL is replaced with `'***'` / `"***"`.
2. **Identifier-token pass** - `SURREAL_AUDIT_REDACT_TABLES=secrets,pii` performs a case-insensitive replacement of each identifier token with `***`.
3. **Regex pass** - `SURREAL_AUDIT_REDACT_REGEX="<pat1>;<pat2>"` (note: **semicolon-separated**) compiles each pattern at startup. An invalid pattern fails startup; valid patterns run in order against the SQL text.

The slow-query log pipeline supports the same three passes under `SURREAL_SLOW_QUERY_REDACT_LITERALS`, `SURREAL_SLOW_QUERY_REDACT_TABLES` and `SURREAL_SLOW_QUERY_REDACT_REGEX`.

## Overflow semantics

Neither overflow policy offers a lossless guarantee:

- `drop` - single non-blocking `try_send`. On `Full` or `Closed` the record is dropped and the `surrealdb_audit_dropped` gauge increments.
- `block` - bounded busy-yield loop (200 retries with `std::thread::yield_now`). `yield_now` does **not** park the OS thread, so on a multi-threaded runtime the drain task can make progress between yields and short bursts may be absorbed without drops. On a `current_thread` runtime the producer holds the only worker and the policy degrades to immediate drop. There is no wall-clock time-bound on the loop - the budget caps iterations only.

The audit pipeline defaults to `block` because audit records are compliance-sensitive; the slow-query pipeline defaults to `drop` because slow-query records are triage data.

> Whichever policy is configured, alert on `rate(surrealdb_audit_dropped[5m]) > 0` and `rate(surrealdb_audit_append_errors[5m]) > 0`. Both indicate records were lost.

## Pipeline self-metrics

Five observable gauges expose the live state of the pipeline. Each is read at scrape time from atomic counters on the worker, so the cost is zero when nothing consumes the metric.

| Metric | Notes |
| --- | --- |
| `surrealdb_audit_records` | Cumulative records successfully enqueued. |
| `surrealdb_audit_dropped` | Cumulative records dropped (overflow or queue closed). **Alert on any non-zero rate.** |
| `surrealdb_audit_queue_depth` | Records currently buffered between observer and worker. Sustained depth above ~50% of `SURREAL_AUDIT_QUEUE_CAPACITY` indicates a slow sink. |
| `surrealdb_audit_appended` | Cumulative records the worker wrote to the sink. The gap to `surrealdb_audit_records` is queue depth plus append errors. |
| `surrealdb_audit_append_errors` | Cumulative sink-write failures. **Alert on any non-zero rate.** |

The slow-query pipeline exposes the same shape under the `surrealdb_slow_query_*` prefix - see [slow-query logging](/docs/manage/observability/slow-query-logging.md#pipeline-self-metrics).

## Related references

- [Configuration → Audit log knobs](/docs/manage/observability/configuration.md#audit-log-knobs) - every audit-log environment variable.
- [Configuration → Compliance checklist](/docs/manage/observability/configuration.md#compliance-checklist) - the minimum tamper-evident configuration.
- [Slow-query logging](/docs/manage/observability/slow-query-logging.md) - the sister pipeline for triage data.
- [Metrics reference → Audit log pipeline self-metrics](/docs/manage/observability/metrics.md#audit-log-pipeline-self-metrics) - the five gauges in the metric catalogue.

---

Source: https://surrealdb.com/docs/manage/observability/configuration

# Configuration reference

Every observability, audit log and slow-query log variable. Recommended configurations for local, production and multi-tenant deployments.

This page lists every environment variable that controls the observability surface. Variables marked _(Enterprise)_ exist only when the Enterprise binary is running; everything else is available in all editions.

The audit log, slow-query log and cluster configuration surfaces are new in SurrealDB 3.1.

## How to read this page

- **Default** is the value used when the variable is unset. Cells marked ` - ` are required when the surrounding feature is enabled.
- **Edition** identifies which builds register the variable. Variables marked _(Enterprise)_ are no-ops on a Community binary.
- All variables can be set via environment, a `.env` file loaded by the deployment, or the orchestrator's secret store.

## Core knobs

These knobs control the two access paths (Prometheus pull and OTLP push) and the global telemetry switches. They are available in every edition.

<table>
    <thead>
        <tr>
            <th scope="col">Variable</th>
            <th scope="col">Default</th>
            <th scope="col">Edition</th>
            <th scope="col">Purpose</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_METRICS_ENABLED</code></td>
            <td scope="row" data-label="Default"><code>true</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Mount the <code>/metrics</code> endpoint. When <code>false</code> the route returns <code>404</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_TELEMETRY_PROVIDER</code></td>
            <td scope="row" data-label="Default">unset</td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Set to <code>otlp</code> to enable the OTLP push pipeline (metrics, logs and traces). Any other value leaves OTLP off.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_TELEMETRY_DISABLE_METRICS</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Skip the OTLP metrics reader specifically, leaving logs and traces unaffected.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_TELEMETRY_DISABLE_TRACING</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Skip the OTLP trace exporter specifically, leaving metrics and logs unaffected.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_METRIC_THRESHOLD_MS</code></td>
            <td scope="row" data-label="Default"><code>1000</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Threshold above which a completed statement is counted on <code>surrealdb_slow_query_total</code>. Set to <code>0</code> to disable the counter.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_PROCESS_METRICS_REFRESH_INTERVAL</code></td>
            <td scope="row" data-label="Default"><code>5</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Cadence in seconds for refreshing the cached process snapshot that backs <code>surrealdb_process_memory_bytes</code> and <code>surrealdb_process_cpu_percent</code>. Floored at 1.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>OTEL_EXPORTER_OTLP_ENDPOINT</code></td>
            <td scope="row" data-label="Default"><code>http://localhost:4317</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">OTLP gRPC endpoint. Standard OpenTelemetry variable.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>OTEL_METRIC_EXPORT_INTERVAL</code></td>
            <td scope="row" data-label="Default"><code>60000</code> (ms)</td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">OTLP push cadence. Standard OpenTelemetry variable.</td>
        </tr>
    </tbody>
</table>

## Audit log knobs

_(Enterprise)_

Controls the [Enterprise audit log pipeline](/docs/manage/observability/audit-logging.md). Setting `SURREAL_AUDIT_SINK=none` (the default) leaves the pipeline disabled entirely; no observer is registered and the cost is zero.

<table>
    <thead>
        <tr>
            <th scope="col">Variable</th>
            <th scope="col">Default</th>
            <th scope="col">Purpose</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_SINK</code></td>
            <td scope="row" data-label="Default"><code>none</code></td>
            <td scope="row" data-label="Purpose">One of <code>none</code>, <code>file</code>. <code>syslog</code> and <code>table</code> are reserved but rejected at startup.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_FILE_PATH</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Required when <code>SURREAL_AUDIT_SINK=file</code>. Parent directory must exist; startup fails loudly if it doesn't. File is opened with mode <code>0600</code> on Unix.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_FILE_ROTATE_BYTES</code></td>
            <td scope="row" data-label="Default"><code>268435456</code> (256 MiB)</td>
            <td scope="row" data-label="Purpose">Size threshold that triggers rotation.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_FILE_ROTATE_KEEP</code></td>
            <td scope="row" data-label="Default"><code>8</code></td>
            <td scope="row" data-label="Purpose">Number of rotated files to retain.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_FSYNC_EVERY</code></td>
            <td scope="row" data-label="Default"><code>0</code></td>
            <td scope="row" data-label="Purpose">Mid-stream fsync cadence. <code>0</code> never fsyncs mid-stream; <code>1</code> fsyncs every record; <code>N&gt;1</code> fsyncs every Nth record. Rotation and graceful shutdown always fsync regardless.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_HASH_CHAIN</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Adds <code>prev_hash</code> / <code>hash</code> SHA-256 fields to every record for tamper-evidence. <strong>Requires <code>SURREAL_AUDIT_FSYNC_EVERY=1</code></strong> - startup fails otherwise.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_INCLUDE_SQL</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Include the full SQL text on <code>statement</code> records. Off by default; the identity and action context is still emitted.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_QUEUE_CAPACITY</code></td>
            <td scope="row" data-label="Default"><code>4096</code></td>
            <td scope="row" data-label="Purpose">Records the bounded observer-to-worker queue can hold.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_OVERFLOW</code></td>
            <td scope="row" data-label="Default"><code>block</code></td>
            <td scope="row" data-label="Purpose"><code>drop</code> (single non-blocking <code>try_send</code>) or <code>block</code> (bounded busy-yield retry). See <a href="/docs/manage/observability/audit-logging.md#overflow-semantics">overflow semantics</a>. Neither offers a lossless guarantee.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_REDACT_TABLES</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Comma-separated identifier tokens replaced with <code>***</code> in captured SQL.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_REDACT_REGEX</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose"><strong>Semicolon-separated</strong> regex patterns applied to captured SQL. Each pattern is compiled at startup; an invalid pattern fails startup.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_REDACT_LITERALS</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Replace every quoted literal with <code>***</code>. Off by default.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_OTEL_EXPORT</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Also emit each audit record as an OTel <code>LogRecord</code>. Off by default so compliance-sensitive records stay on the local file sink.</td>
        </tr>
    </tbody>
</table>

## Slow-query log knobs

_(Enterprise)_

Controls the [Enterprise slow-query log pipeline](/docs/manage/observability/slow-query-logging.md). Setting `SURREAL_SLOW_QUERY_SINK=none` (the default) leaves the pipeline disabled entirely.

<table>
    <thead>
        <tr>
            <th scope="col">Variable</th>
            <th scope="col">Default</th>
            <th scope="col">Purpose</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_SINK</code></td>
            <td scope="row" data-label="Default"><code>none</code></td>
            <td scope="row" data-label="Purpose">Same selector set as <code>SURREAL_AUDIT_SINK</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_FILE_PATH</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Required when the sink is <code>file</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_FILE_ROTATE_BYTES</code></td>
            <td scope="row" data-label="Default"><code>268435456</code> (256 MiB)</td>
            <td scope="row" data-label="Purpose">Rotation threshold.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_FILE_ROTATE_KEEP</code></td>
            <td scope="row" data-label="Default"><code>8</code></td>
            <td scope="row" data-label="Purpose">Retained rotation generations.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_FSYNC_EVERY</code></td>
            <td scope="row" data-label="Default"><code>0</code></td>
            <td scope="row" data-label="Purpose">Same semantics as the audit equivalent.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_HASH_CHAIN</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Tamper-evident chain. Requires <code>SURREAL_SLOW_QUERY_FSYNC_EVERY=1</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_THRESHOLD_MS</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Duration threshold above which a statement is captured. Required when the sink is enabled.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_INCLUDE_SQL</code></td>
            <td scope="row" data-label="Default"><code>true</code></td>
            <td scope="row" data-label="Purpose">Capture SQL text in slow-query records. Setting to <code>false</code> lets throughput-sensitive deployments skip per-statement <code>to_sql()</code> rendering at the cost of losing SQL context on captures.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_QUEUE_CAPACITY</code></td>
            <td scope="row" data-label="Default"><code>4096</code></td>
            <td scope="row" data-label="Purpose">Records the queue can hold.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_OVERFLOW</code></td>
            <td scope="row" data-label="Default"><code>drop</code></td>
            <td scope="row" data-label="Purpose"><strong>Default differs from audit</strong> - slow-query records are triage data, so dropping is preferred over busy-yielding the executor.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_REDACT_TABLES</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Same syntax as the audit equivalent.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_REDACT_REGEX</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Same syntax as the audit equivalent.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_REDACT_LITERALS</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Same semantics as the audit equivalent.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_OTEL_EXPORT</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Emit each slow-query record as an OTel <code>LogRecord</code> at <code>WARN</code> severity.</td>
        </tr>
    </tbody>
</table>

## Cluster networking, consensus and storage

_(Enterprise)_

> [!NOTE]
> The `SURREAL_DS_*` variables below apply to multi-node clusters, which use distributed storage with replication and consensus - on [SurrealDB Cloud Scale](https://surrealdb.com/pricing/scale) or in self-hosted Enterprise installations.

This section lists the cluster networking, consensus and storage-memory knobs that operators routinely tune in response to a metric signal. The complete `SURREAL_DS_*` reference is part of the Enterprise Kubernetes deployment guide.

<table>
    <thead>
        <tr>
            <th scope="col">Variable</th>
            <th scope="col">Default</th>
            <th scope="col">Tune in response to</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_NTW_INBOUND_BYTES</code></td>
            <td scope="row" data-label="Default"><code>3 * MAX_MSG_BYTES</code> (768 MiB) per <code>MessageClass</code></td>
            <td scope="row" data-label="Trigger"><code>QUIC inbound bytes ... saturation warning</code> log.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_NTW_INFLIGHT_PROCESSING_CAP</code></td>
            <td scope="row" data-label="Default"><code>max(384, num_cpus*32)</code></td>
            <td scope="row" data-label="Trigger"><code>QUIC inbound handlers ... saturation warning</code> log.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_MAX_READ_OPERATIONS</code></td>
            <td scope="row" data-label="Default"><code>192</code></td>
            <td scope="row" data-label="Trigger">Steady-state RSS pressure or scan-heavy workloads.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_MAX_WRITE_OPERATIONS</code></td>
            <td scope="row" data-label="Default"><code>96</code></td>
            <td scope="row" data-label="Trigger"><code>Write operations limit saturation warning</code> log.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_CONSENSUS_FAST_QUORUM_TIMEOUT_MS</code></td>
            <td scope="row" data-label="Default">implementation default</td>
            <td scope="row" data-label="Trigger">Rising <code>surrealdb_ds_consensus_fast_quorum_timeouts_total</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_RETRY_BASE_MS</code> / <code>SURREAL_DS_RETRY_MAX_MS</code></td>
            <td scope="row" data-label="Default"><code>500</code> / <code>5000</code></td>
            <td scope="row" data-label="Trigger">Rising <code>surrealdb_ds_finalize_prepare_retries_total</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_ROCKSDB_BLOCK_CACHE_SIZE</code>_(since v3.1.1)_</td>
            <td scope="row" data-label="Default"><code>max(memory/2 - 1 GiB, 16 MiB)</code></td>
            <td scope="row" data-label="Trigger">Steady-state RSS pressure. The largest single consumer in a node using the RocksDB durable backend. Full-text index reads are served through it entirely; vector index reads hit it on load and on cache misses. The default is derived from detected memory, so set it explicitly on memory-constrained pods.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_ROCKSDB_MAX_WRITE_BUFFER_NUMBER</code>_(since v3.1.1)_</td>
            <td scope="row" data-label="Default">Derived from detected memory (<code>2</code>-<code>32</code>)</td>
            <td scope="row" data-label="Trigger">RSS pressure during sustained write phases. The memtable ceiling is this value multiplied by <code>SURREAL_DS_ROCKSDB_WRITE_BUFFER_SIZE</code> and by the four heavy column families, so it is the knob to cap first when headroom is tight.</td>
        </tr>
    </tbody>
</table>

> [!NOTE]
> `SURREAL_DS_ROCKSDB_*` applies only when a node's store path selects the RocksDB durable backend. Memtable and block-cache memory are accounted together, and an undersized write budget presents as write latency rather than errors - but the operative limits are the per-column-family memtable count and the L0 compaction trigger, not the combined ceiling. `SURREAL_DS_ROCKSDB_WRITE_BUFFER_SIZE` sets the per-column-family memtable size; the complete reference is in the Enterprise Kubernetes deployment guide.

## Recommended configurations

**Local development**

The minimum needed to scrape metrics from a local server during development:

```bash
surreal db start --user root --pass secret
# Anonymous: only the public allowlist
curl http://127.0.0.1:8000/metrics
# Authenticated: the full surface
curl -u root:secret http://127.0.0.1:8000/metrics
```

To push to a local OpenTelemetry collector (for example a Grafana / Tempo / Loki / Prometheus docker-compose stack):

```bash
SURREAL_TELEMETRY_PROVIDER=otlp \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
surreal db start --user root --pass secret
```

To disable the endpoint entirely (useful when running with a third-party agent that scrapes via OTLP only):

```bash
SURREAL_METRICS_ENABLED=false surreal db start ...
# /metrics → 404
```

**Self-hosted production**

For a self-hosted production deployment that ships metrics over OTLP and writes audit and slow-query records to a local sink for SIEM ingestion:

```bash
# Telemetry
SURREAL_METRICS_ENABLED=true
SURREAL_TELEMETRY_PROVIDER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.internal:4317
OTEL_METRIC_EXPORT_INTERVAL=30000

# Audit log (Enterprise) - durable, hash-chained
SURREAL_AUDIT_SINK=file
SURREAL_AUDIT_FILE_PATH=/var/log/surrealdb/audit.log
SURREAL_AUDIT_FSYNC_EVERY=1
SURREAL_AUDIT_HASH_CHAIN=true
SURREAL_AUDIT_INCLUDE_SQL=true
SURREAL_AUDIT_REDACT_LITERALS=true
SURREAL_AUDIT_OVERFLOW=block

# Slow-query log (Enterprise) - best-effort triage data
SURREAL_SLOW_QUERY_SINK=file
SURREAL_SLOW_QUERY_FILE_PATH=/var/log/surrealdb/slow-query.log
SURREAL_SLOW_QUERY_THRESHOLD_MS=250
```

Pair this with the alert hints in the [metrics reference](/docs/manage/observability/metrics.md#alert-hints) and the alerting rules on `surrealdb_audit_dropped`, `surrealdb_audit_append_errors` and `surrealdb_audit_queue_depth` listed in the [Compliance checklist](#compliance-checklist) below.

**Multi-tenant / cloud-facing**

For pods that serve customer traffic directly, the default posture is **deny scraping at the pod boundary** and serve metrics from an internal sidecar instead. Customer-visible metrics should be filtered at a proxy.

```bash
# Customer-facing pod
SURREAL_METRICS_ENABLED=false

# Internal sidecar pod (same workload, internal-only interface)
SURREAL_METRICS_ENABLED=true
SURREAL_TELEMETRY_PROVIDER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.internal:4317
```

Filter on `namespace` and `database` at the proxy and strip the `user` label before exposing to a customer. See the [Recommended cloud whitelist](/docs/manage/observability/metrics.md#recommended-cloud-whitelist) for the suggested customer-visible subset.

## Compliance checklist

_(Enterprise)_

For deployments that require tamper-evident, durable audit trails the recommended configuration is:

```bash
SURREAL_AUDIT_SINK=file
SURREAL_AUDIT_FILE_PATH=/var/log/surrealdb/audit.log
SURREAL_AUDIT_FSYNC_EVERY=1            # required for hash chain
SURREAL_AUDIT_HASH_CHAIN=true
SURREAL_AUDIT_INCLUDE_SQL=true         # if a regulator requires statement text
SURREAL_AUDIT_REDACT_LITERALS=true     # scrub embedded PII from quoted values
SURREAL_AUDIT_OVERFLOW=block           # already the default; explicit for clarity
```

Plus alerts on:

- `surrealdb_audit_dropped` - any non-zero rate is a lost record.
- `surrealdb_audit_append_errors` - any non-zero rate is a lost record.
- `surrealdb_audit_queue_depth` sustained above ~50% of `SURREAL_AUDIT_QUEUE_CAPACITY` - the sink is falling behind.

For the full pipeline details (record shape, rotation, hash chain, redaction) see the [Audit logging](/docs/manage/observability/audit-logging.md) reference.

## Troubleshooting

| Symptom | Likely cause | Investigation |
| --- | --- | --- |
| `/metrics` returns `404` | `SURREAL_METRICS_ENABLED=false` | Re-enable, or scrape over OTLP instead. |
| `/metrics` returns only six metrics | Anonymous scrape against the public allowlist | Configure basic-auth with root credentials. |
| No `surrealdb_ds_*` metrics on a single-node setup | Enterprise composer did not start a metrics reader | Confirm the Enterprise binary is running and at least one of `SURREAL_METRICS_ENABLED=true` / `SURREAL_TELEMETRY_PROVIDER=otlp` is set. |
| `surrealdb_audit_records` missing entirely | Audit sink set to `none` (the default) | Set `SURREAL_AUDIT_SINK=file` and `SURREAL_AUDIT_FILE_PATH`. |
| Sustained `surrealdb_audit_dropped` rate | Worker can't keep up | Raise `SURREAL_AUDIT_QUEUE_CAPACITY`; check sink disk I/O; consider switching from `block` to `drop` if drops are acceptable. |
| `QUIC inbound bytes ... saturation warning` log | Per-class inbound budget saturated | Raise `SURREAL_DS_NTW_INBOUND_BYTES`. |
| OTLP collector not receiving metrics | `SURREAL_TELEMETRY_PROVIDER` unset, or `SURREAL_TELEMETRY_DISABLE_METRICS=true` | Confirm both, plus that `OTEL_EXPORTER_OTLP_ENDPOINT` is reachable. |
| Hash chain rejected at startup | `SURREAL_AUDIT_HASH_CHAIN=true` without `SURREAL_AUDIT_FSYNC_EVERY=1` | Set both, or disable hash chaining. |

---

Source: https://surrealdb.com/docs/manage/observability/enterprise-observability

# Enterprise observability

Metrics, logs, and traces in Enterprise versus Community. OTLP opt-ins, audit and slow-query pipelines, and cluster instruments.

SurrealDB **Community** and **Enterprise** share one unified observability implementation in the server: the same labelled metric families, the same `GET /metrics` path, and the same OTLP push pipeline when enabled. **Enterprise** adds optional durability and cluster depth on top:

- **Cluster instruments** (`surrealdb.ds.*`) - consensus, networking, recovery, and garbage collection when distributed storage is the active backend ([SurrealDB Cloud](https://surrealdb.com/pricing) Scale tiers and self-hosted Enterprise).
- **Audit and slow-query pipelines** - bounded queues, file sinks with rotation and optional hash chaining, and pipeline self-metrics (`surrealdb.audit.*`, `surrealdb.slow_query.*`) when those sinks are configured.
- **OpenTelemetry logs for audit and slow-query** - the same redacted records can be emitted as OTel log records, but **only when you opt in** with `SURREAL_AUDIT_OTEL_EXPORT=true` and/or `SURREAL_SLOW_QUERY_OTEL_EXPORT=true` (default `false` so sensitive payloads stay on the file sink unless you choose otherwise).

Throughout this page, markers mean:

- **[C]** - Registered in every build that exposes metrics (Community and Enterprise).
- **[E]** - Registered only when running a **SurrealDB Enterprise** binary with the relevant feature enabled.

For Prometheus pull behaviour, naming rules, the public `PUBLIC_METRICS` allowlist, and multi-tenant guidance common to both editions, see [Observability (metrics and Prometheus)](/docs/manage/observability/observability.md). For OTLP push, intervals, and legacy HTTP/RPC instruments, see [Telemetry (OTLP)](/docs/manage/observability/telemetry.md).

## Pipeline overview

```text
HTTP / WS / Executor → ExecutionObserver → FanOutObserver
                                            ├── MetricsObserver         (labelled surrealdb.*)         [C]
                                            ├── DsMetrics               (surrealdb.ds.*)               [E]
                                            ├── AuditObserver           (audit → file + optional OTel)  [E]
                                            └── SlowQueryObserver       (slow-query → file + optional OTel) [E]
                                                       │
                                            SdkMeterProvider + SdkLoggerProvider
                                                       │
                                       ┌───────────────┼────────────────┐
                                       ▼               ▼                ▼
                          Prometheus text       OTLP metrics push   OTLP logs / traces push
                          exporter (/metrics)
```

`ExecutionObserver` short-circuits when no observer in the chain has work, so a process with neither `/metrics` nor OTLP configured avoids hot-path cost. Build flavour is carried on the OpenTelemetry **resource** as `service.edition` (`community` or `enterprise`), for example on `target_info` in Prometheus - filter dashboards on that attribute rather than on meter scope names.

## `/metrics` versus OTLP

| Pipeline | Trigger | Transport | Authentication | What is exposed |
| --- | --- | --- | --- | --- |
| Prometheus pull | `SURREAL_METRICS_ENABLED=true` (default) | `GET /metrics` on the main HTTP port (`text/plain; version=0.0.4`) | Root credentials unlock the full surface. Anonymous scrapers receive only the **public allowlist** (aggregate process gauges). | Every instrument the running binary registers. |
| OTLP push | `SURREAL_TELEMETRY_PROVIDER=otlp` | gRPC to `OTEL_EXPORTER_OTLP_ENDPOINT` (default `http://localhost:4317`). Push cadence: `OTEL_METRIC_EXPORT_INTERVAL` (ms, default `60000`). | TLS and client authentication are the collector’s responsibility. | Same metrics as an authenticated Prometheus view, plus traces and (when configured) audit and slow-query **log** records. |

Disabling pieces:

| Switch | Default | Effect |
| --- | --- | --- |
| `SURREAL_METRICS_ENABLED` | `true` | When `false`, `/metrics` is not mounted (404). |
| `SURREAL_TELEMETRY_PROVIDER` | unset | Set to `otlp` to start OTLP exporters. |
| `SURREAL_TELEMETRY_DISABLE_METRICS` | `false` | Skips only the OTLP **metrics** reader; logs and traces can still push. |
| `SURREAL_TELEMETRY_DISABLE_TRACING` | `false` | Skips only the OTLP **trace** exporter. |

## Community versus Enterprise (at a glance)

| Concern | Community | Enterprise |
| --- | --- | --- |
| Labelled `surrealdb.*` statement, query, transaction, RPC, HTTP, session, auth, network, live query, process, storage | Yes [C] | Yes [C] |
| Slow-query **counter** (`surrealdb.slow_query.*`) vs threshold | `SURREAL_SLOW_QUERY_METRIC_THRESHOLD_MS` (default 1000 ms; `0` disables) | Same [C] |
| `surrealdb.ds.*` cluster metrics | Not emitted | Emitted when the distributed storage runtime is active and a metrics reader exists [E] |
| Durable **audit** / **slow-query** file sinks | Not available | `SURREAL_AUDIT_*`, `SURREAL_SLOW_QUERY_*` [E] |
| Pipeline self-metrics (`surrealdb_audit_*`, `surrealdb_slow_query_*`) | Not registered | Registered when the matching pipeline is configured [E] |
| OTel log export for audit / slow-query records | N/A (no enterprise pipelines) | Opt-in per pipeline: `SURREAL_AUDIT_OTEL_EXPORT`, `SURREAL_SLOW_QUERY_OTEL_EXPORT` [E] |
| Per-tenant rollup scope `surrealdb.tenant` | Reserved for future low-cardinality rollups | Same; **no instruments are registered there yet**. Use proxy-side filtering on dimensional families for customer-visible views today. |

## Configuration highlights (Enterprise)

### Core switches ([C])

These apply to every build:

| Variable | Default | Purpose |
| --- | --- | --- |
| `SURREAL_METRICS_ENABLED` | `true` | Mount `/metrics`. |
| `SURREAL_TELEMETRY_PROVIDER` | unset | `otlp` enables OTLP push. |
| `SURREAL_TELEMETRY_DISABLE_METRICS` | `false` | Skip OTLP metrics only. |
| `SURREAL_TELEMETRY_DISABLE_TRACING` | `false` | Skip OTLP traces only. |
| `SURREAL_SLOW_QUERY_METRIC_THRESHOLD_MS` | `1000` | Statements slower than this also increment the slow-query counter; `0` disables that counter. |
| `SURREAL_PROCESS_METRICS_REFRESH_INTERVAL` | `5` | Seconds between process memory/CPU gauge refreshes (minimum 1). |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | OTLP gRPC endpoint. |
| `OTEL_METRIC_EXPORT_INTERVAL` | `60000` | OTLP metric push interval (ms). |

### Audit log ([E])

| Variable | Default | Notes |
| --- | --- | --- |
| `SURREAL_AUDIT_SINK` | `none` | `none` or `file`. |
| `SURREAL_AUDIT_FILE_PATH` | - | Required for `file`; parent directory must exist; file mode `0600` on Unix. |
| `SURREAL_AUDIT_FSYNC_EVERY` | `0` | Mid-stream fsync cadence; rotation and graceful shutdown always flush. |
| `SURREAL_AUDIT_HASH_CHAIN` | `false` | Tamper-evident chaining; **requires** `SURREAL_AUDIT_FSYNC_EVERY=1`. |
| `SURREAL_AUDIT_INCLUDE_SQL` | `false` | Include full SQL on statement records. |
| `SURREAL_AUDIT_OVERFLOW` | `block` | `drop` or `block` when the queue is full; compliance deployments usually keep `block`. |
| `SURREAL_AUDIT_OTEL_EXPORT` | `false` | Also emit each record as an OTel log (opt-in). |

### Slow-query log ([E])

| Variable | Default | Notes |
| --- | --- | --- |
| `SURREAL_SLOW_QUERY_SINK` | `none` | Same pattern as audit. |
| `SURREAL_SLOW_QUERY_THRESHOLD_MS` | - | Required when the sink is enabled. |
| `SURREAL_SLOW_QUERY_INCLUDE_SQL` | `true` | SQL in slow-query records (turn off for throughput-sensitive deployments). |
| `SURREAL_SLOW_QUERY_OVERFLOW` | `drop` | Defaults to **drop** (triage data); audit defaults to **block**. |
| `SURREAL_SLOW_QUERY_OTEL_EXPORT` | `false` | OTel log export for slow-query records (opt-in). |

Redaction (`SURREAL_AUDIT_REDACT_*` / `SURREAL_SLOW_QUERY_REDACT_*`) runs on the executor thread **before** the queue so the file sink and OTel path see the same scrubbed text. Full knob lists live with the rest of the server [environment variables](/docs/reference/cli/db/environment-variables.md).

### Cluster tuning ([E])

Large clusters expose saturation through `surrealdb.ds.*` and logs. Typical operator levers include inbound byte budgets, handler caps, read/write operation caps, and consensus timeouts. If you deploy on Kubernetes, pair metrics with the environment-variable reference in your platform runbook.

## Audit and slow-query records ([E])

Records flow in parallel:

1. **File sink** - optional NDJSON with rotation; optional hash chain for tamper evidence.
2. **OpenTelemetry logs** - only when the matching `SURREAL_*_OTEL_EXPORT` flag is `true`.

Severity rules differ slightly between audit event kinds and slow-query (slow-query is always treated as a degraded path - typically **WARN**). Monitor **`surrealdb_audit_dropped`**, **`surrealdb_slow_query_dropped`**, **`surrealdb_audit_append_errors`**, and **`surrealdb_slow_query_append_errors`**: any sustained non-zero rate means **lost or failed** records.

## Recommended exposure tiers (operators)

When you front `/metrics` with a proxy or expose metrics to customers:

1. **Always safe without authentication** - matches the built-in public allowlist (`surrealdb_build_info`, process gauges, `target_info`, `otel_scope_info`).
2. **Customer-visible** - only after the proxy **filters** dimensional series to that customer’s `namespace` / `database`. Strip `user` before showing outside trusted operator contexts.
3. **Operator-only** - `surrealdb.ds.*`, pipeline self-metrics, `surrealdb_auth_*`, storage backend detail, RPC method detail, and similar. Scrape these on a private interface with root credentials.

## Migration from older Prometheus names

Legacy series names were collapsed into labelled families in SurrealDB 3.1. The same migration table applies to Community and Enterprise; see [Observability (metrics and Prometheus)](/docs/manage/observability/observability.md) for the summary, [Telemetry (OTLP)](/docs/manage/observability/telemetry.md) for wire-format and deprecation notes, and the [Metrics reference](/docs/manage/observability/metrics.md#migration-from-30) for the full migration table.

## Local checks

```bash
curl http://127.0.0.1:8000/metrics
curl -u root:secret http://127.0.0.1:8000/metrics
```

Enterprise-only metrics (`surrealdb_ds_*`, audit/slow-query pipeline gauges) appear only in the authenticated view when those subsystems are active.

## Pipeline and configuration references

- [Audit logging](/docs/manage/observability/audit-logging.md) - record shape, rotation, overflow semantics, hash chaining, redaction.
- [Slow-query logging](/docs/manage/observability/slow-query-logging.md) - triage pipeline mirroring the audit observer.
- [Configuration](/docs/manage/observability/configuration.md) - all `SURREAL_AUDIT_*` and `SURREAL_SLOW_QUERY_*` variables and the compliance checklist.

---

Source: https://surrealdb.com/docs/manage/observability/logging

# Logging

Log levels, text and JSON formats, and file, socket and OTel output.

SurrealDB writes server logs to stderr by default. You can set flags on [`surreal db start`](/docs/reference/cli/db/commands/start.md) or the matching [environment variables](/docs/reference/cli/db/environment-variables.md) and ship lines to your collector or log store.

## Core options

| Concern | CLI flag (examples) | Environment variable (examples) |
| --- | --- | --- |
| Level | `--log` | `SURREAL_LOG` |
| Format | `--log-format` (`text` or `json`) | `SURREAL_LOG_FORMAT` |
| Remote socket | `--log-socket` | `SURREAL_LOG_SOCKET` |
| File logging | `--log-file-enabled`, `--log-file-path`, `--log-file-rotation`, … | `SURREAL_LOG_FILE_ENABLED`, `SURREAL_LOG_FILE_PATH`, `SURREAL_LOG_FILE_ROTATION`, … |
| File level | `--log-file-level` | `SURREAL_LOG_FILE_LEVEL` |
| Slow queries | `--slow-log-threshold`, `--slow-log-param-allow`, `--slow-log-param-deny` | `SURREAL_SLOW_QUERY_LOG_THRESHOLD`, `SURREAL_SLOW_QUERY_LOG_PARAM_ALLOW`, `SURREAL_SLOW_QUERY_LOG_PARAM_DENY` |

Further socket and file options (`--log-socket-level`, `--log-file-name`, `--log-file-format`, and others) are documented on [`surreal db start`](/docs/reference/cli/db/commands/start.md) and in [Environment variables](/docs/reference/cli/db/environment-variables.md).

Slow-query logging records statements that exceed your threshold. The `--slow-log-param-allow` / `--slow-log-param-deny` flags control whether bound parameters are expanded in the log line (for example turning `SELECT * FROM $table_name` into `SELECT * FROM person` when `table_name` is allowed). Set these flags for any logs that may contain sensitive data.

## OpenTelemetry trace and log verbosity

OpenTelemetry trace output can be tuned separately from ordinary server logs:

* `--log-otel-level` or `SURREAL_LOG_OTEL_LEVEL` - verbosity of OTel-related tracing output.

When OTLP is enabled, **SurrealDB Enterprise** can also emit audit and slow-query records as OpenTelemetry logs when you set `SURREAL_AUDIT_OTEL_EXPORT` / `SURREAL_SLOW_QUERY_OTEL_EXPORT`. Severity mapping and dual paths (file sink versus OTLP) are summarised on [Observability (metrics and Prometheus)](/docs/manage/observability/observability.md), [Enterprise observability](/docs/manage/observability/enterprise-observability.md), and in the [Metrics reference](/docs/manage/observability/metrics.md). In-tree detail lives in [`doc/OBSERVABILITY.md`](https://github.com/surrealdb/surrealdb/blob/main/doc/OBSERVABILITY.md) on GitHub.

---

Source: https://surrealdb.com/docs/manage/observability/metrics

# Metrics reference

Access paths, labels, and the full metric reference for both editions.

This page is the canonical reference for every metric emitted by SurrealDB. It covers how operators access them, the labels and naming rules that govern the surface, the full catalogue grouped by signal family, the public allowlist for anonymous scrapers, and the migration table from 3.0 names.

The instruments listed below were introduced as part of the 3.1 observability overhaul. The [Migration from 3.0](#migration-from-30) section at the bottom maps every legacy Prometheus name to its current replacement.

## Access paths

Metrics can be exposed in two complementary ways. Both pipelines can run simultaneously.

### Prometheus pull

| Property | Value |
| --- | --- |
| Trigger | `SURREAL_METRICS_ENABLED=true` (default) |
| Transport | `GET /metrics` on the main HTTP port, content-type `text/plain; version=0.0.4` |
| Authentication | Root credentials gate the full surface. Anonymous scrapers receive the [`PUBLIC_METRICS`](#public-metrics-allowlist) allowlist only. |
| Surface | Every instrument the running build registers. |

Disable the endpoint by setting `SURREAL_METRICS_ENABLED=false` - the route then returns `404`.

### OTLP push

| Property | Value |
| --- | --- |
| Trigger | `SURREAL_TELEMETRY_PROVIDER=otlp` |
| Transport | gRPC, default endpoint `http://localhost:4317`, push cadence from `OTEL_METRIC_EXPORT_INTERVAL` (milliseconds; default `60000`), cumulative temporality |
| Authentication | The collector's responsibility. The exporter speaks gRPC and native-TLS but does not enforce client authentication. |
| Surface | The same instruments as `/metrics`, plus the OTel logs surface (audit and slow-query records, when their OTel export is enabled) and `tracing` spans. |

Selectively disable parts of the OTLP pipeline:

- `SURREAL_TELEMETRY_DISABLE_METRICS=true` - suppress the OTLP metrics reader, leaving logs and traces unaffected.
- `SURREAL_TELEMETRY_DISABLE_TRACING=true` - suppress the OTLP trace exporter, leaving metrics and logs unaffected.

For the full environment variable reference, see [Configuration](/docs/manage/observability/configuration.md#core-knobs).

## Render rules

OpenTelemetry instrument names use a dotted form (`surrealdb.statement.duration`). The Prometheus text exporter converts each instrument deterministically:

- Counters get a `_total` suffix.
- Histograms and counters with unit `s` get a `_seconds` suffix.
- Histograms and counters with unit `By` get a `_bytes` suffix.
- Up-down counters render as plain gauges with no suffix.
- Attribute keys containing dots (`http.request.method`, `http.route`, `rpc.method`) are sanitised to underscore form at render time.

A few worked examples:

<table>
    <thead>
        <tr>
            <th scope="col">OpenTelemetry name</th>
            <th scope="col">Unit</th>
            <th scope="col">Prometheus name</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="OTel"><code>surrealdb.statement.duration</code></td>
            <td scope="row" data-label="Unit"><code>s</code></td>
            <td scope="row" data-label="Prometheus"><code>surrealdb_statement_duration_seconds</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="OTel"><code>surrealdb.statement</code> (counter)</td>
            <td scope="row" data-label="Unit">-</td>
            <td scope="row" data-label="Prometheus"><code>surrealdb_statement_total</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="OTel"><code>surrealdb.http.request.size</code></td>
            <td scope="row" data-label="Unit"><code>By</code></td>
            <td scope="row" data-label="Prometheus"><code>surrealdb_http_request_size_bytes_total</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="OTel"><code>surrealdb.process.memory</code></td>
            <td scope="row" data-label="Unit"><code>By</code></td>
            <td scope="row" data-label="Prometheus"><code>surrealdb_process_memory_bytes</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="OTel"><code>surrealdb.http.active_requests</code> (up-down counter)</td>
            <td scope="row" data-label="Unit">-</td>
            <td scope="row" data-label="Prometheus"><code>surrealdb_http_active_requests</code></td>
        </tr>
    </tbody>
</table>

## Common labels

Every labelled family carries `outcome` plus the resolved tenant context (`namespace`, `database`, `user`) where applicable. Unresolved values render as the `"-"` sentinel; record-access principals render as the fixed `<record>` sentinel.

<table>
    <thead>
        <tr>
            <th scope="col">Key</th>
            <th scope="col">Bounded values</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Key"><code>outcome</code></td>
            <td scope="row" data-label="Values"><code>success</code>, <code>error</code>, <code>canceled</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>statement_type</code></td>
            <td scope="row" data-label="Values"><code>select</code>, <code>update</code>, <code>create</code>, … (closed AST set)</td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>namespace</code> / <code>database</code> / <code>user</code></td>
            <td scope="row" data-label="Values">Free-form strings up to a user-configured length. <code>"-"</code> when unresolved; <code>&lt;record&gt;</code> for record-access principals.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>protocol</code></td>
            <td scope="row" data-label="Values"><code>websocket</code>, <code>http</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>auth_action</code></td>
            <td scope="row" data-label="Values"><code>signin</code>, <code>signup</code>, <code>authenticate</code>, …</td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>auth_scope</code></td>
            <td scope="row" data-label="Values"><code>root</code>, <code>namespace</code>, <code>database</code>, <code>record</code>, <code>none</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>http_request_method</code></td>
            <td scope="row" data-label="Values"><code>get</code>, <code>post</code>, <code>put</code>, <code>delete</code>, <code>patch</code>, <code>head</code>, <code>options</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>http_route</code></td>
            <td scope="row" data-label="Values">Matched router pattern, bounded by the router definitions. <code>"-"</code> for unmatched.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>http_response_status_code</code></td>
            <td scope="row" data-label="Values">Stringified status (<code>200</code>, <code>400</code>, …)</td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>rpc_method</code></td>
            <td scope="row" data-label="Values">Bounded by the RPC dispatch table.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Key"><code>error_class</code></td>
            <td scope="row" data-label="Values">Bounded; populated only when <code>outcome != success</code>.</td>
        </tr>
    </tbody>
</table>

The cluster family (`surrealdb.ds.*`) adds a small set of additional labels (`peer`, `kind`, `message_type`, `path`, `phase`, `role`, `result`, `source`, `reason`). See [Cluster labels](#cluster-labels) further down.

## Metric catalogue

The catalogue is grouped by signal family. Each subsection lists the Prometheus name (the form an operator scrapes), the instrument type, the edition that exposes it, the labels it carries, and any notes.

### Process - `surrealdb.process`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
            <th scope="col">Notes</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_build_info</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels"><code>build_version</code></td>
            <td scope="row" data-label="Notes">Always <code>1</code>. The value carries no signal; the label is the static compile-time version.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_process_uptime_seconds</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">-</td>
            <td scope="row" data-label="Notes">Seconds since process start.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_process_memory_bytes</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">-</td>
            <td scope="row" data-label="Notes">Resident set size of the process.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_process_memory_allocated_bytes</code>_(since v3.2.5)_</td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">-</td>
            <td scope="row" data-label="Notes">Tracked memory: live Rust-heap allocations, counted at the size class the allocator reserved for each, plus external memory reporters such as the RocksDB block cache. This is the value compared against <code>SURREAL_MEMORY_THRESHOLD</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_process_memory_requested_bytes</code>_(since v3.3.0)_</td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">-</td>
            <td scope="row" data-label="Notes">Bytes requested for live heap allocations, before the allocator rounded each request up to a size class.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_process_memory_reserved_bytes</code>_(since v3.3.0)_</td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">-</td>
            <td scope="row" data-label="Notes">Reserved Rust-heap bytes: the size classes that live requests were rounded up into, without the external memory reporters.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_process_memory_threshold_bytes</code>_(since v3.2.5)_</td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">-</td>
            <td scope="row" data-label="Notes">The configured <code>SURREAL_MEMORY_THRESHOLD</code> in bytes, or <code>0</code> when the memory guard is disabled. Constant for the life of the process.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_process_cpu_percent</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">-</td>
            <td scope="row" data-label="Notes">Aggregate process CPU %. May exceed <code>100</code> on multi-core hosts.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>target_info</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">resource attributes</td>
            <td scope="row" data-label="Notes">Emitted by the OTel SDK from the process <code>Resource</code>. Carries <code>service_edition</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>otel_scope_info</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">scope attributes</td>
            <td scope="row" data-label="Notes">Emitted by the OTel SDK per instrumentation scope.</td>
        </tr>
    </tbody>
</table>

The cadence of the process snapshot can be tuned with `SURREAL_PROCESS_METRICS_REFRESH_INTERVAL` (seconds; default `5`, floored at 1).

The four `surrealdb_process_memory_*_bytes` gauges are not on the [public allowlist](#public-metrics-allowlist), because together they show how close an instance is to refusing queries, so only an authenticated scrape returns them. The allocated and threshold gauges are available from 3.2.5, and the requested and reserved gauges from 3.3.0. The allocated, requested and reserved gauges report `0` in a build compiled without allocation tracking. Read together, they answer three questions:

- `surrealdb_process_memory_allocated_bytes / surrealdb_process_memory_threshold_bytes` is how close the instance is to the threshold. Once tracked memory passes it, queries fail with a memory threshold error.
- `surrealdb_process_memory_bytes - surrealdb_process_memory_allocated_bytes` is resident memory the tracker cannot see, such as allocator overhead, thread stacks and RocksDB memory outside its caches. This is the headroom to leave between `SURREAL_MEMORY_THRESHOLD` and a container memory limit.
- `surrealdb_process_memory_reserved_bytes - surrealdb_process_memory_requested_bytes` is internal fragmentation. Use the reserved gauge rather than the allocated one here, because the allocated gauge also counts the external reporters.

### Statement - `surrealdb.statement`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_statement_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>statement_type, outcome, error_class, namespace, database, user</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_statement_duration_seconds</code></td>
            <td scope="row" data-label="Type">histogram</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_statement_rows_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels">same as above. Recorded only when <code>result_rows &gt; 0</code>.</td>
        </tr>
    </tbody>
</table>

### Query - `surrealdb.query`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_query_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>outcome, error_class, namespace, database, user</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_query_duration_seconds</code></td>
            <td scope="row" data-label="Type">histogram</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
    </tbody>
</table>

### Transaction - `surrealdb.transaction`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>write, outcome, error_class, namespace, database, user</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_duration_seconds</code></td>
            <td scope="row" data-label="Type">histogram</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_kv_ops_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>op</code> (<code>get</code> / <code>scan</code> / <code>set</code> / <code>del</code> / …)</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_keys_read_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>outcome</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_keys_written_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>outcome</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_key_bytes_read_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>outcome</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_value_bytes_read_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>outcome</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_key_bytes_written_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>outcome</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_value_bytes_written_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>outcome</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_transaction_conflicts_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>namespace, database, user</code> only. Doubles as the retry-pressure signal - alert on <code>rate(...[5m])</code>.</td>
        </tr>
    </tbody>
</table>

### RPC - `surrealdb.rpc`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_rpc_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>rpc_method, outcome, error_class, namespace, database, user</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_rpc_duration_seconds</code></td>
            <td scope="row" data-label="Type">histogram</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
    </tbody>
</table>

### Auth - `surrealdb.auth`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_auth_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>auth_action, auth_scope, outcome, error_class, namespace, database, user</code></td>
        </tr>
    </tbody>
</table>

### Session - `surrealdb.session`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_session_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>session_action</code> (<code>connect</code> / <code>disconnect</code>), <code>protocol</code>, <code>service</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_session_active</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels"><code>protocol, service</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_session_duration_seconds</code></td>
            <td scope="row" data-label="Type">histogram</td>
            <td scope="row" data-label="Labels"><code>protocol, service</code></td>
        </tr>
    </tbody>
</table>

### Network - `surrealdb.network`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_network_received_bytes_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>protocol, namespace, database, user</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_network_sent_bytes_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
    </tbody>
</table>

### Bucket - `surrealdb.bucket`

_(Community)_ _(since v3.3.0)_

One event is recorded per object-store operation that a [file bucket](/docs/reference/query-language/statements/define/bucket.md) performs, including those behind the global bucket. Bucket operations run below the session layer, so these families carry no `namespace`, `database` or `user` labels. They are not on the public allowlist, so only an authenticated scrape returns them.

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
            <th scope="col">Notes</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_bucket_operations_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>backend, op, outcome</code></td>
            <td scope="row" data-label="Notes">Incremented once per operation, successful or not.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_bucket_sent_bytes_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels">same as above</td>
            <td scope="row" data-label="Notes">Bytes given to a successful <code>put</code> or <code>put_if_not_exists</code>. A <code>put_if_not_exists</code> that finds the file already present still counts its bytes.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_bucket_received_bytes_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels">same as above</td>
            <td scope="row" data-label="Notes">Bytes downloaded from the backend. Recorded only for a successful <code>get</code> that found the file.</td>
        </tr>
    </tbody>
</table>

- `backend` is the storage provider taken from the URL scheme: `s3`, `gcs`, `azure`, `file` or `memory`. It is never the bucket name.
- `op` is one of `put`, `put_if_not_exists`, `get`, `head`, `delete`, `exists`, `copy`, `copy_if_not_exists`, `rename`, `rename_if_not_exists` or `list`.
- `outcome` is `success` or `error`.

### HTTP - `surrealdb.http`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_http_request_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>http_request_method, http_route, http_response_status_code, outcome, error_class, namespace, database, user</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_http_request_duration_seconds</code></td>
            <td scope="row" data-label="Type">histogram</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_http_request_size_bytes_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_http_response_size_bytes_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_http_active_requests</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels"><code>http_request_method, http_route</code> (attribute-stripped to keep the gauge balanced)</td>
        </tr>
    </tbody>
</table>

### Live query - `surrealdb.live_query`

_(Community)_

`surrealdb_live_query_orphaned_total` increments when a live-query registration cannot be torn down cleanly after a WebSocket disconnect, leaving a catalog record that still receives notifications. Alert on sustained growth.

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_live_query_active</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels">-</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_live_query_notifications_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels">-</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_live_query_orphaned_total</code>_(since v3.2.0)_</td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>namespace</code>, <code>database</code></td>
        </tr>
    </tbody>
</table>

### Slow-query counter - `surrealdb.slow_query`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_slow_query_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels">Same labels as <code>surrealdb_statement_total</code>. Increments when statement duration ≥ <code>SURREAL_SLOW_QUERY_METRIC_THRESHOLD_MS</code> (default 1000 ms; <code>0</code> disables the counter).</td>
        </tr>
    </tbody>
</table>

The slow-query counter is a Community-edition signal - it bumps for every statement that crosses the threshold. The [Enterprise slow-query log pipeline](/docs/manage/observability/slow-query-logging.md) produces full records on top of this counter.

### GraphQL - `surrealdb.graphql`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_graphql_operation_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>operation_type</code> (<code>query</code> / <code>mutation</code> / <code>subscription</code>), <code>outcome</code>, <code>error_class</code>, <code>namespace</code>, <code>database</code>, <code>user</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_graphql_operation_duration_seconds</code></td>
            <td scope="row" data-label="Type">histogram</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
    </tbody>
</table>

### MCP - `surrealdb.mcp`

_(Community)_

<table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Type</th>
            <th scope="col">Labels</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_mcp_tool_invocation_total</code></td>
            <td scope="row" data-label="Type">counter</td>
            <td scope="row" data-label="Labels"><code>tool, transport, outcome, error_class, namespace, database, user</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_mcp_tool_duration_seconds</code></td>
            <td scope="row" data-label="Type">histogram</td>
            <td scope="row" data-label="Labels">same as above</td>
        </tr>
        <tr>
            <td scope="row" data-label="Name"><code>surrealdb_mcp_session_active</code></td>
            <td scope="row" data-label="Type">gauge</td>
            <td scope="row" data-label="Labels"><code>transport</code></td>
        </tr>
    </tbody>
</table>

### Storage backend - `surrealdb.storage.<backend>`

_(Community)_

Each enabled backend publishes a manifest of `u64` values through the transaction layer. The server bridge registers each entry as an observable gauge under `surrealdb.storage.<backend>.<metric>`; Prometheus renders them as `surrealdb_storage_<backend>_<metric>`. The set is backend-specific (RocksDB, in-memory, SurrealKV) and may change between releases - the canonical list at runtime is whatever the running build exposes at `/metrics`.

Treat these metrics as operator-only - they leak backend-specific implementation detail (compaction stats, block cache hits, etc.) that is rarely meaningful outside the operator persona.

### Cluster - `surrealdb.ds`

_(Enterprise)_ _(since v3.1.0)_

> [!NOTE]
> These instruments describe the distributed storage layer behind multi-node clusters, available on [SurrealDB Cloud Scale](https://surrealdb.com/pricing/scale) and in self-hosted Enterprise deployments. They apply when an Enterprise build runs that storage layer with at least one metrics reader configured.

The cluster family is registered by the Enterprise composer when the runtime has at least one metrics reader configured. Every instrument is operator-only - the [`PUBLIC_METRICS`](#public-metrics-allowlist) allowlist excludes the family in its entirety.

#### Network and transport

| Name | Type | Labels |
| --- | --- | --- |
| `surrealdb_ds_network_bytes_sent_bytes_total` | counter (`By`) | `peer` |
| `surrealdb_ds_network_bytes_received_bytes_total` | counter (`By`) | `peer` (omitted on reply-only variants) |
| `surrealdb_ds_network_packets_sent_total` | counter | `peer` |
| `surrealdb_ds_network_packets_received_total` | counter | `peer` (omitted on reply-only variants) |
| `surrealdb_ds_network_send_errors_total` | counter | `peer, kind` |
| `surrealdb_ds_messages_sent_total` | counter | `message_type, peer` |
| `surrealdb_ds_messages_received_total` | counter | `message_type, peer` (omitted on reply-only variants) |
| `surrealdb_ds_udp_reresolves_total` | counter | `reason` |

#### Transactions and consensus

| Name | Type | Labels |
| --- | --- | --- |
| `surrealdb_ds_transactions_committed_total` | counter | - |
| `surrealdb_ds_transactions_aborted_total` | counter | `reason` |
| `surrealdb_ds_occ_conflicts_total` | counter | - |
| `surrealdb_ds_consensus_path_total` | counter | `path` |
| `surrealdb_ds_consensus_fast_quorum_timeouts_total` | counter | - |
| `surrealdb_ds_consensus_stale_view_aborts_total` | counter | - |
| `surrealdb_ds_finalize_prepare_retries_total` | counter | - |
| `surrealdb_ds_operation_timeouts_total` | counter | `phase` |
| `surrealdb_ds_prepare_results_total` | counter | `role, result` |
| `surrealdb_ds_begin_replies_total` | counter | `role, result` (currently coordinator-side only) |
| `surrealdb_ds_reads_served_total` | counter | `source` |
| `surrealdb_ds_reads_begin_backoffs_total` | counter | - |

#### View management and recovery

| Name | Type | Labels |
| --- | --- | --- |
| `surrealdb_ds_view_changes_total` | counter | `outcome` |
| `surrealdb_ds_recovery_started_total` | counter | - |
| `surrealdb_ds_recovery_completed_total` | counter | `outcome` |
| `surrealdb_ds_recovery_transactions_applied_total` | counter | - |
| `surrealdb_ds_prepare_probes_total` | counter | `outcome` |

#### Garbage collection

| Name | Type | Labels |
| --- | --- | --- |
| `surrealdb_ds_gc_supervisor_ticks_total` | counter | - |
| `surrealdb_ds_gc_runs_total` | counter | `kind, outcome` |
| `surrealdb_ds_gc_records_collected_total` | counter | `kind` |
| `surrealdb_ds_gc_duration_seconds` | histogram | `kind, outcome` |

#### Live state (observable gauges)

Populated only after the replica is wired in via the enterprise observer install hook.

| Name | Type | Notes |
| --- | --- | --- |
| `surrealdb_ds_replica_state` | gauge | Encoded `Normal=0`, `ViewChange=1`, `Recovering=2`, `RecoveringCompleted=3`. |
| `surrealdb_ds_active_view_number` | gauge | Current TAPIR view number. |
| `surrealdb_ds_prepared_list_len` | gauge | Length of the prepared list. |
| `surrealdb_ds_records_len` | gauge | Length of the records list. |
| `surrealdb_ds_pending_finalises_len` | gauge | Number of pending finalises. |
| `surrealdb_ds_cluster_peers_unresolved` | gauge | Number of cluster peers currently unresolved. |

#### Cluster labels

| Key | Used on | Bounded values |
| --- | --- | --- |
| `peer` | `network_*`, `messages_*` | Stringified `NodeId` (interned). Cardinality bounded by cluster size. |
| `kind` | `network_send_errors`, `gc_runs`, `gc_records_collected`, `gc_duration_seconds` | `send_failed`, `serialisation`, `channel_closed`, `unresolved`, `no_endpoint`, `write_failed`, `other` (send errors); `upgrade_tentative`, `gc_records`, `repair_tentative_consensus`, `gc_pending_finalises`, `gc_unlogged_inflight`, `gc_finalize_prepare_decisions`, `backup_recovery` (GC). |
| `message_type` | `messages_sent`, `messages_received` | TAPIR message variants (closed set). |
| `path` | `consensus_path` | `fast`, `slow` |
| `phase` | `operation_timeouts` | `propose`, `finalize`, `broadcast`, `unlogged`, `recovery`, `inconsistent` |
| `role` | `prepare_results`, `begin_replies` | `replica`, `coordinator` |
| `result` | `prepare_results` | `ok`, `abort`, `abstain`, `retry` |
| `result` | `begin_replies` | `eligible`, `anterior_prepared`, `higher_committed` |
| `outcome` | `view_changes`, `recovery_completed`, `prepare_probes` | `triggered`, `completed`, `applied`, `retried`, `rebuild_failed` (view changes); `success`, `failure` (recovery); `committed`, `aborted`, `insufficient` (probes). |
| `source` | `reads_served` | `local`, `remote_eligible`, `remote_higher_committed` |
| `reason` | `transactions_aborted`, `udp_reresolves` | `conflict`, `abstain`, `other` (aborts); `unresolved`, `send_failure`, `recovery_view_change` (re-resolves). |

### Audit log pipeline self-metrics

_(Enterprise)_ _(since v3.1.0)_

Observable gauges registered when the [audit log pipeline](/docs/manage/observability/audit-logging.md) is enabled. The values are pulled at scrape time from atomic counters on the pipeline, so the cost is zero when nothing consumes the metric.

| Name | Type | Notes |
| --- | --- | --- |
| `surrealdb_audit_records` | gauge | Cumulative count of records successfully enqueued (observer → queue). |
| `surrealdb_audit_dropped` | gauge | Cumulative count of records dropped (overflow or queue closed). **Alert on any non-zero rate** - every drop is a lost record. |
| `surrealdb_audit_queue_depth` | gauge | Records currently buffered between observer and worker. Sustained depth above ~50% of `SURREAL_AUDIT_QUEUE_CAPACITY` indicates a slow sink. |
| `surrealdb_audit_appended` | gauge | Cumulative records the worker wrote to the sink. The gap to `surrealdb_audit_records` is queue depth plus append errors. |
| `surrealdb_audit_append_errors` | gauge | Cumulative sink-write failures. **Alert on any non-zero rate**. |

> These are exposed as gauges, not counters - the values are pulled live from atomic counters on the pipeline. The Prometheus name therefore has no `_total` suffix even though the underlying values are monotonic.

### Slow-query log pipeline self-metrics

_(Enterprise)_ _(since v3.1.0)_

The slow-query pipeline exposes the same shape as the audit pipeline under the `surrealdb.slow_query` scope:

| Name | Type | Notes |
| --- | --- | --- |
| `surrealdb_slow_query_records` | gauge | Cumulative records enqueued. |
| `surrealdb_slow_query_dropped` | gauge | Cumulative records dropped. **Alert on any non-zero rate**. |
| `surrealdb_slow_query_queue_depth` | gauge | Current queue depth. |
| `surrealdb_slow_query_appended` | gauge | Cumulative records written to the sink. |
| `surrealdb_slow_query_append_errors` | gauge | Cumulative sink-write failures. **Alert on any non-zero rate**. |

### Licence - `surrealdb.license`

_(Enterprise)_ _(since v3.3.0)_

These observable gauges describe the [licence](/docs/manage/enterprise.md#licence-key) state of an Enterprise node. They are the only place licence state is reported, and an anonymous scrape does not return them. A refused start is reported only through the exit status and the startup log, because the startup check runs before the HTTP listener is bound. Timestamps are absolute Unix seconds rather than countdowns, and a timestamp that does not apply is omitted rather than reported as `0`.

| Name | Type | Labels | Notes |
| --- | --- | --- | --- |
| `surrealdb_license_state` | gauge | `state` | One-hot: `1` for the current state and `0` for the others. `state` is one of `valid`, `expiry_grace`, `offline_grace`, `revoked` or `unlicensed`. Absent until the licence has been checked. |
| `surrealdb_license_expires_at_seconds` | gauge | - | When the licence expires. |
| `surrealdb_license_grace_ends_at_seconds` | gauge | - | When the grace period in effect ends. |
| `surrealdb_license_shutdown_at_seconds` | gauge | - | When the node shuts down, after a confirmed revocation or once the offline allowance has run out. The process exits at the first revalidation after this time, so read it as the earliest exit time. |
| `surrealdb_license_last_validated_at_seconds` | gauge | - | The signed timestamp of the most recent verified response from keygen.sh, fresh or cached. This is keygen.sh's clock, not the node's. |
| `surrealdb_license_generation` | gauge | - | Monotonic count of licence state updates, which tells an unchanged licence apart from a stalled reporter. |
| `surrealdb_license_enforcing` | gauge | - | `1` when the build acts on the licence verdict, `0` when it only reports it. |
| `surrealdb_license_trust_override` | gauge | - | `1` when the build's licence trust anchors were replaced at compile time. Always `0` on a released image. |

### Per-tenant rollups - `surrealdb.tenant`

_(Enterprise)_

The `surrealdb.tenant` meter scope is **reserved** for low-cardinality per-tenant rollups keyed on `(namespace, database)` only. No instruments are currently registered under it. Until rollups land, customer-tenant filtering must be done at a proxy by filtering the higher-cardinality labelled families on `namespace` / `database`.

## Public metrics allowlist

Anonymous scrapers on `/metrics` see only the metrics on the public allowlist. Adding a metric to this list requires a security review. The six allowlisted metrics are:

- `surrealdb_build_info`
- `surrealdb_process_uptime_seconds`
- `surrealdb_process_memory_bytes`
- `surrealdb_process_cpu_percent`
- `target_info`
- `otel_scope_info`

Root credentials unlock the full surface for an authenticated scrape.

### Recommended cloud whitelist

For cloud and multi-tenant deployments the recommendation is a three-tier whitelist:

- **Tier 1 - Always public.** The allowlisted six above.
- **Tier 2 - Customer-visible (per-tenant, via proxy filter).** A subset of labelled families exposed to customers showing their own usage. Every metric in this tier carries `namespace` and `database` labels and the cloud proxy MUST filter on those before responding to a customer request - there is no server-side mechanism today that restricts a labelled family to a single tenant. Strip the `user` label before exposing. Suggested subset: `surrealdb_statement_*`, `surrealdb_transaction_total`, `surrealdb_transaction_duration_seconds`, `surrealdb_transaction_conflicts_total`, `surrealdb_query_*`, `surrealdb_http_request_total`, `surrealdb_network_received_bytes_total`, `surrealdb_network_sent_bytes_total`, `surrealdb_live_query_active`, `surrealdb_slow_query_total`.
- **Tier 3 - Operator-only.** Everything else: all `surrealdb_ds_*`, all pipeline self-metrics, `surrealdb_rpc_*` (full method dimension), `surrealdb_auth_*` (failure rates can be probed by an attacker), `surrealdb_graphql_*`, `surrealdb_mcp_*`, `surrealdb_storage_*`, `surrealdb_bucket_*`, `surrealdb_license_*`, the tracked-memory gauges (`surrealdb_process_memory_allocated_bytes`, `_requested_bytes`, `_reserved_bytes` and `_threshold_bytes`), `surrealdb_http_active_requests` (real-time concurrency snapshot), and the KV-layer `surrealdb_transaction_*` counters. Reach these over an internal sidecar or private network with root credentials.

## Cardinality and sensitivity

| Flag | Where it applies | Mitigation |
| --- | --- | --- |
| **Tenant-identifying** | `namespace`, `database`, `user` on every labelled family | Strip or filter at the proxy before exposing to a customer. Anonymous scrapers cannot see any family carrying these labels because the families are absent from the public allowlist. |
| **High-cardinality** | `peer` (cluster family, bounded by cluster size), `http_route`, `rpc_method` | Fine internally. External exposure should be conditional on the route or method set already being public. |
| **Workload inference** | The allowlisted process gauges (uptime, memory, CPU) | Already on the allowlist, but in a single-tenant-per-pod deployment they correlate with tenant workload. Acceptable trade-off in most architectures. |
| **PII risk** | Audit and slow-query *records* carry SQL when `*_INCLUDE_SQL=true`. The pipeline self-metrics are safe; the records themselves go to the file sink and (opt-in) OTLP logs, not to `/metrics`. | Run the [three-pass redactor](/docs/manage/observability/audit-logging.md#redaction); alert on `*_append_errors`. |
| **Default-deny** | Multi-tenant deployments | Set `SURREAL_METRICS_ENABLED=false` on customer-facing pods; serve scrapes from an internal sidecar bound to a non-tenant-visible interface. |

## Alert hints

A starter set for production:

- `rate(surrealdb_statement_total{outcome="error"}[5m])` rising sharply against a baseline - application or operator error spike.
- `rate(surrealdb_transaction_conflicts_total[5m])` rising - OCC pressure or retry storms.
- `rate(surrealdb_ds_consensus_fast_quorum_timeouts_total[5m]) > 0` - consensus is falling through to the slow path.
- `surrealdb_ds_replica_state != 0` for more than a few seconds - a replica is in view-change or recovery.
- `surrealdb_ds_cluster_peers_unresolved > 0` for more than a startup window - DNS or connectivity gap.
- `rate(surrealdb_audit_dropped[5m]) > 0` or `rate(surrealdb_audit_append_errors[5m]) > 0` - audit records being lost.
- `surrealdb_audit_queue_depth` sustained above ~50% of `SURREAL_AUDIT_QUEUE_CAPACITY` - sink is falling behind.
- `surrealdb_process_memory_allocated_bytes / surrealdb_process_memory_threshold_bytes > 0.9 and surrealdb_process_memory_threshold_bytes > 0` sustained - tracked memory is close to the point where queries fail.
- `surrealdb_license_state{state="valid"} == 0` - an Enterprise node's licence is not currently verified as valid, so its next start may be refused. A `surrealdb_license_shutdown_at_seconds` series means an exit is scheduled.

## Migration from 3.0

The 3.1 release renamed the metric surface to a consistent `surrealdb.*` namespace. Legacy Prometheus names that operators may still see in old dashboards, and their current replacements:

<table>
    <thead>
        <tr>
            <th scope="col">3.0 name</th>
            <th scope="col">3.1 replacement</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_statements_completed_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_statement_total{outcome="success"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_statement_errors_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_statement_total{outcome="error"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_queries_completed_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_query_total{outcome="success"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_query_errors_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_query_total{outcome="error"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_query_dim_duration_seconds</code></td>
            <td scope="row" data-label="New"><code>surrealdb_query_duration_seconds</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_transactions_completed_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_transaction_total{outcome="success"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_transaction_writes_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_transaction_total{write="true",outcome="success"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_transaction_errors_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_transaction_total{outcome="error"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_rpcs_completed_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_rpc_total{outcome="success"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_rpc_errors_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_rpc_total{outcome="error"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_auth_attempts_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_auth_total</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_auth_failures_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_auth_total{outcome!="success"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_http_requests_completed_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_http_request_total{outcome="success"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_http_request_errors_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_http_request_total{outcome="error"}</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_http_dim_active_requests</code></td>
            <td scope="row" data-label="New"><code>surrealdb_http_active_requests</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_network_dim_received_bytes_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_network_received_bytes_total</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>surrealdb_network_dim_sent_bytes_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_network_sent_bytes_total</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>http_server_request_duration_milliseconds</code></td>
            <td scope="row" data-label="New"><code>surrealdb_http_request_duration_seconds</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>http_server_request_count_total</code></td>
            <td scope="row" data-label="New"><code>surrealdb_http_request_total</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>http_server_active_requests</code></td>
            <td scope="row" data-label="New"><code>surrealdb_http_active_requests</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>rpc_server_request_duration_milliseconds</code></td>
            <td scope="row" data-label="New"><code>surrealdb_rpc_duration_seconds</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Old"><code>rpc_server_active_connections</code></td>
            <td scope="row" data-label="New"><code>surrealdb_session_active{protocol="websocket"}</code></td>
        </tr>
    </tbody>
</table>

The `otel_scope_name` label also changes shape in 3.1: scopes are now signal-domain (`surrealdb.statement`, `surrealdb.query`, …) rather than edition-tier (`surrealdb.community`, `surrealdb.enterprise`). Dashboards that filtered by scope should switch to the metric name (which now carries the same information, since scopes mirror the family prefix) or to the `service.edition` resource attribute, which is the authoritative edition signal.

---

Source: https://surrealdb.com/docs/manage/observability/observability

# Observability (metrics and Prometheus)

Unified OpenTelemetry metrics common to Community and Enterprise. The `GET /metrics` endpoint, scraper authentication, naming, and migration.

For server log output (stderr, files, JSON, sockets, slow-query lines), see [Logging](/docs/manage/observability/logging.md).

Use the tabs below depending on your server version. For OTLP (push), process intervals, and legacy SemConv instruments on the wire, see [Telemetry (OTLP)](/docs/manage/observability/telemetry.md). For cluster metrics, audit and slow-query file sinks, and OTLP log export opt-ins, see [Enterprise observability](/docs/manage/observability/enterprise-observability.md).

**SurrealDB 3.1 and later**

SurrealDB exposes a single, unified observability surface backed by OpenTelemetry. Every metric is recorded once and fanned out to whichever exporters you configure:

- **Prometheus (pull)** - text exposition at `GET /metrics` (scrapers call your server). The usual path if you already run Prometheus.
- **OTLP (push)** - when `SURREAL_TELEMETRY_PROVIDER=otlp` is set, the process pushes metrics, traces, and (when enabled) log records to a collector.

**SurrealDB Enterprise** can additionally attach durable **audit** and **slow-query** pipelines and **cluster** metrics when distributed storage is the backend (Cloud Scale or self-hosted Enterprise); OTLP export of audit and slow-query **log** records is **opt-in** per pipeline. See [Enterprise observability](/docs/manage/observability/enterprise-observability.md).

For OTLP configuration, push intervals, process gauges, and backward-compatible HTTP/RPC instruments, see [Telemetry (OTLP)](/docs/manage/observability/telemetry.md).

To inspect async tasks and scheduling inside the Tokio runtime, see [Tokio console](/docs/manage/observability/tokio-console.md).

## Unified model (summary)

```text
HTTP / WS / Executor → ExecutionObserver → FanOutObserver
                                            ├── MetricsObserver         (labelled surrealdb.*)
                                            ├── DsMetrics               (Enterprise, when active)
                                            ├── AuditObserver           (Enterprise, when configured)
                                            └── SlowQueryObserver       (Enterprise, when configured)
                                                       │
                                                       ▼
                                            SdkMeterProvider + SdkLoggerProvider
                                                       │
                                       ┌───────────────┼────────────────┐
                                       ▼               ▼                ▼
                          Prometheus text       OTLP metrics push   OTLP logs / traces push
                          exporter (/metrics)
```

Metrics are recorded once in labelled form. A single `surrealdb.statement` counter, for example, carries `statement_type`, `outcome`, `namespace`, `database`, and `user` in **Community and Enterprise**; unresolved tenant context appears as a `"-"` sentinel so series stay stable. Build flavour (`community` vs `enterprise`) is exposed on the OpenTelemetry **resource** as `service.edition` (for example on `target_info` in Prometheus) - use that attribute for edition-aware dashboards instead of filtering on meter scope alone.

When no exporter is configured, the fan-out short-circuits so the hot path stays fast.

## Naming (summary)

Instruments use dotted OpenTelemetry-style names (for example `surrealdb.statement.duration`) under signal-domain scopes (`surrealdb.statement`, `surrealdb.query`, …). The Prometheus text exporter maps those to underscore names with conventional `_total`, `_seconds`, and `_bytes` suffixes. Attribute keys with dots are sanitised for Prometheus.

Many historical Prometheus series names and label shapes changed in SurrealDB 3.1. Dashboards that pre-date that release should be updated using the migration table in [`doc/OBSERVABILITY.md`](https://github.com/surrealdb/surrealdb/blob/main/doc/OBSERVABILITY.md) (and the Enterprise companion for **[E]**-only series).

## Metrics on or off

| Switch | Default | Effect |
| --- | --- | --- |
| `SURREAL_METRICS_ENABLED` | `true` | When `false`, the `/metrics` route is not mounted |
| `SURREAL_TELEMETRY_PROVIDER` | unset | Set to `otlp` to enable the OTLP push pipeline (metrics, logs, traces) |
| `SURREAL_TELEMETRY_DISABLE_METRICS` | `false` | When `true`, suppresses the OTLP metrics reader (logs and traces unaffected) |
| `SURREAL_TELEMETRY_DISABLE_TRACING` | `false` | When `true`, suppresses the OTLP trace exporter only |

When metrics are disabled, `/metrics` returns 404.

## Collecting `/metrics` and security

Anonymous scrapes only receive families on a public allowlist (aggregate process signals). Root credentials unlock the full Prometheus surface for that process; namespace and database users are not treated as authenticated for this endpoint. Dimensional families such as `surrealdb_statement_total` are intentionally excluded from anonymous output because they carry tenant labels - use OTLP or an authenticated scrape for those. See [`doc/OBSERVABILITY.md`](https://github.com/surrealdb/surrealdb/blob/main/doc/OBSERVABILITY.md) for the `PUBLIC_METRICS` list and security discussion.

## Audit and slow-query (summary)

**Community** builds do not ship the enterprise audit and slow-query file pipelines or their pipeline self-metrics on Prometheus.

**Enterprise** can write tamper-aware NDJSON to disk **and** optionally emit the same redacted records as OpenTelemetry logs when `SURREAL_AUDIT_OTEL_EXPORT=true` / `SURREAL_SLOW_QUERY_OTEL_EXPORT=true` (both default `false`). Redaction runs on the executor thread before the queue so every sink sees the same scrubbed payload. See [Enterprise observability](/docs/manage/observability/enterprise-observability.md) and `SURREAL_AUDIT_*` / `SURREAL_SLOW_QUERY_*` in [Environment variables](/docs/reference/cli/db/environment-variables.md).

## Multi-tenant guidance

Treat `/metrics` as operator-only in multi-tenant deployments: avoid exposing the main listener to untrusted scrapers, prefer disabling metrics if you cannot isolate the endpoint, and never give tenants root credentials used for the authenticated metric view.

## Local development

Compare anonymous and authenticated scrapes:

```bash
curl http://127.0.0.1:8000/metrics
curl -u root:secret http://127.0.0.1:8000/metrics
```

Disable metrics entirely:

```bash
SURREAL_METRICS_ENABLED=false surreal db start ...
```

Push metrics, logs, and traces to a local collector:

```bash
SURREAL_TELEMETRY_PROVIDER=otlp \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
surreal db start ...
```

For OTLP-focused setup and version tabs, see [Telemetry (OTLP)](/docs/manage/observability/telemetry.md). For catalogue-level detail, use the in-repository `doc/OBSERVABILITY.md` / `doc/TELEMETRY.md` files linked above (and the Enterprise `doc/OBSERVABILITY.md` where applicable). For every instrument name, label, and alert hint on this site, see the [Metrics reference](/docs/manage/observability/metrics.md).

**Before SurrealDB 3.1**

Prior to SurrealDB 3.1, operators often saw overlapping metric paths: built-in Prometheus exposition could run alongside a separate OpenTelemetry pipeline oriented around HTTP/RPC semantic conventions. The same request could therefore be reflected more than once, under different scopes and label keys (for example `http.server.*` / `rpc.server.*` versus `surrealdb.*`-style families).

Many deployments also emitted parallel families for the same logical signal - aggregate counters or histograms plus higher-cardinality “dimensional” variants (names with a `*_dim_*` segment, or separate `*_errors_total` series). Dashboards and alerts had to choose which family to query, and mixing them could double-count.

Edition (`community` vs `enterprise`) could appear on meter scope identifiers (for example in `otel_scope_name` after export to Prometheus). That tied dashboards to build flavour at the scope level and made renames risky.

Tenant context (namespace, database, user, and similar) could be attached to labels in more ad hoc ways across recording sites, so you should treat unauthenticated `/metrics` scrapes as potentially sensitive in multi-tenant layouts unless you had a deliberate proxy or allowlist in front of the endpoint.

For the OTLP side of the same era (push interval, `http.server.*` / `rpc.server.*` instruments, deprecated variables), use the Before SurrealDB 3.1 tab on [Telemetry (OTLP)](/docs/manage/observability/telemetry.md).

When you upgrade to 3.1 or later, replan dashboards and alerts using the migration material in [`doc/OBSERVABILITY.md`](https://github.com/surrealdb/surrealdb/blob/main/doc/OBSERVABILITY.md) in the open-source repository (and the Enterprise companion, if you run SurrealDB Enterprise).

---

Source: https://surrealdb.com/docs/manage/observability/slow-query-logging

# Slow-query logging

The Enterprise slow-query log: qualifying, rotation and redaction.

_(Enterprise)_
_(since v3.1.0)_

The slow-query log captures the full statement text and execution context for queries that cross a configurable duration threshold. It is part of SurrealDB Enterprise and is **off by default** - set `SURREAL_SLOW_QUERY_SINK=file`, `SURREAL_SLOW_QUERY_FILE_PATH` and `SURREAL_SLOW_QUERY_THRESHOLD_MS` to enable it.

The slow-query pipeline is architecturally identical to the [audit log pipeline](/docs/manage/observability/audit-logging.md) - same observer, same queue, same worker, same NDJSON file format, same optional OTel logs export, same hash chaining, same three-pass redaction. The intentional symmetry keeps operator runbooks short. The two pipelines differ in defaults and intent:

- **Audit records are compliance data.** Default overflow policy is `block` (preserve at the cost of latency); typical configurations also enable hash chaining.
- **Slow-query records are triage data.** Default overflow policy is `drop` (prefer dropping over busy-yielding the executor on a hot path). Hash chaining is rarely needed.

The full set of configuration variables lives on the [configuration reference](/docs/manage/observability/configuration.md#slow-query-log-knobs).

## Relationship to the slow-query counter

Two related instruments cover slow queries:

- The `surrealdb_slow_query_total` counter (Community edition; see [metrics reference](/docs/manage/observability/metrics.md#slow-query-counter---surrealdbslow_query)) increments for every statement above `SURREAL_SLOW_QUERY_METRIC_THRESHOLD_MS` (default 1000 ms). It is cheap and always-on when metrics are enabled.
- The slow-query log records on this page capture the full statement text and context for queries above `SURREAL_SLOW_QUERY_THRESHOLD_MS` (no default - required when the sink is enabled). This is opt-in and writes to a file sink.

The two thresholds are independent. Most deployments set the metric threshold at a high value (the counter is a sanity-check signal) and the log threshold lower (to capture the long tail of triage candidates).

## How a query qualifies

A statement is captured when its wall-clock duration is `≥ SURREAL_SLOW_QUERY_THRESHOLD_MS`. The pipeline only registers an observer when the sink is enabled, so the cost of slow-query capture is zero when `SURREAL_SLOW_QUERY_SINK=none` (the default).

## Record shape

Slow-query records share the audit envelope (`ts`, `event_type`, `outcome`, `duration_ms`, identity context, optional `sql`, optional hash chain fields) and additionally carry:

- `statement_type` - the statement category (`select`, `update`, `create`, …).
- `threshold_ms` - the threshold the query crossed, for forensic context.
- `result_rows` - number of records produced (when applicable).

The `event_type` field is always `slow_query`. A captured record looks like:

```json
{
  "ts": "2026-03-04T10:23:11.482Z",
  "event_type": "slow_query",
  "outcome": "success",
  "statement_type": "select",
  "duration_ms": 512,
  "threshold_ms": 250,
  "result_rows": 1842,
  "namespace": "acme",
  "database": "prod",
  "user": "svc_orders",
  "sql": "SELECT * FROM orders WHERE status = 'shipped' FETCH customer"
}
```

The `sql` field is included by default (`SURREAL_SLOW_QUERY_INCLUDE_SQL=true`). Throughput-sensitive deployments can set it to `false` to skip the per-statement `to_sql()` rendering, at the cost of losing SQL context on captures.

## OpenTelemetry logs export

When `SURREAL_SLOW_QUERY_OTEL_EXPORT=true` each captured record is also emitted as an OTel `LogRecord` on the SDK logger provider:

- **Event name:** `surrealdb.slow_query.statement`.
- **Severity:** `WARN` (fixed - slow path is degraded by definition).
- **Body:** a short human-readable string, for example `"slow select 500ms (threshold 100ms)"`.
- **Attributes:** the structured fields from the record envelope (`db.namespace`, `db.name`, `db.user`, `db.statement`, `surrealdb.statement_type`, `surrealdb.outcome`, `surrealdb.duration_ms`, `surrealdb.threshold_ms`, `surrealdb.result_rows`).

Like the audit pipeline, OTel export is off by default. The file sink is the primary path.

## Rotation and durability

Same knobs as the audit pipeline, prefixed `SURREAL_SLOW_QUERY_*`:

- File mode `0600` on Unix. The parent directory must exist or startup fails.
- Size-based rotation at `SURREAL_SLOW_QUERY_FILE_ROTATE_BYTES` (default 256 MiB).
- `SURREAL_SLOW_QUERY_FILE_ROTATE_KEEP` rotation generations retained (default `8`).
- Mid-stream fsync cadence from `SURREAL_SLOW_QUERY_FSYNC_EVERY` (default `0`). Rotation and graceful shutdown always fsync regardless.

## Hash chaining

Hash chaining works as it does on the audit pipeline. `SURREAL_SLOW_QUERY_HASH_CHAIN=true` adds `prev_hash` / `hash` SHA-256 fields and requires `SURREAL_SLOW_QUERY_FSYNC_EVERY=1`. Most operators leave hash chaining off for slow-query data - the pipeline is triage tooling, not a compliance record, and the per-record fsync cost is rarely worth paying for a `drop`-default pipeline. See the audit log [Hash chaining](/docs/manage/observability/audit-logging.md#hash-chaining) section for the full semantics.

## Redaction

Redaction works as it does on the audit pipeline, under `SURREAL_SLOW_QUERY_REDACT_LITERALS`, `SURREAL_SLOW_QUERY_REDACT_TABLES` and `SURREAL_SLOW_QUERY_REDACT_REGEX`. Three layered passes run synchronously on the executor thread before the record reaches the queue. See [Redaction](/docs/manage/observability/audit-logging.md#redaction) on the audit log page for the full mechanics.

## Overflow semantics

The slow-query pipeline defaults to `drop` - a record that cannot be enqueued is discarded and the `surrealdb_slow_query_dropped` gauge increments. The reasoning is that the executor is on a hot path during a slow query (by definition), and busy-yielding it to preserve a triage record is a poor trade-off.

Operators who need lossless slow-query capture can set `SURREAL_SLOW_QUERY_OVERFLOW=block`. The trade-off is the same as on the audit pipeline - the policy uses a bounded busy-yield loop, not a wall-clock time-bound, and offers no lossless guarantee on a `current_thread` runtime.

Whichever policy is configured, alert on `rate(surrealdb_slow_query_dropped[5m]) > 0` and `rate(surrealdb_slow_query_append_errors[5m]) > 0` - both indicate records were lost.

## Pipeline self-metrics

Five observable gauges expose the live state of the pipeline, mirroring the audit equivalents:

| Metric | Notes |
| --- | --- |
| `surrealdb_slow_query_records` | Cumulative records successfully enqueued. |
| `surrealdb_slow_query_dropped` | Cumulative records dropped. **Alert on any non-zero rate.** |
| `surrealdb_slow_query_queue_depth` | Records currently buffered between observer and worker. |
| `surrealdb_slow_query_appended` | Cumulative records the worker wrote to the sink. |
| `surrealdb_slow_query_append_errors` | Cumulative sink-write failures. **Alert on any non-zero rate.** |

## Related references

- [Configuration → Slow-query log knobs](/docs/manage/observability/configuration.md#slow-query-log-knobs) - every slow-query environment variable.
- [Audit logging](/docs/manage/observability/audit-logging.md) - the sister pipeline for compliance data.
- [Metrics reference → Slow-query counter](/docs/manage/observability/metrics.md#slow-query-counter---surrealdbslow_query) - the Community-edition counter that bumps when a statement crosses `SURREAL_SLOW_QUERY_METRIC_THRESHOLD_MS`.
- [Metrics reference → Slow-query pipeline self-metrics](/docs/manage/observability/metrics.md#slow-query-log-pipeline-self-metrics) - the five gauges in the metric catalogue.

---

Source: https://surrealdb.com/docs/manage/observability/telemetry

# Telemetry (OTLP)

OTLP push export of metrics, logs, and traces. Intervals, backward-compatible instruments, and differences before and after SurrealDB 3.1.

This page covers push export using the OpenTelemetry Protocol (OTLP): your server sends signals to a collector. For pull scraping of `GET /metrics` (Prometheus), see [Observability (metrics and Prometheus)](/docs/manage/observability/observability.md).

**SurrealDB 3.1 and later**

From SurrealDB 3.1, OpenTelemetry is the single source of truth for metrics and, where configured, for log records emitted on the shared logger provider - including audit and slow-query events on **SurrealDB Enterprise** when you opt in with `SURREAL_AUDIT_OTEL_EXPORT` / `SURREAL_SLOW_QUERY_OTEL_EXPORT`. One meter provider and one logger provider route measurements to multiple exporters, including the Prometheus text exposition at [`GET /metrics`](/docs/manage/observability/observability.md). An OTLP subscriber receives the same **metric** surface that a fully authenticated Prometheus scrape sees, plus traces and any **log** signals you have enabled (subject to the `/metrics` allowlist only for the pull path, not for OTLP).

For Prometheus pull - allowlists, naming, migration from older series, multi-tenant guidance, and Community versus Enterprise context - see [Observability (metrics and Prometheus)](/docs/manage/observability/observability.md) and [Enterprise observability](/docs/manage/observability/enterprise-observability.md).

## What flows over OTLP

| Signal | Source | Carries |
| --- | --- | --- |
| Metrics | Unified meter provider | Every instrument also surfaced on `/metrics`, plus the legacy `http.server.*` / `rpc.server.*` instruments kept for backward compatibility. |
| Logs | Unified logger provider | **Enterprise:** audit (`surrealdb.audit`) and slow-query (`surrealdb.slow_query`) records when the file pipeline is enabled **and** the matching `SURREAL_*_OTEL_EXPORT` flag is `true`. Severity mapping is summarised on [Observability (metrics and Prometheus)](/docs/manage/observability/observability.md) and [Enterprise observability](/docs/manage/observability/enterprise-observability.md). |
| Traces | `tracing` → OpenTelemetry bridge | Instrumented spans; enterprise builds may enrich spans with tenant attributes when enabled. |

Configure the OTLP push pipeline with:

```text
SURREAL_TELEMETRY_PROVIDER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317   # gRPC by default
```

When `SURREAL_TELEMETRY_PROVIDER` is unset (or any value other than `otlp`), OTLP exporters are not built; metrics still flow to Prometheus when `/metrics` is enabled. **SurrealDB Enterprise** audit and slow-query **file** sinks are independent of OTLP - they keep writing whenever configured.

When `SURREAL_TELEMETRY_DISABLE_METRICS=true`, the OTLP metrics reader is skipped while logs and traces continue to push.

When `SURREAL_TELEMETRY_DISABLE_TRACING=true`, the OTLP trace exporter is skipped while metrics and logs continue to push.

## Push interval

Metric push frequency follows the OpenTelemetry specification and is controlled by `OTEL_METRIC_EXPORT_INTERVAL` (milliseconds; default `60000`). The SDK reads this at startup and applies it to the periodic reader behind OTLP metrics. Sub-minute intervals (10-15 s) are common when you want responsive dashboards.

## Process metric freshness

`surrealdb.process.memory` and `surrealdb.process.cpu_percent` are observable gauges backed by a process-wide cache. A background task refreshes the cache on a fixed cadence so values stay fresh whether scrapers hit `/metrics` or OTLP pushes on its own schedule.

| Variable | Default | Notes |
| --- | --- | --- |
| `SURREAL_PROCESS_METRICS_REFRESH_INTERVAL` | `5` | Refresh interval in seconds. Tighter intervals reduce staleness but can make `cpu_percent` noisier (it is computed as a delta since the last refresh). Floored at one second. |

The task runs only when at least one metrics reader is configured (Prometheus and/or OTLP). OTLP-only deployments get the same freshness guarantee as Prometheus scrapers.

## Histogram bucket views

The meter provider applies three default views:

- Instruments named `*.duration` with unit seconds use a quasi-exponential bucket family from 5 ms to 30 s.
- Instruments named `*.duration` with unit milliseconds use a parallel millisecond-scale family for the legacy HTTP/RPC pipeline.
- Instruments named `*.size` with unit bytes use a 1 KiB - 100 MiB byte family.

Operators with custom views can override these before building the provider; refer to the OpenTelemetry SDK documentation.

## Backward-compatibility instrument set

Legacy OpenTelemetry HTTP/RPC instruments are still recorded under separate meter scopes (`surrealdb.http`, `surrealdb.rpc`). They coexist with the new `surrealdb.*` families so existing OTLP dashboards that pivot on semantic-convention names keep working.

| Instrument | Kind | Unit | Labels |
| --- | --- | --- | --- |
| `http.server.active_requests` | UpDownCounter\<i64> | - | `http.request.method`, `http.route`, `network.protocol.{name,version}` |
| `http.server.request.count` | Counter\<u64> | - | as above + `http.response.status_code` |
| `http.server.request.duration` | Histogram\<u64> | `ms` | as `http.server.request.count` |
| `http.server.request.size` | Histogram\<u64> | `By` | as `http.server.request.count` |
| `http.server.response.size` | Histogram\<u64> | `By` | as `http.server.request.count` |
| `rpc.server.active_connections` | UpDownCounter\<i64> | - | `rpc.service` |
| `rpc.server.connection.count` | Counter\<u64> | - | `rpc.service` (incremented on connect only) |
| `rpc.server.request.duration` | Histogram\<u64> | `ms` | `rpc.service`, `rpc.method`, `rpc.error` |
| `rpc.server.request.size` | Histogram\<u64> | `By` | `rpc.service` (per WebSocket frame) |
| `rpc.server.response.size` | Histogram\<u64> | `By` | `rpc.service` (per WebSocket frame) |

## Deprecated environment variables

The following variables are still parsed for backwards compatibility but are no longer applied. The server logs a deprecation warning at startup if either is set:

- `SURREAL_TELEMETRY_NAMESPACE` - the `namespace` attribute was removed from telemetry metrics because it identifies tenants in multi-tenant deployments.
- `SURREAL_TELEMETRY_RPC_LIVE_ID` - per-notification OTLP attribution by `rpc.live_id` was removed when WebSocket telemetry was unified into the execution observer pipeline.

## Local development

From the repository root, start the observability stack under `dev/docker` (collector, Grafana, Prometheus, Tempo, and Loki):

```bash
docker compose -f dev/docker/compose.yaml up -d
SURREAL_TELEMETRY_PROVIDER=otlp OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317" surreal db start
```

Open `http://localhost:3000` in a browser; default Grafana credentials are `admin` / `admin`.

For OpenTelemetry log and trace verbosity, file output, sockets, and other logging flags used alongside this stack, see [Logging](/docs/manage/observability/logging.md) and [`surreal db start`](/docs/reference/cli/db/commands/start.md).

**Before SurrealDB 3.1**

SurrealDB can be monitored by enabling built-in observability.

## Enable observability

To enable observability, the `SURREAL_TELEMETRY_PROVIDER` environment variable has to be set to `otlp`. If set to anything else, no observability will be available.

If enabled, SurrealDB sends metrics and/or traces to an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/). Configuration of the collector follows the [OpenTelemetry environment variable conventions](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/). The most important variable is [`OTEL_EXPORTER_OTLP_ENDPOINT`](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/#otel_exporter_otlp_endpoint). By default this points at localhost. Set it to the gRPC endpoint of your collector. For example, if your collector named `my-collector` runs in Kubernetes in the `monitoring` namespace:

```text
OTEL_EXPORTER_OTLP_ENDPOINT="http://my-collector.monitoring.svc.cluster.local:4317"
```

Metrics can be disabled (even if `SURREAL_TELEMETRY_PROVIDER` is set to `otlp`) by setting `SURREAL_TELEMETRY_DISABLE_METRICS` to `true`. Similarly, traces can be disabled with `SURREAL_TELEMETRY_DISABLE_TRACING=true`.

## Metrics

Metrics are gathered every minute and sent to the collector. The following metrics are present:

<table>
    <thead>
        <tr>
            <th colspan="1" scope="col">Name</th>
            <th colspan="1" scope="col">[Instrument](https://opentelemetry.io/docs/concepts/signals/metrics/#metric-instruments)</th>
            <th colspan="1" scope="col">Explanation</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="1" scope="row" data-label="Metric name">
                rpc.server.duration
            </td>
            <td colspan="1" scope="row" data-label="Type">
                histogram
            </td>
            <td colspan="1" scope="row" data-label="Explanation">
                Measures duration of inbound RPC requests in milliseconds
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Metric name">
                rpc.server.active_connections
            </td>
            <td colspan="1" scope="row" data-label="Type">
                counter
            </td>
            <td colspan="1" scope="row" data-label="Explanation">
                The number of active WebSocket connections
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Metric name">
                rpc.server.response.size
            </td>
            <td colspan="1" scope="row" data-label="Type">
                histogram
            </td>
            <td colspan="1" scope="row" data-label="Explanation">
                Measures the size of HTTP response messages
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Metric name">
                http.server.duration
            </td>
            <td colspan="1" scope="row" data-label="Type">
                histogram
            </td>
            <td colspan="1" scope="row" data-label="Explanation">
                The HTTP server duration in milliseconds
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Metric name">
                http.server.active_requests
            </td>
            <td colspan="1" scope="row" data-label="Type">
                counter
            </td>
            <td colspan="1" scope="row" data-label="Explanation">
                The number of active HTTP requests
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Metric name">
                http.server.request.size
            </td>
            <td colspan="1" scope="row" data-label="Type">
                histogram
            </td>
            <td colspan="1" scope="row" data-label="Explanation">
                Measures the size of HTTP request messages
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Metric name">
                http.server.response.size
            </td>
            <td colspan="1" scope="row" data-label="Type">
                histogram
            </td>
            <td colspan="1" scope="row" data-label="Explanation">
                Measures the size of HTTP response messages
            </td>
        </tr>
    </tbody>
</table>

The metrics are shown here in the form required by the [OpenTelemetry Metrics Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/) with a `.` separator. When ingested into Prometheus the `.` separator is [replaced](https://prometheus.io/blog/2024/03/14/commitment-to-opentelemetry/#support-utf-8-metric-and-label-names) with `_`. For example `rpc.server.active_connections` becomes `rpc_server_active_connections`.

For async runtime introspection (tasks, polls, scheduling), you can use the [Tokio console](/docs/manage/observability/tokio-console.md) which is separate from OTLP.

For server logging (levels, JSON, files, sockets, slow-query logging), see [Logging](/docs/manage/observability/logging.md).

For async runtime debugging with the Tokio console (tasks, poll histograms), see [Tokio console](/docs/manage/observability/tokio-console.md). That workflow sits beside OTLP and is not version-split in the tabs above.

For diagrams, histogram bucket rules, deprecated variables, and the full OTLP signal matrix in prose form, see [`doc/TELEMETRY.md`](https://github.com/surrealdb/surrealdb/blob/main/doc/TELEMETRY.md) in the open-source repository alongside [`doc/OBSERVABILITY.md`](https://github.com/surrealdb/surrealdb/blob/main/doc/OBSERVABILITY.md) for `/metrics`, allowlists, and metric catalogues. The Enterprise distribution ships an extended `doc/OBSERVABILITY.md` with **[C]** / **[E]** markers - summarised on [Enterprise observability](/docs/manage/observability/enterprise-observability.md).

---

Source: https://surrealdb.com/docs/manage/observability/tokio-console

# Tokio console

Inspect the async runtime with the Tokio console: tasks and polls.

_(since v3.0.0)_

[Telemetry (OTLP)](/docs/manage/observability/telemetry.md) and [`GET /metrics`](/docs/manage/observability/observability.md) (Prometheus pull) tell you what the database is doing from the outside (requests, statements, resources). The [Tokio console](https://github.com/tokio-rs/console) shows what the Tokio runtime is doing on the inside: which async tasks exist, how long they spend polled versus waiting, and where scheduling stalls appear. It is aimed at contributors and advanced operators debugging performance or deadlocks, not at replacing production metrics.

SurrealDB embeds Tokio console support behind environment variables. It is disabled by default; enable it only on trusted hosts (for example local development or a dedicated staging instance).

## Install the console CLI

The server exposes data to the console subscriber; you still need the Tokio console client on your machine.

1. [Install Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) if you do not already have them.
2. Install the CLI: `cargo install --locked tokio-console`
3. Start SurrealDB with console support enabled (see below), then run `tokio-console` in another terminal. By default it connects to `127.0.0.1:6669`.

## Enable console support on the server

Set the following before starting `surreal`:

* `SURREAL_TOKIO_CONSOLE_ENABLED` - set to `true` to turn on the subscriber.
* `SURREAL_TOKIO_CONSOLE_SOCKET_ADDR` - optional; overrides the default listen address `127.0.0.1:6669`.
* `SURREAL_TOKIO_CONSOLE_RETENTION` - optional; overrides the default `6s` (six seconds) for how long to [retain data for completed events](https://docs.rs/console-subscriber/0.4.1/console_subscriber/struct.Builder.html#method.retention).

The socket listens only on the address you configure; keep it on loopback unless you fully understand the exposure (the stream describes internal task behaviour).

## What you will see

![A screenshot of the Tokio console showing active SurrealDB tasks.](~/assets/img/surrealdb/reference-guide/tokio_console_1.png)

![A screenshot of the Tokio console showing poll times by percentile.](~/assets/img/surrealdb/reference-guide/tokio_console_2.png)

Typical uses for the tokio console are: spotting tasks that rarely get polled, comparing poll percentiles after a change, and correlating spikes in load with specific async workloads.

---

Source: https://surrealdb.com/docs/manage/organisations

# Organisations

The container for instances, members, usage, and billing. What each section of the organisation view holds.

An organisation groups instances, members, usage, and billing.

Every instance, membership, and invoice belongs to exactly one organisation. Nobody in one organisation can see or change the resources of another.

Create one organisation for each company, department, or environment you keep apart. Separate organisations for production and development each get their own member list and their own invoice.

## Create an organisation

1. Open the overview page in [SurrealDB Studio](https://studio.surrealdb.com).
2. Select **Create organisation**.
3. Enter a name for the organisation.
4. Confirm the name.

You become the [owner](/docs/manage/organisations/members-and-roles.md), and Studio opens the organisation overview. The breadcrumb at the top of Studio switches between the organisations you belong to.

To create an organisation from a terminal, run `surreal cloud org create`. `surreal cloud org use` sets which organisation later commands apply to. See [surreal cloud organisations](/docs/explore/cli/cloud/organisations.md).

## The organisation view

The overview page shows everything the organisation holds. The sidebar leads into each area.

![The overview page for the Acme Corp organisation in SurrealDB Studio, listing the api-production, api-staging and analytics-eu instances with a Deploy new instance button, the support-agent and research-agent Agent Memory contexts below them, and a Resources panel linking to documentation, SurrealDB University, AI agents and community, with a sidebar holding Overview, Instances, Contexts, Connections, Team, Billing, Support, Usage and Settings.](~/assets/img/surrealdb/manage/studio-overview.webp)

| Section | What it holds |
| --- | --- |
| **Instances** | Every [instance](/docs/manage/instances.md) in the organisation. Deploy, open, and filter them here. |
| **Team** | [Members, invitations, and roles](/docs/manage/organisations/members-and-roles.md). |
| **Billing** | [Billing details, payment method, discount codes, and invoices](/docs/manage/organisations/billing.md). |
| **Usage** | [Compute, storage, and spend](/docs/manage/organisations/billing.md#usage-and-spend) for the current or previous month. |
| **Support** | [Support plans and tickets](/docs/manage/organisations/support.md). |
| **Settings** | The organisation name and its id. |

**Contexts** and **Connections** hold [SurrealDB Agent Memory](/docs/agent-memory.md) resources rather than database instances. They use the same organisation for access and billing.

Your role decides what you see. A member without billing permissions does not see the **Billing** section. See [Members and roles](/docs/manage/organisations/members-and-roles.md).

## Organisation settings

**Settings** holds two values: the display name, which you can change at any time, and the organisation id.

![The Settings page for the Acme Corp organisation in SurrealDB Studio, with an editable Name field, a read-only Organisation ID field with a copy button noting the id may be requested by the SurrealDB support team, and a Save changes button.](~/assets/img/surrealdb/manage/organisation-settings.webp)

Quote the organisation id when you raise a [support ticket](/docs/manage/organisations/support.md). The id names one organisation exactly, where a display name might not.

Organisations cannot be deleted at present. Delete the instances inside an organisation you have finished with, and it stops accruing charges.

## Topics

- **[Accounts and sign-in](/docs/manage/organisations/sign-in.md):** Create an account and sign in.
- **[Members and roles](/docs/manage/organisations/members-and-roles.md):** Send invitations, and choose what each member can do.
- **[Billing](/docs/manage/organisations/billing.md):** Payment details, invoices, usage, and spend.
- **[Support](/docs/manage/organisations/support.md):** Community help, support plans, and tickets.
- **[AWS Marketplace](/docs/manage/organisations/aws-marketplace.md):** Subscribe and pay through AWS.
- **[Azure Marketplace](/docs/manage/organisations/azure-marketplace.md):** Subscribe and pay through Azure.
- **[Referrals](/docs/manage/organisations/referrals.md):** The referral link and the rewards it earns.
- **[FAQs](/docs/manage/organisations/faqs.md):** General, security, pricing, legal, and troubleshooting questions.

## Related pages

- **[Instances](/docs/manage/instances.md):** Deploy and operate databases inside an organisation.
- **[surreal cloud organisations](/docs/explore/cli/cloud/organisations.md):** Organisations, teams, invitations, and tokens from the command line.

---

Source: https://surrealdb.com/docs/manage/organisations/aws-marketplace

# AWS Marketplace

Subscribe via AWS Marketplace and link it to an organisation.

Subscribe through AWS Marketplace to pay for managed instances on your existing AWS bill.

You do not need to add a payment card to the organisation in SurrealDB Studio. AWS charges your AWS account for the usage.

This route suits a procurement process that already runs through AWS:

- Consolidated invoicing across vendors.
- Spend drawn down against an existing AWS commitment.
- A purchasing process that approves Marketplace listings only.

Everything else about the instances is the same.

## Subscribe

1. Open the [SurrealDB Cloud listing](https://aws.amazon.com/marketplace/pp/prodview-xydgy4nnih6mk) in AWS Marketplace.
2. Select **Subscribe**.
3. Confirm the contract and the pricing terms.
4. Complete the AWS account and payment linkage as prompted.

## What happens once you subscribe

1. You'll receive an email confirming your subscription.
2. Follow the link in the email to sign in and claim the subscription. Sign in with the email address you want to manage the subscription from, because the subscription is attached to the account you sign in with.
3. Claiming the subscription creates a new organisation, owned by that account and linked to your AWS Marketplace billing. Any instances you deploy in it are billed through the Marketplace.

You can then [deploy instances](/docs/manage/instances/create.md) as you would otherwise. Plans, instance types, regions, and capabilities are the same. Only the invoicing route differs.

### Moving existing instances

Existing instances and members in another SurrealDB Cloud organisation do not move to the new organisation automatically. To move them, please email [support@surrealdb.com](mailto:support@surrealdb.com) with the IDs of the new and the old organisation.

## Where your invoices are

Invoices and payment information for a Marketplace subscription are in the **AWS Billing and Cost Management** console. They do not appear in SurrealDB Studio, so the [Billing](/docs/manage/organisations/billing.md) page shows no invoices for an organisation billed through the Marketplace.

Usage figures stay in Studio under **Usage**. Read them to reconcile what the AWS charge covers. See [Usage and spend](/docs/manage/organisations/billing.md#usage-and-spend).

## Change or cancel a subscription

Changes and cancellations go through AWS. Follow the AWS guidance for Marketplace subscriptions.

> [!WARNING]
> Cancelling the Marketplace subscription removes the billing route for the organisation. Before you cancel, put the organisation on a valid plan, with its own payment method if one is needed. Running instances are otherwise left without a billing route.

## Related pages

- **[Azure Marketplace](/docs/manage/organisations/azure-marketplace.md):** The same billing route through Azure.
- **[Billing](/docs/manage/organisations/billing.md):** The direct billing route, and the usage view all routes share.
- **[Support](/docs/manage/organisations/support.md):** Support plans, which are added to the organisation whichever billing route it uses.

---

Source: https://surrealdb.com/docs/manage/organisations/azure-marketplace

# Azure Marketplace

Subscribe via Azure Marketplace and link it to an organisation.

Subscribe through Azure Marketplace to pay for managed instances on your existing Azure bill.

You do not need to add a payment card to the organisation in SurrealDB Studio. Microsoft charges your Azure account for the usage.

This route suits a procurement process that already runs through Azure:

- Consolidated invoicing across vendors.
- Spend drawn down against an existing Azure commitment.
- A purchasing process that approves Marketplace listings only.

Everything else about the instances is the same.

> [!NOTE]
> If your company signs in with Microsoft accounts, an enterprise organisation can also use [Microsoft Entra ID for single sign-on](/docs/manage/enterprise/single-sign-on/microsoft-entra-id.md), so that members sign in to SurrealDB with the accounts they already have.

## Subscribe

1. Open the [SurrealDB Cloud listing](https://marketplace.microsoft.com/en-us/product/saas/surrealdb.c4340b0d-df06-455d-9321-5117c9f6851d?tab=Overview) in Azure Marketplace.
2. Select **Get it now**.
3. Choose the Azure subscription to bill, and the plan.
4. Review the pricing terms and select **Subscribe**.

## What happens once you subscribe

1. You receive an email confirming your subscription.
2. Follow the link in the email to sign in and claim the subscription. Sign in with the email address you want to manage the subscription from, because the subscription is attached to the account you sign in with.
3. Claiming the subscription creates a new organisation, owned by that account and linked to your Azure Marketplace billing. Any instances you deploy in it are billed through the Marketplace.

After that, [deploy instances](/docs/manage/instances/create.md) as you would otherwise. Plans, instance types, regions, and capabilities are the same. Only the invoicing route differs.

### Moving existing instances

Existing instances and members in another SurrealDB Cloud organisation do not move to the new organisation automatically. To move them, please email [support@surrealdb.com](mailto:support@surrealdb.com) with the IDs of the new and the old organisation.

## Where your invoices are

Invoices and payment information for a Marketplace subscription are in **Cost Management + Billing** in the Azure portal. They do not appear in SurrealDB Studio, so the [Billing](/docs/manage/organisations/billing.md) page shows no invoices for an organisation billed through the Marketplace.

Usage figures stay in Studio under **Usage**. Read them to reconcile what the Azure charge covers. See [Usage and spend](/docs/manage/organisations/billing.md#usage-and-spend).

## Change or cancel a subscription

Changes and cancellations go through Azure. Follow Microsoft's guidance for SaaS subscriptions bought through the Marketplace.

> [!WARNING]
> Cancelling the Marketplace subscription removes the billing route for the organisation. Before you cancel, put the organisation on a valid plan, with its own payment method if one is needed. Running instances are otherwise left without a billing route.

## Related pages

- **[AWS Marketplace](/docs/manage/organisations/aws-marketplace.md):** The same billing route through AWS.
- **[Billing](/docs/manage/organisations/billing.md):** The direct billing route, and the usage view all routes share.
- **[Support](/docs/manage/organisations/support.md):** Support plans, which are added to the organisation whichever billing route it uses.

---

Source: https://surrealdb.com/docs/manage/organisations/billing

# Billing

Payment methods, discount codes, invoices, and the usage behind them.

Set the billing and payment details of your organisation, and check what its instances cost.

Billing applies to the organisation, not to each instance. Compute, storage, and network use across every instance in the organisation roll up into one monthly invoice. Charges follow what you provision and what you use, rather than a flat licence fee.

SurrealDB invoices you monthly for the usage of the previous month. The cycle runs from the first day of a month to the last, and the invoice arrives by email.

Prices on the website exclude VAT and sales tax. Applicable taxes appear on the invoice. Current rates are on the [pricing page](https://surrealdb.com/pricing).

> [!IMPORTANT]
> Only an organisation **Owner** can change billing and payment details. An Owner or an **Admin** can view invoices. See [Members and roles](/docs/manage/organisations/members-and-roles.md).

## Billing and payment details

Open the organisation in [SurrealDB Studio](https://studio.surrealdb.com) and go to **Billing**.

![The Billing page for the Acme Corp organisation in SurrealDB Studio, showing a Billing details card with the name Alex Doe and email alex@example.com, a Payment details card listing a Mastercard ending in 4242, each with an Edit button, and a SurrealDB Agent Memory plan section below with options to upgrade, cancel, or view pricing.](~/assets/img/surrealdb/manage/organisation-billing.webp)

**Billing details** holds the name and email address that appear on invoices. **Payment details** holds the payment method. Select **Edit** on either card to change it.

Use a billing address that your finance team recognises, and an email address that reaches more than one person. An invoice sent to a person who has left the company can go unnoticed until an instance is suspended.

If your organisation has a VAT or tax identification number, enter it in the billing details. The number then appears on every invoice.

## Discount codes

Apply a discount code in the **Discount codes** section of the same page. A code applies to later invoices for the organisation, and not to invoices already issued.

## Other subscriptions

The **Billing** page also holds subscriptions for other SurrealDB products in the organisation, such as [SurrealDB Agent Memory](/docs/agent-memory.md) context packages. Each subscription has its own plan card, with actions to change it or to cancel it. All subscriptions use the payment method of the organisation, and appear on the same invoice.

## Invoices

**Billing** lists each invoice with its date, its status, and its amount, and links to the document itself.

The organisation keeps its invoices after you delete the instances that generated them. An audit or an expense query can still be answered later.

## Usage and spend

**Usage** shows what produced the charges before the invoice arrives, for the current month or the previous month.

![The Usage page for the Acme Corp organisation in SurrealDB Studio, with a Previous month and Current month toggle, cards showing total spend, total compute hours, total storage, and instance count for the selected period, and a Usage breakdown section listing individual ledger entries.](~/assets/img/surrealdb/manage/organisation-usage.webp)

The four cards give the totals: spend, compute hours, storage, and instance count. **Usage breakdown** lists the ledger entries behind those totals, and attributes each charge to a period and a resource. Read the breakdown when a total is higher than you expect.

Check usage after you resize an instance. A larger instance type is billed on what it provisions. The effect on spend appears at once, rather than at the end of the month. See [Scaling](/docs/manage/instances/scaling.md).

`surreal cloud org spend` and `surreal cloud org usage` print the same figures for a script or an internal cost report. See [surreal cloud organisations](/docs/explore/cli/cloud/organisations.md).

## Control what you spend

- **[Pause](/docs/manage/instances/configure.md#pause-an-instance) non-production instances when they are idle.** A paused instance keeps its data and stops accruing usage charges.
- **Right-size an instance rather than over-provision it.** Storage can only increase, so a generous first allocation is a permanent cost.
- **Delete instances you have finished with.** Take an [export](/docs/manage/instances/import-and-export.md) first if the data still matters.

## Marketplace subscriptions

If you subscribed through AWS Marketplace or Azure Marketplace, billing runs through your cloud provider account, and invoices and payment details do not appear in Studio:

- **AWS:** invoices are in the **AWS Billing and Cost Management** console. See [AWS Marketplace](/docs/manage/organisations/aws-marketplace.md).
- **Azure:** invoices are in **Cost Management + Billing** in the Azure portal. See [Azure Marketplace](/docs/manage/organisations/azure-marketplace.md).

## Billing questions

Email [support@surrealdb.com](mailto:support@surrealdb.com) with the details of your query. Write from the address associated with your account, so that the request matches the organisation. The account menu at the top right of SurrealDB Studio shows which address that is.

For technical questions rather than billing questions, see [Support](/docs/manage/organisations/support.md).

---

Source: https://surrealdb.com/docs/manage/organisations/faqs

# FAQs

Managed instances: limits, security, pricing and legal terms.

Short answers to the questions asked most often about managed instances, with links to the page that covers each one in full.

## General

### What do I get with a managed instance?

A SurrealDB deployment that SurrealDB operates. SurrealDB handles provisioning, high availability, patching, backups, and version upgrades. See [Instances](/docs/manage/instances.md) for what that covers, and [Self-hosted](/docs/manage/self-hosted.md) if you would rather run the server yourself.

### How do I get started?

[Create an account](/docs/manage/organisations/sign-in.md) at [studio.surrealdb.com](https://studio.surrealdb.com), [create an organisation](/docs/manage/organisations.md), and [deploy an instance](/docs/manage/instances/create.md). The free instance type needs no payment details.

### Which cloud platforms can I deploy to?

AWS and Microsoft Azure, in the regions listed in [Create an instance](/docs/manage/instances/create.md). You choose the provider and the region together when you deploy, and neither can be changed afterwards.

### Which programming languages can I use?

Any language with a SurrealDB SDK:

- [JavaScript and TypeScript](/docs/reference/javascript.md)
- [Python](/docs/reference/python.md)
- [Go](/docs/reference/golang.md)
- [Java](/docs/reference/java.md)
- [Rust](/docs/reference/rust.md)
- [PHP](/docs/reference/php.md)
- [.NET](/docs/reference/dotnet.md)

Any other language can use the [HTTP API](/docs/manage/instances/connect/via-http.md).

### Can I move an existing SurrealDB application onto a managed instance?

Yes. Export from your current deployment, then import into the instance. See [Import and export](/docs/manage/instances/import-and-export.md), and [Migrating to SurrealDB](/docs/build/migrating.md) for a move from another database.

A dedicated migration tool for existing self-hosted deployments is in development. Until it ships, use `surreal db export` and `surreal db import`.

### Why am I getting an authentication error when connecting through an SDK?

The instance most likely has no user or access method defined yet. Studio authenticates with your own session, so it works before any credentials exist. An SDK needs credentials. See [Connect via SDK](/docs/manage/instances/connect/via-sdk.md#create-credentials).

### Are my instances backed up?

Yes. Automated snapshots run on a schedule, with tiered daily, weekly, and monthly retention. The snapshot frequency and the retention of each tier depend on the instance type, and Scale instances allow a configurable frequency. See [Backups and recovery](/docs/manage/instances/backups.md).

### How often can I increase the disk size of an instance?

Once every six hours, and a change of instance type counts as a storage change. Storage can increase but never decrease, up to the maximum of the instance type. See [Storage limits on Start](/docs/manage/instances/scaling.md#storage-limits-on-start) and [Configure an instance](/docs/manage/instances/configure.md#compute-and-storage).

### Can I transfer an instance to another organisation?

No. Deploy an instance in the target organisation, then move the data with [`surreal db export` and `surreal db import`](/docs/manage/instances/import-and-export.md).

### Can I delete an organisation?

Not at present. Delete the instances inside it, and it stops accruing charges. See [Organisations](/docs/manage/organisations.md).

### What are the current limits?

- **Server flags and environment variables** cannot be set directly. [Instance capabilities](/docs/manage/instances/configure.md#capabilities) control runtime behaviour instead, in Studio or with `surreal cloud instance capabilities`.
- **[GraphQL](/docs/learn/querying/graphql/overview.md)** is not yet enabled on managed instances.
- **Free instances** become read-only above the 1 GB storage allowance. Existing data stays available, and new writes are refused until you move to a paid instance type.
- **Request sizes** are capped per endpoint. See [request size limits](/docs/manage/instances/connect/via-http.md#request-size-limits).

### Where are the documentation and tutorials?

- **[SurrealDB documentation](/docs):** Guides and reference, including [SurrealDB Studio](/docs/explore/studio.md).
- **[SurrealDB University](/learn):** A self-paced course with hands-on labs.
- **[SurrealDB Book](/learn/book):** An in-depth guide to the database.
- **[YouTube channel](https://www.youtube.com/@surrealdb):** Release walkthroughs and tutorials.

## Security

### How secure are managed instances?

Instances run with encryption at rest and in transit, network isolation, access controls, and monitoring, as set out in the [Security Addendum](/legal/security-addendum). Multi-factor authentication is available through the identity provider you sign in with.

SurrealDB holds ISO 27001, SOC 2 (Type 2), and Cyber Essentials Plus certifications. Further industry-specific programmes, such as HIPAA and PCI DSS, are planned. The [Trust Centre](https://trust.surrealdb.com/) holds the current detail.

Instances also inherit the [security features](/docs/learn/security/authentication/summary.md#product) of SurrealDB itself, and the [security practices](/docs/learn/security/authentication/summary.md#process) the database is developed under. To assess the service for a workload with specific requirements, use the [contact form](/contact).

### How do I report a security issue?

Email [security@surrealdb.com](mailto:security@surrealdb.com). For a vulnerability in SurrealDB itself, submit a report through [GitHub Security Advisories](https://github.com/surrealdb/surrealdb/security/advisories) rather than opening a public issue.

SurrealDB asks you to:

- Disclose the details privately to SurrealDB first.
- Include enough information to reproduce the issue.
- Keep the details from the public and from third parties until SurrealDB has addressed them.
- Limit any exploitation of the vulnerability to what verifies that it exists.
- Get permission before you run automated security tools against SurrealDB infrastructure.

In return, SurrealDB:

- Acknowledges your report within three business days.
- Verifies the issue, and reports progress to you.
- Treats your report and any data you share as confidential.
- Takes no legal action against you for a report made under this policy.
- Credits you in any resulting advisory, unless you prefer otherwise.

### How do I configure user authentication?

Connect to the instance, then [define users](/docs/reference/query-language/statements/define/user.md) or [access methods](/docs/reference/query-language/statements/define/access.md) at root, namespace, or database level. Those credentials work across every interface: the [HTTP API](/docs/reference/rest-api.md), the [CLI](/docs/reference/cli/db.md), the SDKs, and [SurrealDB Studio](https://studio.surrealdb.com). See [Connect via SDK](/docs/manage/instances/connect/via-sdk.md#create-credentials).

### Can I use my own encryption keys?

Not at present. SurrealDB manages the keys that encrypt data at rest. The architecture supports customer-managed encryption keys, so they may be offered in future. Use the [contact form](/contact) if you need them.

### Can I control who reaches my instances over the network?

Instances are reachable over the public internet by default, protected by the authentication and authorisation defined inside them.

Two additional controls exist:

- **[AWS PrivateLink](/docs/manage/instances/private-connectivity.md)** gives an instance a private network path from your AWS VPC. In `private` mode the instance gets no public hostname at all. It is available in a subset of regions, and the SurrealDB team onboards you to it.
- **[Instance capabilities](/docs/manage/instances/configure.md#capabilities)** restrict what the engine does: which RPC methods and HTTP endpoints it serves, whether guest access is permitted, and which [outbound destinations](/docs/manage/instances/network-access.md) a query can reach.

## Pricing

### How much does it cost?

You pay for the compute you consume and the storage you provision. Current rates are on the [pricing page](/pricing). [Usage](/docs/manage/organisations/billing.md#usage-and-spend) shows what an organisation has accrued so far this month.

### Is there a free tier?

Yes. The free instance type carries no charge, and provides limited resources for development and evaluation. It has a 1 GB storage allowance, cannot expand its storage, and pauses automatically after seven days without use.

### I am on the free tier. Can I add another instance?

Studio prompts you to add [billing information](/docs/manage/organisations/billing.md) to the organisation first.

### How am I billed?

Monthly, for the usage of the previous month. The cycle runs from the first day of a month to the last, and the invoice arrives by email. See [Billing](/docs/manage/organisations/billing.md).

### Are invoices itemised, and is tax identified?

Yes. Enter your VAT or tax identification number in the billing details of the organisation, and it appears on every invoice.

### Are there additional fees?

No. All costs are on the [pricing page](/pricing). Prices quoted on the website exclude VAT and sales tax, and applicable taxes appear on the invoice.

### Who do I contact about billing?

Email [support@surrealdb.com](mailto:support@surrealdb.com) from the address associated with your account, and describe the query.

## Legal

### What terms govern my use of the service?

The Master Services Agreement governs your use of the managed service. It is on the [legal page](/legal), together with the compliance and privacy documentation.

### Do you serve US-embargoed countries or Russia?

SurrealDB Inc. complies with U.S. regulations on embargoed countries and regions. It currently prohibits use of its products and services in Cuba, Iran, North Korea, and Syria. The prohibition also covers the Russian-controlled regions of Crimea, the Luhansk People's Republic (LNR), and the Donetsk People's Republic (DNR).

SurrealDB uses geoblocking to prevent logins from restricted regions. A regional block can restrict an entire country, which is **beyond the direct control of SurrealDB**.

SurrealDB Ltd is also subject to Article 5n(2b) of Council Regulation 833/2014. That article prohibits the supply of software for the management of enterprises, or for design and manufacture. The prohibition covers the Government of Russia and entities established in Russia.

SurrealDB reviews these obligations and updates them as regulations change.

## Troubleshooting

### I cannot connect to my instance

Work through these checks in order:

1. Confirm the instance is **running** and not [paused](/docs/manage/instances/configure.md#pause-an-instance). A paused instance is unreachable, and a free instance pauses itself after seven days.
2. Check the **endpoint, namespace, and database** against the Connect menu of the instance in Studio.
3. Check your **credentials**, and confirm the user or access method has permission on that namespace and database.
4. On [PrivateLink](/docs/manage/instances/private-connectivity.md), confirm you connect from inside the VPC. A private hostname does not resolve outside it.
5. Check whether the failing operation depends on a denied [capability](/docs/manage/instances/configure.md#capabilities), such as an outbound HTTP call.
6. Check [metrics and logs](/docs/manage/instances/monitoring.md) for connection saturation or errors around the time the failures started.

If the connection still fails, raise a [support ticket](/docs/manage/organisations/support.md) with the instance id.

### How do I report a bug or request a feature?

Raise a [support ticket](/docs/manage/organisations/support.md), or report a bug in SurrealDB itself as a [GitHub issue](https://github.com/surrealdb/surrealdb/issues). For a bug, include steps to reproduce the problem, the instance id, what you expected, and what happened instead. Suggest a feature in the Ideas category of [GitHub Discussions](https://github.com/orgs/surrealdb/discussions), where others can add their use cases, or discuss it on [Discord](https://discord.gg/surrealdb).

---

Source: https://surrealdb.com/docs/manage/organisations/members-and-roles

# Members and roles

Invite people, and choose the Owner, Admin or Member role.

Invite people to your organisation, and give each one the role that matches the work they do.

The **Team** section of the organisation in [SurrealDB Studio](https://studio.surrealdb.com) holds membership. Each member holds exactly one role, and that role decides what the member can see and change across every instance in the organisation.

![The Team page for the Acme Corp organisation in SurrealDB Studio, showing Members and Pending tabs with one pending invitation, a member search box, an Invite member button, and a single row for Alex Doe at alex@example.com labelled OWNER and YOU.](~/assets/img/surrealdb/manage/organisation-members.webp)

The **Members** tab lists the people who accepted an invitation. The **Pending** tab lists invitations that have been sent but not yet accepted.

## Invite a member

1. Open **Team**.
2. Select **Invite member**.
3. Enter the email address of the person you invite.
4. Choose the role that person holds.
5. Send the invitation.

The invitation appears under **Pending** until the person accepts it. An invitee without an account creates one first, then joins the organisation on the role you chose. See [Accounts and sign-in](/docs/manage/organisations/sign-in.md).

Invite the address of a named person rather than a shared mailbox, above all for the Admin and Owner roles. A shared mailbox hides which person acted, and you cannot withdraw it from one reader who leaves.

`surreal cloud team invite` sends an invitation. `surreal cloud team list` reports current membership, which helps when you review access across several organisations. See [surreal cloud organisations](/docs/explore/cli/cloud/organisations.md).

## What each role can do

The person who creates an organisation is its **Owner**. An Owner invites members as **Admin** or **Member**.

| Action | Owner | Admin | Member |
| --- | --- | --- | --- |
| View instances and team | Yes | Yes | Yes |
| Deploy, pause, and delete instances | Yes | Yes | No |
| Change instance configuration and [capabilities](/docs/manage/instances/configure.md#capabilities) | Yes | Yes | No |
| Invite and remove members | Yes | Yes | Own membership only |
| Change another member's role | Yes | No | No |
| View [invoices](/docs/manage/organisations/billing.md) | Yes | Yes | No |
| Change billing and payment details | Yes | No | No |

An Admin operates the infrastructure of the organisation, but does not control its payment details or its access list. A Member reads what the organisation holds, and can end its own membership.

> [!NOTE]
> Each role is a fixed set of permissions. An [enterprise](/docs/manage/enterprise.md) adds roles you compose yourself, which govern the administration of members, domains, and single sign-on rather than of instances.

## Least privilege

Grant the lowest role that lets a person do their work, then review the member list at regular intervals.

Three actions need particular attention, because each one causes loss or exposure that is hard to reverse:

- **Delete an instance:** destroys the data in it. See [Configure an instance](/docs/manage/instances/configure.md#delete-an-instance).
- **Change [network access](/docs/manage/instances/network-access.md):** decides which external destinations a query can reach.
- **Restore a [backup](/docs/manage/instances/backups.md):** creates a new instance that holds a copy of production data.

Every Owner and every Admin can run all three. To let someone operate instances without these permissions, use a separate organisation rather than a lower role.

Remove access when a person leaves the project. A member who has left keeps the Admin role until you remove the membership.

## Related pages

- **[Organisations](/docs/manage/organisations.md):** The organisation view and its settings.
- **[Accounts and sign-in](/docs/manage/organisations/sign-in.md):** How an invitee creates an account.
- **[surreal cloud organisations](/docs/explore/cli/cloud/organisations.md):** Teams, invitations, and tokens from the command line.

---

Source: https://surrealdb.com/docs/manage/organisations/referrals

# Referrals

Share a referral link to earn credits when someone signs up.

Share your referral link to earn rewards when someone signs up through it.

Every account gets its own referral link. Rewards range from account credit to items from the SurrealDB store.

## How it works

1. Copy your referral link from the [referrals page](https://studio.surrealdb.com/referrals).
2. Share the link with the people you recommend SurrealDB to.

When a person signs up through your link, the reward is credited to your account.

<img src="~/assets/img/image/cloud/referrals.png" alt="The Referral Program page in SurrealDB Studio, showing a card with your personal referral link, a copy button and a Share button, a Progress section counting the users you have referred and how many more unlock the next reward, an Unlockable rewards row of cards for free credits, store items and a Discord badge at rising referral counts, and a How does this work section explaining that you share your link, a friend signs up, and you get rewarded." />

The same page tracks the sign-ups attributed to your link and the rewards they earn. Credit applies to future invoices for your organisation. See [Billing](/docs/manage/organisations/billing.md).

---

Source: https://surrealdb.com/docs/manage/organisations/security

# Two-step verification

Add an authenticator app, and keep a recovery code for it.

Two-step verification asks for a code from a second device after your password, so that a leaked password is not enough to reach your account on its own. It is set per account rather than per organisation: turning it on protects your own sign-in, and every organisation you belong to.

Security options live in the account portal at [account.surrealdb.com/security](https://account.surrealdb.com/security), under **Security settings**. Studio does not manage them.

> [!NOTE]
> If you sign in with Google or GitHub, two-factor authentication configured with that provider already applies to your SurrealDB account, because the provider verifies you before returning you to SurrealDB. Adding an authenticator app here covers accounts that sign in with an email address and password. See [Accounts and sign-in](/docs/manage/organisations/sign-in.md).

## Add an authenticator app

An authenticator app generates a new six-digit code every thirty seconds, with no network connection needed. Any TOTP app works, including 1Password, Bitwarden, Google Authenticator and Authy.

1. Open [account.surrealdb.com/security](https://account.surrealdb.com/security).
2. Under **Set up a new security option**, choose **Authenticator app**.
3. Scan the QR code with your authenticator app. Where a camera is not available, use the **Manual setup code** shown beside it and enter the key by hand.
4. Enter the code your app displays to confirm the pairing.

The method appears under **Two-step verification** once it is confirmed. From then on, signing in asks for a code after your password.

One authenticator app can be registered per account. To move to a different app or device, remove the existing method first and add the new one.

## Save your recovery code

Setting up your first method produces a single recovery code. It signs you in if your authenticator app is ever unavailable, so store it somewhere you can reach without that device: a password manager, or somewhere safe offline.

The code is shown once. Copy it before selecting **I have safely recorded this code**, because it cannot be displayed again afterwards.

> [!WARNING]
> Losing both your authenticator app and your recovery code locks you out of the account. Neither can be recovered by SurrealDB. Save the recovery code somewhere separate from the device running the app, so that losing one device does not cost you both.

## Remove a method

Each registered method can be removed from the same page. Removing your only method returns the account to password-only sign-in, so add a replacement first if you intend to keep two-step verification on.

## Next steps

1. [Invite colleagues](/docs/manage/organisations/members-and-roles.md) so each person signs in with their own account and their own second factor.
2. [Create a token](/docs/explore/cli/cloud/authentication.md) for automation rather than sharing an interactive account.

---

Source: https://surrealdb.com/docs/manage/organisations/sign-in

# Accounts and sign-in

Create an account with Google, GitHub or email, and sign in.

One account gets you into [SurrealDB Studio](https://studio.surrealdb.com), where you manage [organisations](/docs/manage/organisations.md) and [instances](/docs/manage/instances.md).

An account needs no payment details. The free instance type carries no charge, so you can deploy before you add a payment method.

To create an account or to sign in, open [studio.surrealdb.com](https://studio.surrealdb.com) and continue to the authentication page.

## Choose a sign-in method

Three methods are available. Use the same method every time. If you sign up with Google and later sign in by email on the same address, you create a second account. The first account is not recovered.

| Method | What happens | Suited to |
| --- | --- | --- |
| **Google** | You authorise SurrealDB on Google's own sign-in page | Teams already on Google Workspace |
| **GitHub** | You authorise SurrealDB on GitHub's own sign-in page | Developers already signed in to GitHub |
| **Email and password** | You set a password, then confirm the address from a verification link | Anyone who prefers not to link a third-party account |

> [!IMPORTANT]
> Google and GitHub sign-in uses OAuth2. The provider's own page collects your password, so SurrealDB never receives it. SurrealDB requests your email address and name only. After you authorise the connection, the provider returns a token that confirms your identity.

Two-factor authentication configured with Google or GitHub also applies to your SurrealDB account, because the provider verifies you before returning you to SurrealDB. For an account that signs in with an email address and password, add an authenticator app instead. See [Two-step verification](/docs/manage/organisations/security.md) for more.

## Create an account

With Google or GitHub:

1. Select **Sign up with Google** or **Sign up with GitHub**.
2. Choose your account in the provider's window.
3. Allow the requested permissions.

Studio signs you in as soon as the provider returns.

With email:

1. Select **Sign up with Email**.
2. Enter your email address.
3. Set a password.
4. Open the verification link sent to your inbox to confirm the address.

## Sign in

1. Open [studio.surrealdb.com](https://studio.surrealdb.com).
2. Select the method you registered with: **Continue with Google**, **Continue with GitHub**, or **Sign in with Email**.
3. Complete any two-factor step the provider asks for.

To reset a forgotten email password, select **Forgot password?** on the authentication page. Follow the instructions sent to your address.

An account created through Google or GitHub has no SurrealDB password to reset. Recover the provider account instead.

## Accounts for a team

Each person needs their own account. Invite colleagues to the organisation instead of sharing credentials. Separate accounts let each member hold a different role, and let you remove someone without changing a password the rest of the team uses. See [Members and roles](/docs/manage/organisations/members-and-roles.md).

For automation, use a token from [`surreal cloud`](/docs/explore/cli/cloud/authentication.md) rather than an interactive account.

## Next steps

1. [Create an organisation](/docs/manage/organisations.md) to hold your instances and billing.
2. [Deploy an instance](/docs/manage/instances/create.md).
3. [Connect to it](/docs/manage/instances/connect.md) from SurrealDB Studio, the CLI, an SDK, or over HTTP.

---

Source: https://surrealdb.com/docs/manage/organisations/support

# Support plans and raising a ticket

Support plans, response targets, severity levels and raising a ticket.

SurrealDB offers three paid support plans: Standard, Business Critical and Premium. Each commits to published initial response targets, from 8 business hours for a Sev (Severity) 1 on Standard to 30 minutes on Premium. A plan applies to the whole organisation and is added to an annual contract.

Community help is free and open to everyone. A support plan is what lets you raise a technical ticket. Account and billing tickets are open to every organisation, with or without a plan.

## Support plans

Each plan sets the coverage hours, the number of named contacts, and the channels your organisation can reach support through.

| | Community (free) | Standard | Business Critical | Premium |
| --- | --- | --- | --- | --- |
| **For** | Anyone building on SurrealDB | Development teams | Production applications | Mission-critical applications |
| **Coverage** | Best effort, no commitment | 09:00 to 17:00, business days | 24x7 | 24x7 |
| **[Response-time targets](#initial-response-targets)** | None | Yes | Yes | Yes |
| **Named contacts** | Anyone on your team | 5 | 10 | 20 |
| **Channels** | Discord and GitHub Discussions | Tickets via the SurrealDB Studio support portal | Tickets via the SurrealDB Studio support portal | Tickets via the SurrealDB Studio support portal, plus a dedicated Slack or Teams channel |
| **Technical account manager** | No | No | No | Yes |

Plans are priced as a percentage of your annual contract and can be added to an organisation at any time. To add one, [enquire about paid support](https://surrealdb.com/contact) or speak to your account manager.

With a plan in place, **Support** in the organisation sidebar shows your plan and every ticket for the organisation. Without one, it shows **View plans**, which opens the [pricing page](https://surrealdb.com/pricing).

<img src="~/assets/img/image/cloud/add-support-plan.png" alt="The Support section of an organisation in SurrealDB Studio, showing a Support Plan panel with a Community card described as help from community members on Discord and GitHub and a View plans button, and a Support History panel stating that a support plan is required for expedited support from the SurrealDB team, with a second View plans button." />

<img src="~/assets/img/image/cloud/create-support-ticket.png" alt="The Support section of an organisation in SurrealDB Studio with a Standard support plan card described as professional support for development teams with business hours coverage and email support, a View plans button, and a Support History panel with a New ticket button." />

## Severity levels

You choose the severity when you raise a ticket. Choose it against business impact rather than against how urgent the work feels.

| Severity | Definition |
| --- | --- |
| Sev 1 | Production is down or unusable, or production data is at risk. No workaround. |
| Sev 2 | Production is running in reduced capacity. No workaround, or the workaround is not sustainable. |
| Sev 3 | A fault with a workaround available, or a non-production environment is affected. |
| Sev 4 | A question, a documentation issue, or a feature request. No operational impact. |

## Initial response targets

| Severity | Standard | Business Critical | Premium |
| --- | --- | --- | --- |
| Sev 1 | 8 business hours | 2 hours | 30 minutes |
| Sev 2 | 12 business hours | 4 hours | 1 hour |
| Sev 3 | 24 business hours | 8 business hours | 4 business hours |
| Sev 4 | 24 business hours | 24 business hours | 24 business hours |

An initial response target is the time to the first substantive response from a SurrealDB support engineer. Targets are not guarantees, and they are not times to resolution.

Business hours are 09:00 to 17:00 on business days in your organisation's assigned support region, which is set in your contract.

## What a support plan covers

A support plan covers every SurrealDB product on your contract, including SurrealDB, SurrealDB Cloud and Agent Memory, whether you run them in the cloud or self-hosted. In scope:

- Faults and unexpected behaviour in the product
- Configuration, deployment, upgrade and migration questions
- Performance triage, including query and index behaviour
- Availability, backup and recovery for Cloud instances

Forward-deployed engineers are available to work alongside your team on deeper pieces of work, scoped separately from your support plan. Speak to your account manager.

Support covers the software SurrealDB provides. Infrastructure and third-party services from other suppliers sit outside it.

## Raise a ticket

Tickets are raised from SurrealDB Studio. A paid support plan is required to raise a technical ticket. Account and billing tickets are open to every organisation.

1. Open **Support** in the organisation sidebar.
2. Select **New ticket**.
3. Enter a **Subject** that names the problem.
4. Describe the problem under **What is your reason for contacting us?**
5. Choose the **Organisation** the ticket belongs to.
6. Choose the **Severity level**.
7. Select **Submit**.

<img src="~/assets/img/image/cloud/create-support-ticket-form.png" alt="The Create new ticket dialog in SurrealDB Studio, with a note that replies are sent to the ticket and to your email address, a Subject field reading I have an issue with my functions, a reason field describing an unresponsive instance, an Organisation selector, a Severity level selector set to partial loss of functionality affecting some users or features, a note that attachments are included by replying to the ticket, and a Submit button." />

In the description, state what you expected, what happened, and when the problem started. Include the [instance id](/docs/manage/instances/configure.md#the-instance-id) and the [organisation id](/docs/manage/organisations.md#organisation-settings), which you can copy from Studio. Both save a round trip.

For a Sev 1, also include the time the incident began, the error returned, and whether the instance is reachable. Response targets run from the moment the ticket is submitted.

Studio opens the ticket after you submit it. Replies appear in the ticket and also go to your email address. To attach a log, a query, or a screenshot, add it to a reply.

<img src="~/assets/img/image/cloud/create-support-ticket-page.png" alt="A submitted support ticket in SurrealDB Studio, showing the description and severity level on the left, a panel on the right with the state Submitted, the type Standard and the time of the last update, an updates timeline recording the state change, a reply box with an attachment button, and an All tickets button at the top right." />

**All tickets** at the top right of the **Support** section lists every open and closed ticket for the organisation.

## Escalation and incidents

To escalate, raise a new ticket at the severity the problem now warrants. Alerting is triggered by new tickets, so a reply on an existing ticket raises no alert. Where the new ticket covers the same problem, say so in the description and the two can be merged once the issue is resolved.

For platform-wide incidents, check the [status page](https://status.surrealdb.com) before raising a ticket.

## Account and billing questions

Every organisation can raise an account or billing ticket in SurrealDB Studio, with or without a paid support plan. Open **Support** in the organisation sidebar and select **New ticket**.

You can also write to [support@surrealdb.com](mailto:support@surrealdb.com) from the address associated with your account. See [Billing](/docs/manage/organisations/billing.md).

## Community support

The [SurrealDB Discord](https://discord.gg/surrealdb) answers most questions. `#surrealdb-cloud` covers managed instances, and `#help` or `#general` cover everything else. SurrealDB Ambassadors and the SurrealDB team read the server and escalate the issues that need it.

[GitHub Discussions](https://github.com/orgs/surrealdb/discussions) suits a question whose answer others will search for later, such as how to model some data or why a query behaves as it does. A question there stays public and searchable, and a Q&A post can have an answer marked as the solution. Use [GitHub issues](https://github.com/surrealdb/surrealdb/issues) instead to report a bug.

Use the community for questions that carry no confidential detail. Raise a ticket for anything that involves your data, your credentials, or your billing. Community help carries no response-time commitment.

## Report a security issue

Send a security report to [security@surrealdb.com](mailto:security@surrealdb.com) rather than to Discord or a ticket. For a vulnerability in SurrealDB itself, submit a report through [GitHub Security Advisories](https://github.com/surrealdb/surrealdb/security/advisories) rather than a public issue. The disclosure policy is in the [FAQs](/docs/manage/organisations/faqs.md#how-do-i-report-a-security-issue).

---

Source: https://surrealdb.com/docs/manage/schema-migration

# SurrealKit schema migration

SurrealKit is the official schema migration CLI for SurrealDB. Define your schema in .surql files and keep databases in sync across every environment.

> [!NOTE]
> Examples on these pages assume a [SurrealDB Cloud](/docs/manage/instances.md) instance with the namespace `main`, the database `main`, and a root user named `root` with the password `secret`. Set the endpoint once so that each example stays short:
> ```bash
> export SURREALDB_HOST=wss://production-6xk2.aws-euw1.surreal.cloud
> ```
> Find the endpoint for an instance with [`surreal cloud instance endpoint`](/docs/reference/cli/cloud/commands/instance.md). Namespace `main` and database `main` are the defaults, so only `--user root --pass secret` is shown alongside each command.

SurrealKit is the official schema management and migration CLI for SurrealDB. You define your database schema as plain `.surql` files, commit them alongside your application code, and SurrealKit keeps every environment in sync with those definitions.

It has two modes for getting schema into a database:

- **Sync:** immediately pushes your schema files to the connected database. Best for local development and ephemeral environments where fast iteration matters and losing data is acceptable.
- **Rollouts:** generates a reviewed, phased migration manifest and applies changes in non-destructive then destructive passes, with rollback support. Best for shared, staging, and production databases.

Most teams use Sync day-to-day and switch to Rollouts when promoting changes to shared environments.

SurrealKit also provides:

- **Templates:** scaffold a new project from a template with selectable features (`surrealkit init`).
- **Schema modules and targets:** split a project into independently tracked schemas and apply them across several databases.
- **Seeding:** apply `.surql` seed data, tracked so that each file runs once.
- **Type generation:** introspect a database to emit JSON and TypeScript types for your application.
- **Testing:** a declarative framework for validating schema, permissions, and API endpoints.

> [!IMPORTANT]
> These pages document SurrealKit `1.0.0-beta.1`. Anything added in the 1.0 line is marked with a badge: _(since v1.0)_. Upgrading from 0.7 needs a few deliberate changes, two of which are silent: see [Upgrading to SurrealKit 1.0](/docs/manage/schema-migration/upgrading.md).

## Installation

1.0 is a prerelease, so it has to be requested by version. Without one, Cargo installs the latest stable release, which is still 0.7.

**cargo binstall** (recommended, no compilation required once [cargo binstall is installed](https://github.com/cargo-bins/cargo-binstall#installation)):

```bash
cargo binstall surrealkit --version 1.0.0-beta.1
```

**Cargo from source:**

```bash
cargo install surrealkit --version 1.0.0-beta.1
```

**Docker:**

```bash
docker pull ghcr.io/surrealdb/surrealkit:1.0.0-beta.1
```

The `latest` tag also points at the newest stable release rather than the beta. See [Configuration](/docs/manage/schema-migration/configuration.md#inside-a-container) for mounting a project into the container.

Prebuilt binaries for Linux (x86_64 / aarch64), macOS (x86_64 / aarch64), and Windows (x86_64) are available on the [GitHub releases page](https://github.com/surrealdb/surrealkit/releases).

## Initialise a project

```bash
surrealkit init
```

This scaffolds a project from a template, letting you pick which optional features to include. It always writes the base layout:

```text
database/
├── schema/        # .surql schema definition files
├── rollouts/      # rollout manifests (generated)
├── snapshots/     # schema and catalog snapshots
├── seed/          # optional seed data
├── tests/         # test suites and config
└── setup.surql    # runs before sync
surrealkit.toml    # project configuration
```

See [Project templates](/docs/manage/schema-migration/templates.md) for the feature checklist, non-interactive flags, and custom templates, and [Configuration](/docs/manage/schema-migration/configuration.md) for what `surrealkit.toml` holds.

## Connection configuration

SurrealKit resolves connection details in the following order (first match wins):

1. CLI arguments (`--host`, `--ns`, `--db`, `--user`, `--pass`, `--auth-level`)
2. `SURREALDB_*` environment variables
3. `.env` or `.env.local` in the working directory

| Environment variable | CLI equivalent | Purpose |
|---|---|---|
| `SURREALDB_HOST` | `--host` | Database endpoint URL |
| `SURREALDB_NAMESPACE` | `--ns` | Namespace |
| `SURREALDB_NAME` | `--db` | Database name |
| `SURREALDB_USER` | `--user` | Username |
| `SURREALDB_PASSWORD` | `--pass` | Password |
| `SURREALDB_AUTH_LEVEL` | `--auth-level` | `root`, `namespace`/`ns`, `database`/`db`, or `none` |
| `SURREALDB_FOLDER` | `--folder` | Project root, holding `schema/`, `rollouts/`, `snapshots/`, `seed/` and `tests/`. Defaults to `./database` |

> [!WARNING]
> The `DATABASE_*` aliases were removed in 1.0. One set without its `SURREALDB_*` replacement is an error rather than being ignored, because ignoring it would fall back to the defaults and connect to the wrong database. See [Upgrading](/docs/manage/schema-migration/upgrading.md#rename-the-database-variables).

Example connecting via CLI flags:

```bash
surrealkit --user root --pass secret sync
```

To apply schema to more than one database from one project, declare [targets](/docs/manage/schema-migration/modules-and-targets.md) instead of switching these values between runs.

## Embedded databases

_(since v1.0)_

SurrealKit can manage an in-process SurrealDB by pointing `--host` at an embedded endpoint. Authentication is skipped automatically, because a fresh embedded datastore has no users:

```bash
surrealkit --host surrealkv://./data --ns main --db main sync
```

The recognised schemes are `mem://`, `surrealkv://`, `surrealkv+versioned://`, `rocksdb://`, `speedb://`, `file://`, `tikv://` and `indxdb://`. Pass `--auth-level none` to force the same no-signin path on any endpoint.

The prebuilt CLI bundles the in-memory engine only, to keep the binary small. Build with the matching feature for an on-disk engine:

```bash
cargo install surrealkit --version 1.0.0-beta.1 --features kv-surrealkv
```

`kv-rocksdb` is available on the same basis, and `embedded` enables every engine that ships a feature.

> [!NOTE]
> `speedb://`, `tikv://` and `indxdb://` are recognised as embedded endpoints but have no corresponding cargo feature in `1.0.0-beta.1`, so they fail when the connection is opened. Embedded engines are also single-process: the CLI holds an exclusive lock on the datastore, so run it while your application is stopped. To manage schema inside your application at startup, use the [library](/docs/manage/schema-migration/library.md) instead.

## Next steps

- [New databases](/docs/manage/schema-migration/getting-started/new-databases.md): start a fresh project with SurrealKit from the beginning
- [Existing databases](/docs/manage/schema-migration/getting-started/existing-databases.md): adopt SurrealKit in a project that already has a database
- [Sync vs Rollouts](/docs/manage/schema-migration/getting-started/sync-vs-rollouts.md): choose the right mode for each environment
- [Configuration](/docs/manage/schema-migration/configuration.md): everything `surrealkit.toml` accepts, and where it has to live
- [Schema modules and targets](/docs/manage/schema-migration/modules-and-targets.md): several schemas across several databases
- [Project templates](/docs/manage/schema-migration/templates.md): scaffold a project with selectable features
- [Type generation](/docs/manage/schema-migration/typegen.md): generate JSON and TypeScript types from your schema

---

Source: https://surrealdb.com/docs/manage/schema-migration/configuration

# Configuration

The surrealkit.toml project file, and every section it accepts.

`surrealkit.toml` declares what a SurrealKit project contains. Template variables and type generation settings have always lived here. Since 1.0 the file also declares [schema modules and database targets](/docs/manage/schema-migration/modules-and-targets.md), so it now matters where the file is.

Every section is optional. A project with no config file, or one holding only `[variables]` and `[typegen]`, behaves the way it did before 1.0: one unnamed schema module applied to one database taken from the environment.

## Where SurrealKit looks for it

`surrealkit init` writes `surrealkit.toml` to the current directory, next to `database/` rather than inside it:

```text
surrealkit.toml    # project configuration
database/
├── schema/
├── rollouts/
├── snapshots/
├── seed/
├── tests/
└── setup.surql
```

SurrealKit starts at the working directory and walks up to the filesystem root, taking the first `surrealkit.toml` it finds. Running a command from `apps/api/` in a monorepo therefore finds the config at the repository root. Before 1.0 the file was read from the working directory only.

There is no flag for pointing at a different file. To use another config, change directory first.

> [!WARNING]
> The upward walk applies to `[schema.*]` and `[target.*]` only. `[variables]` and `[typegen]` are still read from the working directory alone, so running from a subdirectory applies your modules but silently drops your template variables and stops generating TypeScript. Run project commands from the directory that holds `surrealkit.toml` until this is resolved.

## Inside a container

The published image is built on `gcr.io/distroless/cc-debian12:nonroot` and sets no working directory of its own, so a container inherits `/home/nonroot` from the base image. Config discovery starts there, which means a mounted `surrealkit.toml` has to land at `/home/nonroot/surrealkit.toml`:

```bash
docker run --rm \
  -v "$(pwd)/database:/database:ro" \
  -v "$(pwd)/surrealkit.toml:/home/nonroot/surrealkit.toml:ro" \
  ghcr.io/surrealdb/surrealkit:1.0.0-beta.1 \
  --host wss://production-6xk2.aws-euw1.surreal.cloud \
  --ns main --db main --user root --pass secret sync
```

Setting a working directory is the alternative, and lets you mount the project once at its own shape:

```bash
docker run --rm -w /project \
  -v "$(pwd):/project" \
  ghcr.io/surrealdb/surrealkit:1.0.0-beta.1 \
  --host wss://production-6xk2.aws-euw1.surreal.cloud \
  --ns main --db main --user root --pass secret --folder /project/database sync
```

The schema mount target is `/database` in the first example because the image sets `SURREALDB_FOLDER=/database`. Point `--folder` somewhere else, as the second example does, and that default no longer applies. Get the endpoint for a Cloud instance with [`surreal cloud instance endpoint`](/docs/reference/cli/cloud/commands/instance.md).

> [!NOTE]
> A read-only mount suits `sync` against an existing project. It conflicts with `[typegen] typescript`, which writes into the mounted tree, and with the `setup.surql` that `sync` scaffolds when the file is absent. Drop `:ro` for either.

## Sections

| Section | Purpose |
| --- | --- |
| `[variables]` | Default values for [template variables](/docs/manage/schema-migration/template-variables.md) |
| `[typegen]` | [Type generation](/docs/manage/schema-migration/typegen.md) output directory and formatter |
| `[schema.<name>]` | A named [schema module](/docs/manage/schema-migration/modules-and-targets.md) |
| `[target.<name>]` | A named [database target](/docs/manage/schema-migration/modules-and-targets.md) |

Unknown keys and unknown sections are rejected when the file is parsed, so a misspelt key fails the command rather than being ignored.

### `[variables]`

A free-form map of template variable names to values. Keys are matched case-insensitively, so `schema_prefix` and `SCHEMA_PREFIX` set the same variable. This is the lowest-priority source: `--var` and `SURREALKIT_VAR_*` both outrank it.

```toml
[variables]
schema_prefix = "myapp"
environment = "development"
```

### `[typegen]`

<OptionsTable
	title="Keys"
	options={[
		{ "name": "typescript", "type": "path", "description": "Directory for the generated `index.ts`. Setting it enables TypeScript output for `typegen` and `sync`." },
		{ "name": "format", "type": "string", "description": "Formatter run on the generated file. The file path is appended to the command as its last argument. Failures are warnings, never errors." }
	]}
/>

```toml
[typegen]
typescript = "src/types"
format = "biome check --write"
```

### `[schema.<name>]`

_(since v1.0)_

Declares one schema module. The section name is the module name.

<OptionsTable
	title="Keys"
	options={[
		{ "name": "path", "type": "path", "default": "<folder>/modules/<name>/schema", "description": "Where this module's `.surql` files live. A relative path resolves against the project folder; an absolute path is used as given." },
		{ "name": "depends_on", "type": "string[]", "default": "[]", "description": "Modules that must be applied before this one. Selecting a module also selects everything it depends on." }
	]}
/>

Module names are lowercase `a-z`, `0-9`, `_` and `-`, must start with a letter or a digit, and are at most 64 characters. `meta` is reserved. The charset is narrow because the name appears in metadata partition keys, lock keys, and as a directory name.

```toml
[schema.core]

[schema.billing]
depends_on = ["core"]
```

### `[target.<name>]`

_(since v1.0)_

Declares one database to apply modules to. Every field is optional and falls back to the ambient connection, so a target usually names only its namespace and database.

<OptionsTable
	title="Keys"
	options={[
		{ "name": "host", "type": "string", "description": "Database endpoint URL. Inherits `--host` when unset." },
		{ "name": "ns", "type": "string", "description": "Namespace. Inherits `--ns` when unset." },
		{ "name": "db", "type": "string", "description": "Database name. Inherits `--db` when unset." },
		{ "name": "user", "type": "string", "description": "Username. Inherits `--user` when unset." },
		{ "name": "pass_env", "type": "string", "description": "Name of an environment variable holding this target's password. Passwords are never read from the config file itself." },
		{ "name": "auth_level", "type": "string", "description": "One of `root`, `namespace` (or `ns`), `database` (or `db`), and `none` (also spelled `no-auth` or `noauth`)." },
		{ "name": "schemas", "type": "string[]", "description": "Restrict which modules apply to this target. All declared modules apply when unset." },
		{ "name": "primary", "type": "boolean", "description": "Use this target when neither `--target` nor `--all` is given. Defaults to false, and at most one target may set it." }
	]}
/>

```toml
[target.acme]
ns = "acme"
db = "prod"
pass_env = "ACME_DB_PASSWORD"
primary = true

[target.warehouse]
ns = "internal"
db = "analytics"
schemas = ["core", "analytics"]
pass_env = "WAREHOUSE_DB_PASSWORD"
```

## Passwords

A `pass` or `password` key under `[target.*]` is rejected. SurrealKit scans for it before parsing the rest of the file, so the error names the fix and lists every offending target at once:

```text title="Error output"
passwords must not be committed to source control, but surrealkit.toml sets them inline:
  [target.acme] pass
  [target.globex] password
Use environment indirection instead:
    pass_env = "MY_DB_PASSWORD"
```

The offending value is never echoed. Name an environment variable with `pass_env` instead, and set it wherever the command runs. A `pass_env` that names an unset variable fails before any connection is opened, so a misconfigured target cannot leave a fan-out half applied.

## Validation

The whole file is validated before a database connection is attempted, so a mistake costs a failed command rather than a partial write:

- Module names must satisfy the charset and length rules above, and must not be `meta`.
- A `depends_on` entry must name a declared module, and a module cannot depend on itself.
- Dependency cycles are rejected, with the cycle named in the error.
- A `schemas` entry under `[target.*]` must name a declared module.
- At most one target may set `primary = true`.
- Any unknown key or section is a parse error.

## A complete file

```toml title="surrealkit.toml"
[variables]
app_name = "acme"

[typegen]
typescript = "src/types"
format = "biome check --write"

[schema.core]
# path defaults to database/modules/core/schema

[schema.billing]
depends_on = ["core"]

[target.acme]
ns = "acme"
db = "prod"
pass_env = "ACME_DB_PASSWORD"
primary = true

[target.globex]
ns = "globex"
db = "prod"
pass_env = "GLOBEX_DB_PASSWORD"
```

## Next steps

- [Schema modules and targets](/docs/manage/schema-migration/modules-and-targets.md): apply several schemas across several databases
- [Template variables](/docs/manage/schema-migration/template-variables.md): parameterise schema files per environment
- [Type generation](/docs/manage/schema-migration/typegen.md): emit JSON and TypeScript from a live database

---

Source: https://surrealdb.com/docs/manage/schema-migration/embed-schema-macro

# embed_schema! macro

Bake .surql schema into the Rust binary with embed_schema!.

The `embed_schema!` macro reads your `database/schema/` directory at compile time and generates a Rust module containing the SQL for every `.surql` file it finds. Because the schema is compiled into the binary, there are no external files to deploy and the schema version is always tied to the application version.

Add SurrealKit to your dependencies. The `embed_schema!` macro is re-exported from the main crate, so a single dependency is enough:

```toml
[dependencies]
surrealkit = "1.0.0-beta.1"
```

## Basic usage

Call the macro at the crate root (typically `main.rs` or `lib.rs`):

```rust
surrealkit::embed_schema!();
```

This generates an `embedded_schema` module. Call `sync` on it after connecting to apply any outstanding schema changes:

```rust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let db = surrealkit::connect(
        &surrealkit::DbCfg::from_env(None, &Default::default())?
    ).await?;

    embedded_schema::sync(&db).await?;

    // application startup continues here
    Ok(())
}
```

`sync` behaves identically to `surrealkit sync` from the CLI: it applies new or changed definitions and prunes any that have been removed, using the `__entity` metadata table to track state.

## What the macro generates

Given a `database/schema/` directory with these files:

```text
database/schema/
├── users.surql
└── orders.surql
```

The macro generates roughly:

```rust
pub mod embedded_schema {
    pub static SCHEMA: &[surrealkit::EmbeddedSchemaFile] = &[
        surrealkit::EmbeddedSchemaFile {
            path: "database/schema/users.surql",
            sql: "DEFINE TABLE user SCHEMAFULL; ...",
        },
        surrealkit::EmbeddedSchemaFile {
            path: "database/schema/orders.surql",
            sql: "DEFINE TABLE order SCHEMAFULL; ...",
        },
    ];

    pub async fn sync(
        db: &surrealkit::Surreal<surrealkit::engine::any::Any>,
    ) -> surrealkit::anyhow::Result<()> {
        surrealkit::Sync::embedded(SCHEMA).run(db).await
    }
}
```

The generated `SCHEMA` static is public, so when you need to customise sync behaviour you can pass it to the [`Sync`](/docs/manage/schema-migration/library.md) builder directly instead of calling `embedded_schema::sync`:

```rust
use surrealkit::Sync;

Sync::embedded(embedded_schema::SCHEMA)
    .prune(false)
    .run(&db)
    .await?;
```

## Embedding several modules

_(since v1.0)_

A project split into [schema modules](/docs/manage/schema-migration/modules-and-targets.md) names each one and the directory it comes from:

```rust
surrealkit::embed_schema!(
    core    = "database/modules/core/schema",
    billing = "database/modules/billing/schema",
);
```

This generates a submodule per arm, alongside a top-level `sync` that applies all of them:

```rust
embedded_schema::sync(&db).await?;           // every module, in declaration order
embedded_schema::billing::sync(&db).await?;  // one module
```

Order the arms so that a module follows the ones it depends on. `embedded_schema::sync` exists in both the single- and named-module forms, so moving from one module to several needs no change at the call site. `embed_schema!()` and `embed_schema!("dir")` are unchanged.

## Embedding seed files

`embed_seed!` is the counterpart for [seed data](/docs/manage/schema-migration/seeding.md), reading `database/seed/**/*.surql` at compile time:

```rust
surrealkit::embed_seed!();

embedded_seed::seed(&db).await?;
```

Embedded seeds are tracked in `__seed` exactly as filesystem seeds are, so each file runs once even though the application calls this on every start.

## Compile-time rebuild behaviour

Cargo re-runs the macro whenever a `.surql` file in `database/schema/` changes, because the macro registers each file with `include_str!`. This means schema changes always produce a fresh build, and there is no risk of shipping stale SQL.

> [!WARNING]
> `include_str!` registers the files that exist when the macro runs, so the directory listing itself is not tracked. **Adding** a `.surql` file does not trigger a rebuild, and the new file is silently absent from the binary. Add a `build.rs` to cover that:
>
> ```rust
> fn main() {
>     println!("cargo:rerun-if-changed=database");
> }
> ```

## When to use the macro vs runtime loading

|-| `embed_schema!` | Runtime `Sync` |
|---|---|---|
| Schema location | Compiled into binary | Built from files at runtime |
| Deployment | No schema files needed | Schema directory must be present |
| Dev iteration | Rebuild required on change | Files can be swapped without rebuild |
| Best for | Production builds, Docker images | Development, mounted volumes |

For most production deployments `embed_schema!` is the right choice. For local development or environments that mount schema as a volume, building an `EmbeddedSchemaFile` slice at runtime and passing it to [`Sync::embedded`](/docs/manage/schema-migration/library.md) is more convenient.

---

Source: https://surrealdb.com/docs/manage/schema-migration/getting-started/existing-databases

# Existing databases

Adopt SurrealKit in a project that already has a SurrealDB database by capturing a baseline snapshot and moving to the Rollouts workflow.

If your project already has a running SurrealDB database, you can adopt SurrealKit without disrupting it. A baseline snapshot records the current schema state so SurrealKit can compute diffs from there.

## 1. Install and initialise

Install SurrealKit and initialise the project directory if you have not already done so:

```bash
cargo binstall surrealkit
surrealkit init
```

## 2. Mirror your existing schema into files

Before taking a baseline, write your existing schema definitions into `.surql` files under `database/schema/`. These files should reflect what is currently in the database, as SurrealKit treats them as the target state going forward.

You can use `INFO FOR DB` and `INFO FOR TABLE` in SurrealQL to inspect what is currently defined:

```surql
INFO FOR DB;
INFO FOR TABLE my_table;
```

The following code can be used to return all the define statements in a database as an array of strings.

```surql
LET $db = INFO FOR DB;
  $db.tables.values() +
  $db.users.values() + 
  $db.tables.keys().map(|$t| {
    LET $i = INFO FOR TABLE $t;
    $i.fields.?.values() + $i.indexes.?.values()
  }).flatten().filter(|$v| !!$v);
```

Copy the returned `DEFINE` statements into appropriately named files in `database/schema/`.

## 3. Take a baseline snapshot

Once your schema files mirror the live database, run:

```bash
surrealkit rollout baseline --user root --pass secret
```

This connects to the database, captures its current schema state, and writes two snapshot files:

```text
database/snapshots/schema_snapshot.json
database/snapshots/catalog_snapshot.json
```

These snapshots tell SurrealKit what the database looked like before any SurrealKit-managed rollout. Future `rollout plan` commands diff against this baseline to produce migration manifests.

> [!NOTE]
> The baseline command does not modify the database. It only reads the current state and writes the snapshot files locally.

## 4. Plan and apply future changes

With a baseline in place, use the standard Rollouts workflow for all subsequent schema changes:

```sh
# Edit files in database/schema/, then:

surrealkit rollout plan --name describe_your_change
surrealkit rollout start <rollout-id>

# Deploy your application changes
surrealkit rollout complete <rollout-id>
```

See the [Rollouts](/docs/manage/schema-migration/rollouts.md) page for the full reference.

## Using Sync on existing databases

You can also use `surrealkit sync` against an existing database, but **be aware that Sync will remove definitions from the database that are not present in your schema files (pruning)**. If your schema files are not yet complete, this could delete definitions you did not intend to remove.

To prevent accidental pruning on a shared database, Sync requires an explicit flag:

```bash
surrealkit sync --allow-shared-prune
```

For production and shared databases, use the Rollouts workflow to keep full control over what changes are applied and when.

## Next steps

- [Rollouts](/docs/manage/schema-migration/rollouts.md): full reference for the phased migration workflow
- [Sync vs Rollouts](/docs/manage/schema-migration/getting-started/sync-vs-rollouts.md): a side-by-side comparison

---

Source: https://surrealdb.com/docs/manage/schema-migration/getting-started/new-databases

# New databases

Start a new SurrealDB project with SurrealKit from the beginning, writing schema files and syncing to a local database.

If you are starting a new project, SurrealKit can manage your schema from the very first definition. This guide walks through initialising a project, writing your first schema file, and pushing it to a local SurrealDB instance.

## 1. Initialise the project

In the root of your repository, run:

```bash
surrealkit init
```

This creates a `database/` directory with the following layout:

```text
database/
├── schema/
├── seed/
├── tests/
└── rollouts/
```

## 2. Write a schema file

Create a `.surql` file inside `database/schema/`. Each file can contain one or more `DEFINE` statements.

```surql
-- database/schema/users.surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD email ON user TYPE string ASSERT string::is_email($value);
DEFINE FIELD created_at ON user TYPE datetime DEFAULT time::now();
DEFINE INDEX unique_email ON user FIELDS email UNIQUE;
```

Schema files can be organised by table, by domain, or kept in a single file. SurrealKit applies everything in `database/schema/` together.

## 3. Point at a database

Create a [SurrealDB Cloud instance](/docs/manage/instances.md) if you do not have one, and read its endpoint:

```bash
surreal cloud instance endpoint my-instance
```

Any SurrealDB endpoint works here. Cloud is used throughout these pages because it needs no setup of its own.

## 4. Sync your schema

```bash
surrealkit --user root --pass secret sync
```

SurrealKit reads every file in `database/schema/`, applies the `DEFINE` statements to the database, and records a content hash for each file in an internal `__entity` metadata table. Future syncs only re-apply files that have changed.

## 5. Enable watch mode during development

Pass `--watch` to keep SurrealKit running and automatically re-sync whenever a schema file changes:

```bash
surrealkit --user root --pass secret sync --watch
```

## Vite integration

If your project uses Vite, the `vite-plugin-surrealkit` package runs sync automatically when the dev server starts:

```bash
npm install --save-dev vite-plugin-surrealkit
```

```ts
// vite.config.ts
import { defineConfig } from 'vite';
import { surrealkitPlugin } from 'vite-plugin-surrealkit';

export default defineConfig({
    plugins: [
        surrealkitPlugin(),
    ],
});
```

The plugin watches `database/schema/**/*.surql` and re-syncs on any change, with debouncing to avoid overlapping runs.

## Storing connection details

Rather than passing flags on every command, store your local connection details in a `.env` file at the project root:

```bash title=".env"
SURREALDB_HOST=wss://production-6xk2.aws-euw1.surreal.cloud
SURREALDB_NAMESPACE=dev
SURREALDB_NAME=myapp
SURREALDB_USER=root
SURREALDB_PASSWORD=secret
```

With those set, `surrealkit sync` picks them up automatically. SurrealKit reads `.env` and `.env.local` from the working directory.

Project settings that are not connection details - template variables, type generation, schema modules and targets - live in [`surrealkit.toml`](/docs/manage/schema-migration/configuration.md) instead.

## Next steps

- [Sync](/docs/manage/schema-migration/sync.md): full reference for all sync options
- [Sync vs Rollouts](/docs/manage/schema-migration/getting-started/sync-vs-rollouts.md): when to move from Sync to the Rollouts workflow
- [Seeding](/docs/manage/schema-migration/seeding.md): add starting data to the database

---

Source: https://surrealdb.com/docs/manage/schema-migration/getting-started/sync-vs-rollouts

# Sync vs Rollouts

Understand when to use SurrealKit's Sync mode for development and Rollouts mode for production, and how teams typically combine both.

SurrealKit has two modes for applying schema changes. Which one you use depends on the environment.

## At a glance

|-| Sync | Rollouts |
|---|---|---|
| **Philosophy** | Desired state: your files are truth | Phased migration via reviewed manifests |
| **Speed** | Immediate | Planned and staged |
| **Destructive changes** | Automatic (pruning) | Explicit, in a separate phase |
| **Rollback** | Restore previous files and re-sync | `rollout rollback` command |
| **Concurrency safety** | Not guaranteed | Blocked by in-progress rollout |
| **Best for** | Local dev, ephemeral, disposable DBs | Shared, staging, production |

## When to use Sync

Sync is the right choice when:

- You are working on a local or personal development database
- The database is ephemeral or disposable (a Docker container, a CI job, a preview environment)
- Losing and recreating data is acceptable
- You want fast iteration without planning each change explicitly

Sync gives you an instant feedback loop: edit a `.surql` file, save it, and the database reflects the change within seconds (especially with `--watch` mode).

## When to use Rollouts

Rollouts are the right choice when:

- You are modifying a database shared with other developers or services
- You are promoting changes to staging or production
- The migration involves both non-destructive additions and destructive removals that must happen in separate phases, with your application deployed in between
- You need a record of what was changed and when, with the ability to roll back

The phased approach (expand first, then contract after the application is updated) means schema changes and application code can be deployed independently, with no downtime or data loss.

## How teams typically combine both

A common pattern is:

1. **Local development**: every developer runs `surrealkit sync --watch` against their own local SurrealDB instance. Schema changes are iterated freely.
2. **Pull request / CI**: a `surrealkit test` step validates the schema and permissions against an ephemeral database. Sync is used here too, since the CI database is disposable.
3. **Staging / production**: when changes are ready to promote, a developer runs `surrealkit rollout plan` to generate a migration manifest, commits it, and the deployment pipeline runs `rollout start` and `rollout complete` at the right points around the application release.

## Prune behaviour on shared databases

Sync's automatic pruning (removing definitions no longer in your files) is safe on a personal database but dangerous on a shared one. If a colleague has added a definition not yet in your local files, Sync will delete it.

SurrealKit protects against this: running `surrealkit sync` against a database that already contains SurrealKit metadata (the `__entity` table) will require you to pass `--allow-shared-prune` explicitly before it will prune anything. The flag exists to make you stop and confirm, so do not pass it routinely.

## Next steps

- [Sync](/docs/manage/schema-migration/sync.md): full Sync reference
- [Rollouts](/docs/manage/schema-migration/rollouts.md): full Rollouts reference

---

Source: https://surrealdb.com/docs/manage/schema-migration/library

# Using SurrealKit as a library

Embed SurrealKit in a Rust application, and skip the CLI.

SurrealKit is published as a Rust crate, so you can drive connections, schema sync, rollouts, and seeding from application code rather than the CLI. This suits applications that apply schema inside their own process at startup, for example with an embedded SurrealDB backend (RocksDB, SpeeDB) or when running SurrealDB in the same binary during tests.

## Adding the dependency

```toml
[dependencies]
surrealkit = "1.0.0-beta.1"
```

The default features include `cli`, which pulls in the argument parser and its supporting crates. A library-only consumer can drop them:

```toml
[dependencies]
surrealkit = { version = "1.0.0-beta.1", default-features = false, features = ["kv-mem"] }
```

> [!WARNING]
> The `surrealkit` binary declares `cli` as a required feature, so an install with `--no-default-features` and without `cli` builds no binary at all. Use the trimmed form for a dependency, not for installing the CLI.

### Logging

_(since v1.0)_

The library reports progress through the `log` facade and is silent unless the consuming application installs a logger. Initialise one and set `RUST_LOG=surrealkit=info` to see what the CLI would print.

## Sync vs rollout

SurrealKit gives you two ways to get schema into a database. Pick based on whether the database is disposable or shared.

|-| Sync | Rollout |
|---|---|---|
| Mental model | Declarative *desired state*: "make the database match this schema" | Staged, reviewable *migration* with an explicit undo |
| Applies | All changed files, idempotently | Ordered steps across `start` / `complete` / `rollback` phases |
| Removes objects | Automatically (prune) | Only in the `complete` phase, via explicit steps |
| Reversible | No | Yes (`rollback`) |
| Use when | Dev, test, CI, single-owner or embedded databases | Shared and production databases needing expand → contract and a rollback path |

The two work together: use **sync** for everyday schema, and use a **rollout** when a change needs to land safely while old and new code run side by side.

## Connecting

`DbCfg` reads connection details from the same environment variables as the CLI (`SURREALDB_HOST`, `SURREALDB_NAMESPACE`, and so on), with optional overrides. `connect` builds the `surrealdb::Surreal` client and authenticates:

```rust
use surrealkit::{DbCfg, DbOverrides, connect};

let cfg = DbCfg::from_env(None, &DbOverrides::default())?;
let db = connect(&cfg).await?;
```

`DbOverrides` lets you override specific fields programmatically while leaving the rest to the environment:

```rust
let cfg = DbCfg::from_env(None, &DbOverrides {
    host: Some("wss://production-6xk2.aws-euw1.surreal.cloud".to_string()),
    ..Default::default()
})?;
```

### In-process SurrealDB

For an embedded SurrealDB engine (`mem://`, `rocksdb://`, `speedb://`), construct a `Surreal` client directly and pass it to any library function:

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::Config;
use surrealdb::opt::capabilities::Capabilities;

let db = connect(("mem://", Config::new().capabilities(Capabilities::all()))).await?;
db.use_ns("main").use_db("main").await?;
```

## Syncing schema

Use the [`Sync`] builder to apply a slice of schema files to the database. By default it prunes objects that are no longer present and stops on the first error (`prune = true`, `fail_fast = true`):

```rust
use surrealkit::{EmbeddedSchemaFile, Sync, Surreal};
use surrealkit::engine::any::Any;

static SCHEMA: &[EmbeddedSchemaFile] = &[EmbeddedSchemaFile {
    path: "database/schema/person.surql",
    sql:  "DEFINE TABLE person SCHEMALESS;",
}];

async fn run(db: &Surreal<Any>) -> anyhow::Result<()> {
    // Defaults: prune = true, fail_fast = true.
    Sync::embedded(SCHEMA).run(db).await?;
    Ok(())
}
```

The builder methods customise behaviour before calling `run`:

```rust
Sync::embedded(SCHEMA)
    .prune(false)               // don't remove objects missing from SCHEMA
    .allow_all_statements(true) // permit non-DEFINE statements (INSERT/UPDATE/…)
    .allow_shared_prune(true)   // permit pruning on a shared database
    .dry_run(true)              // report what would change without applying
    .run(db)
    .await?;
```

`Sync` runs setup internally and reads nothing from the filesystem. To embed your `.surql` files at compile time instead of hand-writing the slice, use the [`embed_schema!` macro](/docs/manage/schema-migration/embed-schema-macro.md).

### `EmbeddedSchemaFile`: `path` vs `sql`

The two fields serve different purposes:

- **`path` is a stable tracking key**, not a path that must exist on disk. SurrealKit stores it in its metadata tables to identify the file, detect content changes, and prune files that disappear. Keep it stable across releases: renaming it makes SurrealKit treat the old key as deleted and the new one as added.
- **`sql` is the content** that gets applied. Changing `sql` while holding `path` constant is exactly what triggers a re-apply on the next sync.

## Rollouts

Rollouts are defined entirely in code, with no TOML or `.surql` files on disk required. Build a spec with [`RolloutSpec::builder`] and drive it with the [`Rollout`] facade.

> [!IMPORTANT]
> Since 1.0, `Rollout::start`, `Rollout::complete` and `Rollout::rollback` are purely in-database. They previously defaulted to `./database` and created `./database/setup.surql` in the caller's working directory. Call `.folder("database")` to opt back into the filesystem workflow.

### Status lifecycle

```text
planned → running_start → ready_to_complete → running_complete → completed
                                   │
                                   └── running_rollback → rolled_back
```

`completed` and `rolled_back` are terminal. `failed` and the `running_*` states are stuck states from an interrupted run; recover them with [`Rollout::abandon`] (or the CLI [`repair`](/docs/manage/schema-migration/rollouts.md) command). Only one rollout may be in a non-terminal state at a time.

### Lifecycle example

```rust
use surrealkit::{
    Rollout, RolloutSpec, RolloutStep, RolloutPhase, RolloutCompatibility,
    EmbeddedSchemaFile, EntityKey, EntityKind, Surreal,
};
use surrealkit::engine::any::Any;

// The desired schema once the rollout completes (used to compute the managed
// catalog). Pass `&[]` if your steps fully describe the entity changes.
static TARGET: &[EmbeddedSchemaFile] = &[EmbeddedSchemaFile {
    path: "database/schema/account.surql",
    sql:  "DEFINE TABLE account SCHEMAFULL;",
}];

async fn run(db: &Surreal<Any>) -> anyhow::Result<()> {
    let spec = RolloutSpec::builder("20260604__add_account")
        .name("Add account table")
        .compatibility(RolloutCompatibility::Phased)
        // Expand: add the new table (non-destructive).
        .step(RolloutStep::apply_schema(
            "create_account", RolloutPhase::Start,
            "DEFINE TABLE account SCHEMAFULL;",
        ))
        // Backfill during complete. run_sql must be safe to re-run.
        .step(RolloutStep::run_sql(
            "backfill", RolloutPhase::Complete,
            "UPDATE account SET active = true WHERE active = NONE;",
        ))
        // Undo the expand phase on rollback.
        .step(RolloutStep::remove_entities(
            "undo", RolloutPhase::Rollback,
            vec![EntityKey { kind: EntityKind::Table, scope: None, name: "account".into() }],
        ))
        .build();

    let rollout = Rollout::new(spec, TARGET);

    rollout.start(db).await?;        // expand (blocks if another rollout is active)
    // ... deploy new code, drain traffic ...
    rollout.complete(db).await?;     // contract, or call rollout.rollback(db).await?
    Ok(())
}
```

### Step actions

Each [`RolloutStep`] carries exactly one action, built with a constructor, so invalid combinations cannot be represented:

| Constructor | What it does |
|---|---|
| `RolloutStep::apply_schema(id, phase, sql)` | Apply inline DDL (`OVERWRITE` is injected; safe to retry) |
| `RolloutStep::run_sql(id, phase, sql)` | Run data-mutation SQL (must be safe to re-run) |
| `RolloutStep::assert_sql(id, phase, sql, expect)` | Assert a query's output equals `expect` |
| `RolloutStep::remove_entities(id, phase, entities)` | `REMOVE … IF EXISTS` the given objects |

Entities are identified with `EntityKey { kind: EntityKind, scope, name }`, where `EntityKind` is an enum (`Table`, `Field`, `Index`, `Module`, and so on) rather than a string.

### Recovering a stuck rollout

If a process dies mid-rollout, the rollout is left in a `running_*` or `failed` state and blocks new rollouts. Inspect the recorded state, then recover it:

```rust
use surrealkit::{Rollout, RolloutSpec, Surreal};
use surrealkit::engine::any::Any;

async fn run(db: &Surreal<Any>, spec: RolloutSpec) -> anyhow::Result<()> {
    // Inspect the recorded state.
    let rollout = Rollout::new(spec, &[]);
    if let Some(report) = rollout.status(db).await? {
        println!("{:?}: {:?}", report.status, report.last_error);
    }

    // Last resort: force a wedged rollout to a terminal state so a new one can
    // start. This does NOT revert schema changes already applied. Reconcile
    // those with a fresh sync or a follow-up rollout.
    Rollout::abandon(db, "20260604__add_account").await?;
    Ok(())
}
```

## Seeding

[`seed`] runs the `.surql` files in a project's `seed/` directory (lexicographic order), applying template variables:

```rust
use surrealkit::{seed, TemplateVars, Surreal};
use surrealkit::engine::any::Any;

async fn run(db: &Surreal<Any>) -> anyhow::Result<()> {
    seed(db, "database", &TemplateVars::default()).await?;
    Ok(())
}
```

The second argument is the project folder (the directory containing `seed/`), matching the CLI's `--folder` / `SURREALDB_FOLDER`.

Each file is applied once and re-applied only when its content changes, tracked in `__seed`, so this is safe to call on every application start. Use the [`Seed`] builder for runtime control, including `force` and embedded seed files. See [Seeding](/docs/manage/schema-migration/seeding.md).

## Template variables

`${VAR}` placeholders in schema, seed, or rollout SQL are substituted from a [`TemplateVars`] map before execution. Lookups are case-insensitive, and an undefined variable is an error naming the missing key and file. Pass them via `Sync::vars(...)`, `Rollout::vars(...)`, or the `seed` argument:

```rust
use surrealkit::{Sync, TemplateVars};

let mut vars = TemplateVars::default();
vars.insert("schema_prefix", "acme");

Sync::embedded(SCHEMA).vars(vars).run(db).await?;
```

See [Template variables](/docs/manage/schema-migration/template-variables.md) for the full resolution rules.

## Metadata tables

SurrealKit maintains internal tables in your namespace and database, created automatically:

| Table | Purpose |
|---|---|
| `__entity` | Tracks every schema object SurrealKit manages (content hash, tracking key) |
| `__rollout` | Tracks rollout execution state (see the status lifecycle above) |
| `__seed` | Tracks which seed files have been applied |

Records in `__entity` are partitioned per [schema module](/docs/manage/schema-migration/modules-and-targets.md). Apply a named module with `Sync::embedded(BILLING).module("billing")?`, and pass the module to `rollout::run_baseline` and `rollout::run_abandon_rollout`, both of which gained a module argument in 1.0.

## Next steps

- [Library usage example](/docs/manage/schema-migration/library/example.md): full worked sync and rollout programs
- [`embed_schema!` macro](/docs/manage/schema-migration/embed-schema-macro.md): bake schema into the binary at compile time
- [Upgrading to SurrealKit 1.0](/docs/manage/schema-migration/upgrading.md#if-you-use-the-rust-library): renamed and removed library items
- [Type generation](/docs/manage/schema-migration/typegen.md): generate JSON and TypeScript types from the live schema
</content>
</invoke>

---

Source: https://surrealdb.com/docs/manage/schema-migration/library/example

# Library usage example

Worked examples of using SurrealKit's library API for both sync and rollouts in a Rust application.

Worked examples of using SurrealKit's library API directly in Rust, covering both sync (for development and ephemeral databases) and rollouts (for shared and production databases).

For the macro-based approach to sync, see the [`embed_schema!` macro](/docs/manage/schema-migration/embed-schema-macro.md).

## Shared setup

Both examples below assume the following `Cargo.toml` and connection setup.

```toml
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"

[dependencies]
surrealkit = "1.0.0-beta.1"
surrealdb = "3"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
```

```rust
use surrealkit::{DbCfg, DbOverrides, connect};

let cfg = DbCfg::from_env(None, &DbOverrides::default())?;
let db = connect(&cfg).await?;
```

`DbCfg::from_env` reads `SURREALDB_HOST`, `SURREALDB_NAMESPACE`, `SURREALDB_NAME`, `SURREALDB_USER`, and `SURREALDB_PASSWORD` from the environment or a `.env` file.

## Sync

Use sync against local or ephemeral databases where fast iteration matters and pruning is safe. Schema files are embedded at compile time with `include_str!`, then applied with the [`Sync`](/docs/manage/schema-migration/library.md) builder.

```rust
use anyhow::Result;
use surrealkit::{DbCfg, DbOverrides, EmbeddedSchemaFile, Sync, connect, seed, TemplateVars};

static SCHEMA: &[EmbeddedSchemaFile] = &[
    EmbeddedSchemaFile {
        path: "database/schema/users.surql",
        sql: include_str!("../database/schema/users.surql"),
    },
    EmbeddedSchemaFile {
        path: "database/schema/posts.surql",
        sql: include_str!("../database/schema/posts.surql"),
    },
];

#[tokio::main]
async fn main() -> Result<()> {
    let cfg = DbCfg::from_env(None, &DbOverrides::default())?;
    let db = connect(&cfg).await?;

    // Defaults: prune = true, fail_fast = true.
    Sync::embedded(SCHEMA).run(&db).await?;

    // Optionally load seed data after syncing.
    seed(&db, "database", &TemplateVars::default()).await?;

    Ok(())
}
```

Cargo re-compiles whenever an `include_str!`-referenced file changes, so the binary always reflects the latest schema. `Sync::embedded(...).run(...)` applies new or changed definitions and removes any that have been deleted from the schema files.

Seed files run on every startup, so use `UPSERT` or guard inserts if idempotency matters.

## Rollouts

Use rollouts for shared and production databases. Build a [`RolloutSpec`](/docs/manage/schema-migration/library.md) with the builder, wrap it in the `Rollout` facade, then call `start` and `complete` at the appropriate points around your application deployment.

```rust
use anyhow::Result;
use surrealkit::{
    DbCfg, DbOverrides, connect,
    Rollout, RolloutSpec, RolloutStep, RolloutPhase, RolloutCompatibility,
    EntityKey, EntityKind,
};

#[tokio::main]
async fn main() -> Result<()> {
    let cfg = DbCfg::from_env(None, &DbOverrides::default())?;
    let db = connect(&cfg).await?;

    let spec = RolloutSpec::builder("20260604__add_account")
        .name("Add account table")
        .compatibility(RolloutCompatibility::Phased)
        .step(RolloutStep::apply_schema(
            "create_account", RolloutPhase::Start,
            "DEFINE TABLE account SCHEMAFULL;",
        ))
        .step(RolloutStep::remove_entities(
            "rollback_account", RolloutPhase::Rollback,
            vec![EntityKey { kind: EntityKind::Table, scope: None, name: "account".into() }],
        ))
        .build();

    // `&[]` here means the steps fully describe the entity changes. Pass the
    // desired post-rollout schema files to have SurrealKit compute the catalog.
    let rollout = Rollout::new(spec, &[]);

    rollout.start(&db).await?;

    Ok(())
}
```

After `start` succeeds, deploy your application. Once the new version is stable, call `complete` to apply the contract phase:

```rust
rollout.complete(&db).await?;
```

If something goes wrong before completing, roll back the start phase:

```rust
rollout.rollback(&db).await?;
```

If a process is killed mid-rollout, the `__rollout` record can be left in a `running_*` state. Inspect it with `rollout.status(&db).await?` and recover it with the CLI [`rollout repair`](/docs/manage/schema-migration/rollouts.md) command or `Rollout::abandon(&db, "20260604__add_account").await?`.
</content>

---

Source: https://surrealdb.com/docs/manage/schema-migration/modules-and-targets

# Schema modules and targets

Track schema modules separately and apply them across databases.

_(since v1.0)_

A SurrealKit project applies one set of schema files to one database. That covers most projects and needs no configuration. When a project outgrows it, [`surrealkit.toml`](/docs/manage/schema-migration/configuration.md) can declare **schema modules** - independently tracked sets of `.surql` files - and **targets** - named databases to apply them to.

The two combine into a matrix. Selecting two modules and three targets gives six pairs, applied one at a time.

## The default project

With no `[schema.*]` sections, a project has a single unnamed module at `database/schema`, applied to the connection given by `--host`, `--ns` and `--db`. This is the pre-1.0 layout and nothing about it changed, so an existing project keeps working untouched. Everything below is additive.

## Declaring modules

Each `[schema.<name>]` section names a module. Files live under `database/modules/<name>/`:

```toml title="surrealkit.toml"
[schema.core]

[schema.billing]
depends_on = ["core"]
```

```text
database/
├── schema/                    # the default module, unchanged
└── modules/
    ├── core/
    │   └── schema/
    └── billing/
        └── schema/
```

Each module gets its own directories for the artefacts that belong to a schema. Tests and `setup.surql` belong to the project rather than to any one module, so they are not module-scoped:

| Directory | Default module | Module `billing` |
| --- | --- | --- |
| Schema | `database/schema` | `database/modules/billing/schema` |
| Rollouts | `database/rollouts` | `database/modules/billing/rollouts` |
| Snapshots | `database/snapshots` | `database/modules/billing/snapshots` |
| Seed | `database/seed` | `database/modules/billing/seed` |

Named modules sit under `modules/` rather than inside `database/schema` because the default module walks its schema directory recursively. Nesting a named module inside it would make the default module collect those files and claim ownership of them. Set `[schema.<name>] path` to put a module somewhere else entirely.

### Dependencies

`depends_on` orders application, so a module is never applied before something it depends on. Selecting a module also selects its dependencies, the way `cargo build -p` does. Pass `--no-deps` to select only what you named. Independent modules are ordered alphabetically so runs are reproducible, and dependency cycles are rejected before anything touches a database.

## Independent tracking

Each module owns its own metadata, which is what makes modules safe to mix in one database: **a module only ever prunes its own objects**. Without that, syncing one schema against a database another schema had populated would remove the other's tables.

SurrealKit partitions the `__entity` table by module. The `ns` field is a partition key here, unrelated to a SurrealDB namespace:

| Tracked data | Default module | Module `billing` |
| --- | --- | --- |
| File hashes | `sync` | `sync@billing` |
| Managed entities | `schema` | `schema@billing` |
| Locks | `lock` | `lock@billing` |

Pre-1.0 records already sit in the unqualified partitions, which is why adopting modules needs no migration: existing schema stays the default module with its metadata intact.

> [!WARNING]
> Do not rename a module, and do not change which module is the default, once it has been applied. A module's name determines where its metadata lives, so renaming presents the whole module as stale and the next sync drops its database objects. Create the new module and migrate to it deliberately instead.

## Declaring targets

Each `[target.<name>]` section names a database. A target inherits everything it does not set from the ambient connection, so it usually names only `ns` and `db`:

```toml title="surrealkit.toml"
[target.acme]
ns = "acme"
db = "prod"
pass_env = "ACME_DB_PASSWORD"
primary = true

[target.globex]
ns = "globex"
db = "prod"
pass_env = "GLOBEX_DB_PASSWORD"
```

Passwords are read from the environment through `pass_env`, never from the file. A literal `pass` or `password` key is rejected, and a `pass_env` naming an unset variable fails before any connection opens, so a misconfigured target cannot leave a fan-out half applied. See [Configuration](/docs/manage/schema-migration/configuration.md) for the full key reference.

A target can also point at a different instance entirely, which is the usual shape for promoting the same schema from staging to production:

```toml title="surrealkit.toml"
[target.staging]
host = "wss://staging-4pq7.aws-euw1.surreal.cloud"
ns = "main"
db = "main"
pass_env = "STAGING_DB_PASSWORD"

[target.production]
host = "wss://production-6xk2.aws-euw1.surreal.cloud"
ns = "main"
db = "main"
pass_env = "PRODUCTION_DB_PASSWORD"
```

Get the endpoint for a Cloud instance with [`surreal cloud instance endpoint`](/docs/reference/cli/cloud/commands/instance.md).

### Restricting modules per target

`schemas` limits which modules apply to a target. An analytics database that needs the shared tables but not the billing ones declares only what it wants:

```toml title="surrealkit.toml"
[target.warehouse]
ns = "internal"
db = "analytics"
schemas = ["core", "analytics"]
pass_env = "WAREHOUSE_DB_PASSWORD"
```

## Selecting what to apply

<OptionsTable
	title="Selection flags"
	options={[
		{ "name": "--schema", "short": "-s", "value": "<NAME>", "description": "Apply this module. Repeat for several. Accepts `default` for the unnamed module." },
		{ "name": "--no-deps", "description": "Apply only the modules named with `--schema`, without pulling in their `depends_on` entries." },
		{ "name": "--target", "short": "-t", "value": "<NAME>", "description": "Apply to this target. Repeat for several." },
		{ "name": "--all", "description": "Apply every declared module to every declared target." },
		{ "name": "--keep-going", "description": "Continue to the next target after one fails, instead of stopping." }
	]}
/>

```bash
surrealkit sync                              # every declared module, primary target
surrealkit sync --schema billing             # billing, and core because billing depends on it
surrealkit sync --schema billing --no-deps   # billing alone
surrealkit sync --target acme                # every module, one target
surrealkit sync --all                        # the full matrix
surrealkit sync --all --keep-going           # do not stop at the first failure
```

Omitting `--schema` selects every declared module, or the default module when none are declared. Targets resolve in this order, first match winning:

1. Every `--target` given.
2. Every declared target, when `--all` is passed.
3. The target marked `primary = true`.
4. The only declared target, when exactly one exists.
5. The ambient connection from `--host`, `--ns` and `--db`.

## How a fan-out runs

Targets are applied one at a time. Within a target, modules stop at the first failure, because they are dependency-ordered and the rest would build on a broken base. Targets also stop at the first failure unless you pass `--keep-going`.

There is no transaction across databases, so a failed run can leave some targets applied and others not. Every operation is idempotent, so re-running after a fix is safe.

When a run covers more than one pair, SurrealKit prints a summary and exits non-zero if any pair failed:

```text title="Output"
  schema   target   status
  ------   ------   ------
  core     acme     ok
  billing  acme     FAILED

1 ok, 1 failed
```

## Empty source sets

Before opening any connection, SurrealKit resolves every selected module and refuses if one has no `.surql` files. A wrong working directory or `--folder` value would otherwise look like a schema that had been deleted, and sync would prune the database to match:

```text title="Error output"
refusing filesystem sync: schema_module=billing resolved_schema_dir=./database/modules/billing/schema source_count=0; check --folder / SURREALDB_FOLDER and module selection, or pass --allow-empty-prune if the empty source set is intentional
```

Pass `--allow-empty-prune` when the empty source set is deliberate and you do want everything removed. The check also covers `--dry-run`, `--no-prune` and the first sync of a `--watch` session, because an empty selection cannot otherwise be told apart from a wrong path. A selection where no module applies to any target stays an error rather than a successful no-op.

## Beta limitations

`--schema`, `--target`, `--all` and `--keep-going` are accepted on every subcommand, but not every subcommand acts on them yet. In `1.0.0-beta.1`:

| Command | Honours `--schema` | Honours `--target` and `--all` |
| --- | --- | --- |
| `sync` | Yes | Yes |
| `rollout baseline` | Yes | No |
| Other `rollout` verbs | No | No |
| `seed` | No | No |
| `typegen` | No | No |
| `test` | No | No |

Everything in the "No" column uses the ambient connection from `--host`, `--ns` and `--db`, and the default module's directories. `surrealkit seed --schema billing` therefore seeds `database/seed` rather than `database/modules/billing/seed`, and `surrealkit typegen --target acme` introspects the ambient database. Pass the connection explicitly for those commands until this is resolved.

`--watch` refuses a selection covering more than one module or target, because watching a whole matrix on a timer is rarely what you want.

## Next steps

- [Configuration](/docs/manage/schema-migration/configuration.md): the full `surrealkit.toml` key reference
- [Sync](/docs/manage/schema-migration/sync.md): apply schema files to a database
- [Rollouts](/docs/manage/schema-migration/rollouts.md): phased migrations for shared databases

---

Source: https://surrealdb.com/docs/manage/schema-migration/rollouts

# Rollouts

A phased migration workflow with review, staging and rollback.

Rollouts are SurrealKit's migration system for shared and production databases. Rather than immediately pushing all changes (as Sync does), Rollouts generate a reviewed manifest and apply it in two phases: an expand phase that adds new definitions without breaking existing consumers, and a contract phase that removes old ones after your application has been updated. Deploying schema and application changes separately means no downtime.

## The phased migration model

A complete rollout has three stages:

1. **Start:** applies non-destructive changes: adding tables, fields, indexes, and access methods that new application code will use. The old application code continues to work alongside the new definitions.
2. **App cutover:** you deploy the new version of your application. Both old and new application code remain compatible with the database during this window.
3. **Complete:** applies destructive changes: removing legacy tables, fields, or indexes that are no longer needed. Only run this after the new application version is stable.

## Command workflow

### For an existing database: baseline first

If you are adopting SurrealKit on a database that already exists, capture a baseline before planning any rollout:

```bash
surrealkit rollout baseline --user root --pass secret
```

This reads the current database schema and writes snapshot files to `database/snapshots/`. Future plans diff against this baseline. You only need to run this once.

### Plan a rollout

After editing your schema files, plan a rollout:

```bash
surrealkit rollout plan --name add_customer_indexes
```

SurrealKit computes the diff between the current snapshot and your schema files, then generates a timestamped manifest in `database/rollouts/`:

```text
database/rollouts/20260302153045__add_customer_indexes.toml
```

Review this file before proceeding. It lists each step, the SQL it will execute, and which phase (start or complete) it belongs to. Commit it to your repository alongside your schema changes.

### Start the rollout

```bash
surrealkit rollout start 20260302153045__add_customer_indexes --user root \
  --pass secret
```

This applies all start-phase steps. SurrealKit records resumable state in the `__rollout` metadata table, so if the process is interrupted it can pick up where it left off. Only one rollout can be in progress at a time; concurrent starts are blocked.

### Deploy your application

After `start` completes, deploy the new version of your application. At this point both the old and new schema definitions coexist in the database.

### Complete the rollout

Once the new application version is healthy:

```bash
surrealkit rollout complete 20260302153045__add_customer_indexes --user root --pass secret
```

This applies all complete-phase steps (removing legacy definitions) and marks the rollout as done.

## Rollback

If something goes wrong after `start` but before `complete`, you can undo the start-phase changes:

```bash
surrealkit rollout rollback 20260302153045__add_customer_indexes --user root --pass secret
```

Rollback reverses what `start` applied. It is not available after `complete` has run, so verify your application is working correctly before calling `complete`.

## Other commands

### status

Inspect the state of rollouts stored in the database:

```bash
surrealkit rollout status --user root --pass secret
```

### lint

Validate a manifest file without connecting to the database or applying anything:

```bash
surrealkit rollout lint 20260302153045__add_customer_indexes --user root \
  --pass secret
```

This is useful in CI to catch manifest errors before they reach a shared environment.

### repair

If `rollout complete` (or `rollback`) is killed mid-flight, the `__rollout` record can be left in an intermediate state (`running_complete`, `running_rollback`, or `running_start`) even though the schema changes have already been applied. Re-running `complete` or `rollback` will not always heal the metadata, because the underlying SQL steps are already done, and the stuck rollout blocks any new one from starting.

Use `repair` to finish the metadata transition without re-running any SQL:

```bash
surrealkit rollout repair 20260302153045__add_customer_indexes --user root --pass secret
```

The behaviour depends on the stuck state:

| Stuck state | Repair result |
|---|---|
| `running_complete` | Flips to `completed` and restores the target entities |
| `running_rollback` | Flips to `rolled_back` and restores the source entities |
| `running_start` | Flips to `failed`; re-run `start` (idempotent) or `rollback` |

Repair never re-executes per-step SQL. It only reconciles the `__rollout` and `__entity` tables so subsequent `sync` and `plan` runs see a clean state.

## Rollout state machine

Each rollout transitions through the following states, recorded in the `__rollout` table:

```text
planned → running_start → ready_to_complete → running_complete → completed
                                       │
                                       └── running_rollback → rolled_back
```

| State | Meaning |
|---|---|
| `planned` | Manifest exists; neither start nor complete has run |
| `running_start` | Start phase is in progress or was interrupted |
| `ready_to_complete` | Start phase completed successfully |
| `running_complete` | Complete phase is in progress or was interrupted |
| `completed` | Both phases completed (terminal) |
| `running_rollback` | Rollback is in progress or was interrupted |
| `rolled_back` | Start phase was reversed (terminal) |
| `failed` | A phase failed; recover with `rollback` or `repair` |

`completed` and `rolled_back` are terminal. The `running_*` states and `failed` are stuck states left by an interrupted run; recover them with [`repair`](#repair). Only one rollout may be in a non-terminal state at a time.

## Artifacts

| Path | Description |
|---|---|
| `database/rollouts/*.toml` | Generated rollout manifests; commit these |
| `database/snapshots/schema_snapshot.json` | Schema state snapshot used for diffing |
| `database/snapshots/catalog_snapshot.json` | Catalog snapshot |

## Next steps

- [Template Variables](/docs/manage/schema-migration/template-variables.md): parameterise schema files for different environments
- [Testing](/docs/manage/schema-migration/testing.md): validate schema and permissions before promoting a rollout

---

Source: https://surrealdb.com/docs/manage/schema-migration/seeding

# Seeding

surrealkit seed applies .surql data files to a database. Each file runs once and re-runs only when its content changes, tracked by hash in the __seed table, so seeding is safe on every deploy.

Seeding applies `.surql` data files to a database: reference data, default records, a demo dataset. It is separate from [schema sync](/docs/manage/schema-migration/sync.md), which manages definitions rather than records.

```bash
surrealkit seed --user root --pass secret
```

## Seed files

Seed files live in `database/seed/`, which `surrealkit init` scaffolds. Only files ending in `.surql` are read, and only at the top level of the directory - subdirectories are ignored. Files run in lexicographic order by filename, so a numeric prefix fixes the sequence when one file depends on another:

```text
database/seed/
├── 000_regions.surql
├── 010_plans.surql
└── 020_demo_users.surql
```

## Each file runs once

_(since v1.0)_

`surrealkit seed` is idempotent. SurrealKit records every file it applies in a `__seed` table and skips the ones that have not changed since, so the command is safe to run on every deploy:

```bash
surrealkit seed --user root --pass secret
```

```text title="Output"
Seeding from database/seed (3 files found)
  skipping database/seed/000_regions.surql (unchanged)
  skipping database/seed/010_plans.surql (unchanged)
  executing database/seed/020_demo_users.surql
Seeded 1 file(s); 2 unchanged
```

> [!IMPORTANT]
> Before 1.0, `surrealkit seed` re-ran every file on every invocation. If you used it to reset a development database, that no longer happens - use `--force` below.

A file is tracked by its path, and compared by a hash of its contents. Editing a file re-runs it; renaming or moving one makes SurrealKit see a removal and a new file, so the new path runs again.

The hash is taken from the file on disk, before [template variables](/docs/manage/schema-migration/template-variables.md) are substituted. Changing a `--var` value therefore does not re-run a seed on its own. Edit the file, or use `--force`.

## Forcing a re-run

`--force` ignores the tracking table and applies every file:

```bash
surrealkit seed --force --user root --pass secret
```

Seed files that are not written to be idempotent will duplicate their data when re-run. Prefer `UPSERT` over `CREATE`, or `CREATE` with an explicit record id, so that a forced run converges rather than accumulates.

## The tracking table

SurrealKit creates `__seed` in the target database:

```surql
DEFINE TABLE IF NOT EXISTS __seed SCHEMAFULL PERMISSIONS NONE;
DEFINE FIELD IF NOT EXISTS key ON __seed TYPE string;
DEFINE FIELD IF NOT EXISTS hash ON __seed TYPE string;
DEFINE FIELD IF NOT EXISTS applied_at ON __seed TYPE datetime DEFAULT time::now();
DEFINE INDEX IF NOT EXISTS by_seed_key ON __seed FIELDS key UNIQUE;
```

The table is created on the first write of a run rather than upfront, so a run where every file is unchanged performs no schema changes and needs no `DEFINE` privileges. Reads tolerate its absence and treat every file as new.

Like the other SurrealKit metadata tables, `__seed` is managed by the tool and should not be edited directly, except when [upgrading from an older layout](/docs/manage/schema-migration/upgrading.md).

## Template variables

Seed files support the same `${VAR}` substitution as schema files, so environment-specific values can be parameterised:

```surql
-- database/seed/000_regions.surql
CREATE region:eu SET name = 'Europe', bucket = '${asset_bucket}';
```

## Embedding seeds in a binary

An application that ships without its `database/` directory can bake the seed files into the binary at compile time with the `embed_seed!` macro, the counterpart to [`embed_schema!`](/docs/manage/schema-migration/embed-schema-macro.md):

```rust
// Reads database/seed/**/*.surql relative to Cargo.toml at compile time.
surrealkit::embed_seed!();

async fn run(db: &surrealkit::Surreal<surrealkit::engine::any::Any>) -> anyhow::Result<()> {
    embedded_seed::seed(db).await?;
    Ok(())
}
```

Embedded seeds are tracked in `__seed` exactly as filesystem seeds are, so an application can call this on every start.

> [!NOTE]
> `surrealkit seed` reads `database/seed` regardless of `--schema`, so a project using [schema modules](/docs/manage/schema-migration/modules-and-targets.md) cannot yet seed a named module's directory from the CLI in `1.0.0-beta.1`. Use the library's `Seed` builder, or point `--folder` at the module.

## Next steps

- [Sync](/docs/manage/schema-migration/sync.md): apply schema definitions to a database
- [Template variables](/docs/manage/schema-migration/template-variables.md): parameterise files per environment
- [Using SurrealKit as a library](/docs/manage/schema-migration/library.md): drive seeding from Rust

---

Source: https://surrealdb.com/docs/manage/schema-migration/sync

# Sync

Sync pushes your .surql schema files to SurrealDB immediately, keeping the database in desired state. Use it for local development and ephemeral environments.

Sync applies all `.surql` files in `database/schema/` to the connected SurrealDB database in a single pass, bringing the database into alignment with your files. It tracks each file by content hash so that unchanged files are skipped on subsequent runs.

## Basic usage

```bash
surrealkit sync --user root --pass secret
```

SurrealKit connects to the database, reads your schema files, applies any definitions that have changed, and removes definitions for files that have been deleted (pruning).

To apply several independently tracked schemas, or to apply one schema to several databases in a single run, see [Schema modules and targets](/docs/manage/schema-migration/modules-and-targets.md). Sync is the only command that fans out across targets in `1.0.0-beta.1`.

## Watch mode

During active development, pass `--watch` to keep SurrealKit running and automatically re-sync on any file change:

```bash
surrealkit sync --watch --user root --pass secret
```

SurrealKit debounces rapid consecutive saves, so editing multiple files in quick succession triggers a single sync rather than many overlapping ones.

## How it works

When Sync runs:

1. All `.surql` files in `database/schema/` are read and their content hashes computed.
2. Hashes are compared against the `__entity` metadata table stored in the database.
3. New or changed files have their SQL statements applied.
4. Files that existed in a previous sync but are no longer present are pruned: their definitions are removed from the database.
5. The `__entity` table is updated to reflect the new state.

Any definition that exists in the database but not in a file will be removed on the next sync. Pruning emits `REMOVE … IF EXISTS`, so a tracking entry that points at an object which is already gone no longer errors: drift between SurrealKit's metadata and the database self-heals on the next sync.

## Tracked schema objects

Sync manages the full range of SurrealDB schema definitions, tracking each one in the `__entity` table so it can be updated and pruned. This includes `TABLE`, `FIELD`, `INDEX`, `ANALYZER`, `PARAM`, `FUNCTION`, `ACCESS`, and `USER` definitions, as well as `BUCKET`, `SEQUENCE`, `CONFIG`, `MODEL`, and `MODULE`.

## Empty source sets

_(since v1.0)_

Before opening a connection, SurrealKit resolves the schema directory and refuses if it holds no `.surql` files. A wrong working directory or `--folder` value would otherwise look like a schema that had been deleted, and Sync would prune the database to match:

```text title="Error output"
refusing filesystem sync: schema_module=default resolved_schema_dir=./database/schema source_count=0; check --folder / SURREALDB_FOLDER and module selection, or pass --allow-empty-prune if the empty source set is intentional
```

Pass `--allow-empty-prune` when the empty source set is deliberate and you do want every managed object removed. The check also covers `--dry-run` and the first sync of a `--watch` session, because an empty selection cannot otherwise be told apart from a wrong path.

## Pruning on shared databases

Pruning is safe when you are the sole owner of the database. On a shared database (one already containing SurrealKit metadata from another developer or environment), automatic pruning could remove definitions that others have added.

SurrealKit detects this situation and refuses to prune unless you pass an explicit flag:

```bash
surrealkit sync --allow-shared-prune --user root --pass secret
```

Do not use this flag routinely on shared databases. Consider switching to [Rollouts](/docs/manage/schema-migration/rollouts.md) for any database that more than one person or service writes to.

## Running non-DEFINE statements

By default, schema files may only contain `DEFINE` statements. To allow other statements such as `INSERT`, `UPDATE`, or `CREATE` in your schema files, pass `--allow-all-statements`:

```bash
surrealkit sync --allow-all-statements --user root --pass secret
```

This disables catalog entity tracking for the run: SurrealKit can no longer reason about individual objects, so only file-level content hashes are tracked. Pruning of individual definitions is unavailable while this flag is in effect. Prefer keeping data changes in [seed files](/docs/manage/schema-migration/getting-started/new-databases.md) or [rollout steps](/docs/manage/schema-migration/rollouts.md) rather than schema files.

## Metadata tables

SurrealKit creates internal tables in the target database:

| Table | Purpose |
|---|---|
| `__entity` | Stores the content hash and file key for every schema definition SurrealKit manages. Used to detect changes and drive pruning. |
| `__rollout` | Stores rollout state. Created when you first use Rollouts; may be present even if you use Sync only. |
| `__seed` | Stores which [seed files](/docs/manage/schema-migration/seeding.md) have been applied. Created on the first seed run. |

On a project using [schema modules](/docs/manage/schema-migration/modules-and-targets.md), records in `__entity` are partitioned per module, so each module only ever prunes its own objects.

These tables are managed by SurrealKit and should not be modified directly.

## Vite plugin

If you are using Vite, the `vite-plugin-surrealkit` package integrates Sync with the dev server:

```bash
npm install --save-dev vite-plugin-surrealkit
```

```ts
// vite.config.ts
import { defineConfig } from 'vite';
import { surrealkitPlugin } from 'vite-plugin-surrealkit';

export default defineConfig({
    plugins: [
        surrealkitPlugin(),
    ],
});
```

On `vite dev`, the plugin:

1. Runs `surrealkit sync` once at startup.
2. Watches `database/schema/**/*.surql` and `database/modules/*/schema/**/*.surql`.
3. Re-runs sync on any file change, debouncing concurrent runs so they never overlap.

<OptionsTable
	title="Options"
	options={[
		{ "name": "binary", "type": "string", "default": "surrealkit", "description": "SurrealKit binary name, or a path to it." },
		{ "name": "cwd", "type": "string", "description": "Working directory for the SurrealKit process. Defaults to the Vite root." },
		{ "name": "env", "type": "object", "description": "Extra environment variables, merged over the Vite process environment." },
		{ "name": "syncArgs", "type": "string[]", "default": "[]", "description": "Additional arguments appended after `surrealkit sync`." },
		{ "name": "schemas", "type": "string[]", "description": "Schema modules to sync, passed as `--schema`. Defaults to whatever `surrealkit sync` selects." },
		{ "name": "targets", "type": "string[]", "description": "Database targets to sync to, passed as `--target`. Defaults to the primary or only target." },
		{ "name": "all", "type": "boolean", "default": false, "description": "Sync every declared module against every declared target, passing `--all`." },
		{ "name": "schemaGlobs", "type": "string[]", "description": "Globs relative to the Vite root that trigger a sync. Override this for a custom `[schema.<name>] path`." },
		{ "name": "include", "type": "string[]", "default": "['serve']", "description": "Where the plugin runs: `serve`, `build`, or both." },
		{ "name": "runOnStartup", "type": "boolean", "default": true, "description": "Run an initial sync when the dev server starts or a build begins." },
		{ "name": "reloadOnSync", "type": "boolean", "default": false, "description": "Send a full page reload after a successful file-triggered sync in dev." },
		{ "name": "debounceMs", "type": "number", "default": 150, "description": "Debounce window for file changes in dev, in milliseconds." },
		{ "name": "logLevel", "type": "string", "default": "info", "description": "One of `silent`, `error`, `info` and `debug`." },
		{ "name": "failBuildOnError", "type": "boolean", "default": true, "description": "Fail the Vite build when sync exits non-zero." }
	]}
/>

The `schemas`, `targets` and `all` options, and the glob covering `database/modules`, were added in SurrealKit 1.0. Older plugin releases accept neither.

## Reference

| Flag | Default | Description |
|---|---|---|
| `--watch` | off | Re-sync automatically when schema files change |
| `--allow-shared-prune` | off | Permit pruning on a database that already has SurrealKit metadata |
| `--allow-all-statements` | off | Permit non-`DEFINE` statements (e.g. `INSERT`, `UPDATE`, `CREATE`) in schema files; disables catalog entity tracking |
| `--allow-empty-prune` | off | Permit a sync whose schema directory holds no `.surql` files |
| `--dry-run` | off | Print what would be applied without modifying the database |
| `--fail-fast` | off | Stop on the first error rather than continuing |

Sync also accepts the [selection flags](/docs/manage/schema-migration/modules-and-targets.md#selecting-what-to-apply) `--schema`, `--no-deps`, `--target`, `--all` and `--keep-going`.

The schema root defaults to `./database`. Override it with the global `--folder` flag or the `SURREALDB_FOLDER` environment variable. `--folder` had no effect before 1.0, so check any command that passes it when [upgrading](/docs/manage/schema-migration/upgrading.md#check-whether-you-pass-folder).

---

Source: https://surrealdb.com/docs/manage/schema-migration/template-variables

# Template variables

Template variables let you parameterise .surql schema files with environment-specific values, substituted at runtime by SurrealKit.

Template variables are placeholder tokens in your `.surql` schema files that SurrealKit substitutes with actual values at runtime. A single set of schema files can then cover all environments (development, staging, production) with names, prefixes, or other values varying per environment.

## Syntax

Use `${VAR_NAME}` anywhere in a `.surql` file:

```surql
-- database/schema/roles.surql
DEFINE ROLE ${talent_role} PERMISSIONS FULL;

-- database/schema/tables.surql
DEFINE TABLE ${schema_prefix}_orders SCHEMAFULL;
DEFINE FIELD owner ON ${schema_prefix}_orders TYPE record<user>;
```

Variable names are case-insensitive: `${FOO}`, `${foo}`, and `${Foo}` all match the key `FOO`.

If a variable is referenced in a file but has no value configured, SurrealKit exits with an error and a clear message indicating which variable is missing.

## Resolution order

SurrealKit resolves variable values in the following order (first match wins):

1. **CLI flag:** `--var KEY=VALUE` (repeatable)
2. **Environment variable:** `SURREALKIT_VAR_<KEY>` (case-insensitive key matching)
3. **`surrealkit.toml` `[variables]` section**

### CLI flags

```bash
surrealkit sync --var schema_prefix=acme --var talent_role=talent_rw
surrealkit rollout start my_rollout --var schema_prefix=acme
```

### Environment variables

**Bash**

```bash
export SURREALKIT_VAR_SCHEMA_PREFIX=acme
export SURREALKIT_VAR_TALENT_ROLE=talent_rw
surrealkit sync --user root --pass secret
```

**PowerShell**

```powershell
$env:SURREALKIT_VAR_SCHEMA_PREFIX = "acme"
$env:SURREALKIT_VAR_TALENT_ROLE = "talent_rw"
surrealkit sync --user root --pass secret
```

### `surrealkit.toml`

```toml
[variables]
schema_prefix = "myapp"
talent_role = "talent_rw"
environment = "development"
```

Place `surrealkit.toml` in the root of your project. It is read automatically. The same file holds the [`[typegen]` section](/docs/manage/schema-migration/typegen.md) and, since 1.0, [schema modules and targets](/docs/manage/schema-migration/modules-and-targets.md). See [Configuration](/docs/manage/schema-migration/configuration.md) for the full key reference.

> [!WARNING]
> `[variables]` is read from the working directory only, while `[schema.*]` and `[target.*]` are found by walking up from it. Running a command from a subdirectory of your project therefore applies your modules but leaves every template variable undefined. Run project commands from the directory that holds `surrealkit.toml`.

## Escaping

To emit a literal `${...}` string in generated SQL (for example, inside a SurrealQL string value), double the dollar sign:

```surql
SET note = 'pass $${MY_VAR} literally';
```

SurrealKit outputs this as `pass ${MY_VAR} literally` without substitution.

## Which commands apply substitution

Template Variables are substituted when applying schema to a database. They are not substituted in commands that only read or plan:

| Command | Substitution applied |
|---|---|
| `sync` | Yes |
| `seed` | Yes |
| `rollout start` | Yes |
| `rollout complete` | Yes |
| `rollout rollback` | Yes |
| `rollout plan` | No |
| `rollout baseline` | No |
| `rollout status` | No |
| `rollout lint` | No |

## Known limitations

**Hash-based re-sync:** SurrealKit tracks files by content hash. Changing a variable value without editing the file will not trigger a re-sync. Touch the file to force re-application:

```bash
touch database/schema/tables.surql
surrealkit sync --user root --pass secret
```

**Working directory:** `[variables]` is read from the current directory, not found by walking up the tree. See the warning above.

**Watch mode:** Variables in `surrealkit.toml` are resolved once at startup. If you edit `surrealkit.toml` while `--watch` is running, restart SurrealKit for the new values to take effect. Variables passed via `--var` or environment variables are re-read on each sync cycle.

**Catalog snapshots:** Entity names containing `${VAR}` appear literally (without substitution) in `catalog_snapshot.json`. This is cosmetic and does not affect functionality.

**String literals:** Substitution is purely textual. `${VAR}` inside a SurrealQL string is replaced with the variable's value, which may or may not be the intended behaviour. Test carefully when embedding variables inside string literals.

---

Source: https://surrealdb.com/docs/manage/schema-migration/templates

# Project templates

surrealkit init scaffolds a project from a template with selectable features. Use the bundled template, pick only the features you need, or supply your own.

`surrealkit init` scaffolds a new project from a template and lets you choose which optional features to include. It always writes the base project layout first, then copies in the schema, seed, and test files for the features you select.

```bash
surrealkit init
```

In a terminal this shows a checklist of the template's features. Pick the ones you want and SurrealKit writes their files into `database/`.

## Base layout

Every `init` creates the base project regardless of which features you choose:

```text
database/
├── schema/        # .surql schema definition files
├── rollouts/      # rollout manifests (generated)
├── snapshots/     # schema and catalog snapshots
├── seed/          # optional seed data
├── tests/         # test suites and config
└── setup.surql    # runs before sync
surrealkit.toml    # project configuration
```

`surrealkit.toml` holds template variables, type generation settings, and any [schema modules and targets](/docs/manage/schema-migration/modules-and-targets.md) the project declares. See [Configuration](/docs/manage/schema-migration/configuration.md) for the full key reference. `init` never overwrites an existing one.

## Choosing features without a prompt

When there is no terminal (such as CI), or when you pass any of the flags below, `init` runs without prompting:

| Flag | Behaviour |
|---|---|
| `--feature <id>` | Enable a feature by id. Repeatable, and pulls in what it requires. |
| `-y`, `--yes` | Take the template's default features. |
| `--minimal` | Scaffold the base project only, with no features. |
| `--force` | Overwrite files that already exist. The default is to skip them. |

```bash
surrealkit init --feature organizations --feature teams
surrealkit init -y
surrealkit init --minimal
```

A feature can depend on other features. Selecting one adds what it requires, and `init` prints what it added.

## Using your own template

Point `--from` at a local path or a git repository instead of the bundled template, or pick a bundled template by name with `--template`:

```bash
surrealkit init --from ./path/to/template
surrealkit init --from https://github.com/your-org/your-template.git
surrealkit init --from https://github.com/your-org/your-template.git#v1.0.0
surrealkit init --template default
```

Git sources are cloned with `git clone --depth 1`, so `git` must be on your `PATH`. Pin a branch, tag, or commit with `#rev`, and target a subdirectory with `#rev:subdir`.

## Template layout

A template is a directory with a `template.toml` manifest plus the files each feature contributes:

```toml
schema_version = 1
name = "default"
display_name = "My starter"
description = "Shown above the feature checklist"

[[features]]
id = "organizations"
name = "Organizations"
description = "Shown next to the feature in the checklist"
default = false
schema   = ["schema/organization/organization.surql"]
seed     = ["seed/organization_permissions.surql"]
suites   = ["tests/suites/organization.toml"]
fixtures = ["tests/fixtures/organization_seed.surql"]

[[features]]
id = "teams"
name = "Teams"
requires = ["organizations"]
schema = ["schema/team/team.surql"]
```

Each feature lists the files it adds, grouped by where they land:

- `schema` files are copied into `database/schema/`
- `seed` files into `database/seed/`
- `suites` files into `database/tests/suites/`
- `fixtures` files into `database/tests/fixtures/`

Set `default = true` to pre-check a feature in the prompt and include it with `-y`. Use `requires` to declare dependencies on other features.

## Bundled template

The bundled `default` template provides an organisation and access-control model with four opt-in features:

- **Organisations:** organisations, roles that bundle permissions, a per-app permission catalogue, employees, and invitations.
- **Teams:** teams within an organisation, with per-member roles.
- **Organisation units:** a department and region hierarchy with unit-scoped permissions.
- **Subsidiaries and delegation:** parent and child organisations with cross-org delegated permissions.

Teams, units, and subsidiaries each require the organisations feature.

## Next steps

- [New databases](/docs/manage/schema-migration/getting-started/new-databases.md): start a fresh project with SurrealKit
- [Sync](/docs/manage/schema-migration/sync.md): push your scaffolded schema to a database
- [Testing](/docs/manage/schema-migration/testing.md): run the test suites the template scaffolded
</content>

---

Source: https://surrealdb.com/docs/manage/schema-migration/testing

# Testing

SurrealKit includes a built-in testing framework for validating schema correctness, permissions, and API behaviour across multiple actor types.

SurrealKit includes a testing framework that lets you write declarative test suites for your SurrealDB schema. Tests run against an isolated ephemeral database per suite, so they are safe to run in any environment without affecting persistent data.

## Running tests

```bash
surrealkit test
```

SurrealKit reads all suite files from `database/tests/suites/*.toml`, runs them in parallel (configurable), and exits non-zero if any case fails.

## Project structure

```text
database/tests/
├── config.toml          # global defaults
└── suites/
    ├── security.toml
    └── api.toml
```

### Global config

`database/tests/config.toml` sets defaults shared across all suites:

```toml
[defaults]
timeout_ms = 10000
base_url = "http://localhost:8000"

[actors.root]
kind = "root"
```

Suite files are parsed strictly: an unknown key in a case, an actor, or the global config is an error rather than being ignored, so a typo fails the run instead of quietly disabling a check.

> [!NOTE]
> `surrealkit test` applies a filesystem sync before running, so the [empty source set check](/docs/manage/schema-migration/sync.md#empty-source-sets) applies to it. A suite whose fixtures own the complete schema, with nothing in `database/schema`, needs `--no-sync`.

## Test types

SurrealKit supports five test types, specified via the `kind` field on each test case.

### `sql_expect`

Runs a SurrealQL statement and asserts whether it succeeds or fails:

```toml
[[cases]]
name = "guest_cannot_create_order"
kind = "sql_expect"
actor = "guest"
sql = "CREATE order CONTENT { total: 10 };"
allow = false
error_contains = "permission"
```

Optional `assertions` check the returned data:

```toml
[[cases]]
name = "user_sees_own_profile"
kind = "sql_expect"
actor = "user_alice"
sql = "SELECT * FROM user WHERE id = $auth.id;"
allow = true

[[cases.assertions]]
path = "0.id"
equals_auth = "$auth.id"
```

#### Missing paths fail

_(since v1.0)_

An assertion whose `path` is not present in the result fails with `path '<path>' not found`, rather than the comparison being skipped. The same applies to a `header_assertions` entry naming a header the response never sent. This catches typos and queries that matched zero records.

To assert that a value is genuinely absent, state it explicitly. `exists = false` is the only specification that passes on a missing path:

```toml
[[cases.assertions]]
path = "0.secret"
exists = false
```

Paths are dot-separated. A segment that parses as a number indexes an array, and anything else keys an object, so `0.owner.name` reaches the owner name of the first record. There is no bracket syntax, no `$.` prefix, and no escaping, so a field whose name contains a dot cannot be addressed.

> [!IMPORTANT]
> Assertions that go red after upgrading from 0.7 were most likely never being evaluated, because a missing path used to pass. Check the shape of the actual result before changing the assertion. See [Upgrading](/docs/manage/schema-migration/upgrading.md#if-you-use-the-tester).

### `permissions_matrix`

Validates that a single actor has the expected create / select / update / delete permissions on a table or record:

```toml
[[cases]]
name = "reader_cannot_modify_orders"
kind = "permissions_matrix"
actor = "reader"
table = "order"
record_id = "order:test"

[[cases.rules]]
action = "select"
allow = true

[[cases.rules]]
action = "update"
allow = false
error_contains = "permission"
```

> [!NOTE]
> Permission probing was reworked in 1.0 to handle row-level security, where a filtered result cannot be told apart from a rejected one. Create probes recreate a record with the same fields, update probes write a marker field first, records created during the matrix are cleaned up afterwards, and a permission that skips is now reported differently from one that throws. An existing matrix can report a different verdict as a result.

### `schema_metadata`

Asserts structural facts about the schema: that a field exists with a given type, that an index is defined, and so on.

### `schema_behavior`

Tests computed fields, functions, and record relations by asserting on the values returned after specific operations.

### `api_request`

Tests HTTP API endpoints, useful when your SurrealDB instance exposes a custom API layer:

```toml
[[cases]]
name = "orders_endpoint_returns_200"
kind = "api_request"
actor = "root"
method = "GET"
path = "/api/orders"
expected_status = 200

[[cases.body_assertions]]
path = "0.id"
exists = true
```

## Actors

Each test case runs as a named actor with a specific authentication method. Actors are defined in `config.toml` or at the suite level.

| Actor kind | When to use |
|---|---|
| `root` | Full root-level access |
| `database` | Database-level user credentials |
| `record` | Record access via signup / signin |
| `token` | JWT token from an environment variable |
| `headers` | Custom HTTP headers (e.g. tenant ID) |

```toml
[actors.user_alice]
kind = "record"
access = "app_access"

[actors.user_alice.signin_params]
email = "alice@example.com"
password = "secret"

[actors.tenant_a]
kind = "headers"
headers = { "x-tenant-id" = "tenant_a" }
```

## Filtering

| Flag | Description |
|---|---|
| `--suite <glob>` | Run only suites whose name matches the glob |
| `--case <glob>` | Run only cases whose name matches the glob |
| `--tag <tag>` | Run only cases tagged with the given tag (repeatable) |
| `--fail-fast` | Stop on the first failure |
| `--parallel <N>` | Number of parallel execution threads |

## Debugging

| Flag | Description |
|---|---|
| `--keep-db` | Preserve the ephemeral database after the run for manual inspection |
| `--no-sync` | Skip the schema sync phase before running tests |
| `--no-seed` | Skip the seeding phase before running tests |
| `--json-out <path>` | Write a machine-readable JSON report to the specified file |

## Next steps

- [CI / CD](/docs/manage/schema-migration/testing/ci-cd.md): integrate tests into automated pipelines with GitHub Actions and Docker Compose

---

Source: https://surrealdb.com/docs/manage/schema-migration/testing/ci-cd

# CI / CD

Run SurrealKit tests in CI pipelines using the official GitHub Action, with JSON output for test reporting and Docker Compose for full end-to-end environments.

Tests exit non-zero on any failure and can write machine-readable JSON output. An official GitHub Action handles installation and execution.

## Test output

The human-readable report prints a summary first and expands only the suites and cases that failed, so a passing run stays short and a failing one leads with what broke:

```text title="Output"
Test run: FAIL (1000ms)

Summary
- suites: 1 total, 1 failed
- cases : 4 total, 3 passed, 1 failed

Suite: orders
- file     : orders.toml
- namespace: main
- database : main
- result   : 3 passed, 1 failed

Case 1: reader_cannot_modify_orders

- update_allowed: path '0.id' not found
```

## JSON output

Pass `--json-out` to write a structured report alongside the human-readable output:

```bash
surrealkit test --fail-fast --json-out results.json
```

The JSON file can be consumed by CI reporting tools or used for dashboards and notifications.

## GitHub Actions

The [`surrealkit-action`](https://github.com/surrealdb/surrealkit-action) installs the SurrealKit CLI and runs any SurrealKit command. It supports Linux x64, macOS ARM64, and Windows x64.

### Basic test workflow

```yaml
name: tests
on: [push, pull_request]

jobs:
  surrealkit-test:
    runs-on: ubuntu-latest
    services:
      surrealdb:
        image: surrealdb/surrealdb:latest
        ports:
          - '8000:8000'
        options: >-
          --health-cmd "/surreal is-ready"
          --health-interval 2s
          --health-timeout 5s
          --health-retries 10
        env:
          SURREAL_USER: root
          SURREAL_PASS: secret
    steps:
      - uses: actions/checkout@v4
      - uses: surrealdb/surrealkit-action@v1
        with:
          command: test
          host: http://localhost:8000
          user: root
          pass: secret
          args: --fail-fast --json-out results.json
```

### Running a rollout in CI

You can also use the action to apply a rollout as part of a deployment workflow:

```yaml
- uses: surrealdb/surrealkit-action@v1
  with:
    command: rollout start
    args: ${{ env.ROLLOUT_ID }}
    host: ${{ secrets.SURREALDB_HOST }}
    user: ${{ secrets.SURREALDB_USER }}
    pass: ${{ secrets.SURREALDB_PASSWORD }}
```

## Docker Compose

For end-to-end testing that mirrors a production-like environment, run SurrealKit alongside SurrealDB in Docker Compose:

```yaml
services:
  surrealdb:
    image: surrealdb/surrealdb:latest
    command: start --user root --pass secret
    healthcheck:
      test: ["CMD", "/surreal", "is-ready"]
      interval: 1s
      timeout: 5s
      retries: 30

  surrealkit:
    image: ghcr.io/surrealdb/surrealkit:1.0.0-beta.1
    depends_on:
      surrealdb:
        condition: service_healthy
    volumes:
      - ./database:/database:ro
    command:
      - --host=http://surrealdb:8000
      - --ns=main
      - --db=main
      - --user=root
      - --pass=secret
      - test
      - --fail-fast
      - --json-out=/database/results.json
```

Run with:

```bash
docker compose run --rm surrealkit
```

The `database/` directory is mounted read-only; the container reads your schema files and test suites but cannot write back to them. The JSON report is written to `database/results.json` on the host.

A project using [`surrealkit.toml`](/docs/manage/schema-migration/configuration.md) needs that file inside the container too. The image sets no working directory, so mount it at `/home/nonroot/surrealkit.toml` or give the service a `working_dir`.

## Seeding before tests

SurrealKit runs a setup phase before executing tests. By default this includes syncing your schema and running any seed files. To skip individual phases:

```bash
surrealkit test --no-sync   # skip schema sync, use whatever is already in the DB
surrealkit test --no-seed   # skip seed data
```

In most CI environments you want both phases to run so each test job starts from a clean, consistent state. Use `--no-sync` when the suite's fixtures own the complete schema, because the sync phase otherwise refuses an empty `database/schema`.

Seed files are tracked, so a job that reuses a database applies only what changed. Pass `--force` to `surrealkit seed` when a job needs every seed file re-applied. See [Seeding](/docs/manage/schema-migration/seeding.md).

---

Source: https://surrealdb.com/docs/manage/schema-migration/typegen

# Type generation

surrealkit typegen introspects a live database and emits a structured JSON schema document, with optional TypeScript types for the SurrealDB JavaScript SDK.

`surrealkit typegen` introspects a live database and emits a structured description of its schema. JSON is the primary output; TypeScript interfaces for the [SurrealDB JavaScript SDK](/docs/reference/javascript.md) can be generated alongside it.

```bash
surrealkit typegen --user root --pass secret
```

By default this writes a JSON document to `database/types/schema.json`. SurrealKit's internal bookkeeping tables (`__entity`, `__rollout`, `__seed`) are excluded from the output.

## Command flags

| Flag | Default | Description |
|---|---|---|
| `--out <path>` | `{folder}/types/schema.json` | Write the JSON to a specific path |
| `--stdout` | off | Print the JSON to stdout instead of writing a file |
| `--compact` | off | Emit compact, single-line JSON instead of pretty-printed |

```bash
# Pretty-printed JSON to the default location
surrealkit typegen --user root --pass secret

# Pipe compact JSON into another tool
surrealkit typegen --stdout --compact --user root --pass secret
```

The JSON document includes the namespace, database, generation timestamp, and a typed description of every table, field, and function in the schema.

## TypeScript output

TypeScript generation is opt-in through the `[typegen]` section of [`surrealkit.toml`](/docs/manage/schema-migration/configuration.md). Set `typescript` to the directory where the generated `index.ts` should be written:

```toml
[typegen]
typescript = "./src/types"
format = "biome check --write"
```

| Key | Description |
|---|---|
| `typescript` | Directory for the generated `index.ts`. Setting it enables TypeScript output for `typegen` and `sync --watch`. |
| `format` | Optional formatter command run on the generated file after writing (for example `biome check --write`, `prettier --write`, or `eslint --fix`). |

With `typescript` configured, `surrealkit typegen` writes both the JSON document and an `index.ts`:

```ts
// Generated by SurrealKit - do not edit.
// Run `surrealkit typegen` to regenerate.

import type { RecordId } from 'surrealdb';

export interface User {
  id: RecordId<'user'>;
  name: string;
  email: string;
}
```

The emitter targets the SurrealDB JavaScript SDK (v2): one interface per table, every record carrying a typed `id: RecordId<'table'>`, with schema field types mapped to the SDK's wrapper types.

### Formatter

When `format` is set, SurrealKit appends the generated file path to the command and runs it, so the output matches your project's house style. The command inherits the working directory so the formatter discovers your project's own config. Formatter failures (a missing binary or a non-zero exit) are reported as warnings and never fail `typegen` or `sync --watch`.

## Regenerating on sync

When `[typegen] typescript` is configured, `surrealkit sync --watch` regenerates the TypeScript types whenever the schema changes, so your types stay current with the schema during local development:

```bash
surrealkit sync --watch --user root --pass secret
```

Regeneration is gated on actual schema changes (or a missing output file), so idle watch ticks do not re-introspect the database.

## Type generation and targets

`typegen` introspects the database given by `--host`, `--ns` and `--db`. It does not resolve [targets](/docs/manage/schema-migration/modules-and-targets.md), so in `1.0.0-beta.1` neither `--target` nor `[target.<name>] primary = true` changes which database it reads. Pass the connection explicitly to introspect a particular one:

```bash
surrealkit typegen --host wss://production-6xk2.aws-euw1.surreal.cloud --ns main --db main --user root --pass secret
```

> [!NOTE]
> There is one `[typegen] typescript` directory for the whole project, so `surrealkit sync --all` regenerates the same `index.ts` once per module and target pair, and the last pair to run leaves its output in place. Generate types from a single target rather than from a fan-out.

## Library API

Type generation is also available from the [`surrealkit::typegen`](/docs/manage/schema-migration/library.md) module when embedding SurrealKit in Rust:

```rust
use surrealkit::typegen::{generate, render_typescript, write_typescript};
use surrealkit::Surreal;
use surrealkit::engine::any::Any;

async fn run(db: &Surreal<Any>) -> anyhow::Result<()> {
    // Introspect the database into a structured document (no filesystem IO).
    let doc = generate(db).await?;

    // Render TypeScript as a string …
    let ts = render_typescript(&doc)?;
    println!("{ts}");

    // … or write it to `<dir>/index.ts`.
    write_typescript(&doc, std::path::Path::new("src/types"))?;
    Ok(())
}
```

`generate` returns a `SchemaTypes` document that the JSON and TypeScript emitters share, so a single introspection can produce both formats. `write_typescript_formatted` writes the file and runs an optional formatter command in the same call.

## Next steps

- [Sync](/docs/manage/schema-migration/sync.md): keep the database aligned with your schema files
- [Using SurrealKit as a library](/docs/manage/schema-migration/library.md): drive SurrealKit from Rust
</content>

---

Source: https://surrealdb.com/docs/manage/schema-migration/upgrading

# Upgrading to SurrealKit 1.0

What changes when moving a SurrealKit project from 0.7 to 1.0: the removed seed.surql fallback, rejected DATABASE_* variables, a --folder flag that now works, stricter test assertions, and library API changes.

Most of a 0.7 project needs no change. Every 0.7 command still works, and running `surrealkit sync` on an existing project after upgrading re-applies nothing and prunes nothing. This page covers the parts that do need attention, and the new features you can adopt afterwards.

Two of the changes are silent, in the sense that a project keeps running but does something different. Read [Rename the `DATABASE_*` variables](#rename-the-database-variables) and [Check whether you pass `--folder`](#check-whether-you-pass-folder) even if nothing appears broken.

## Install the beta

SurrealKit 1.0 is a prerelease, so it has to be requested by version:

```bash
cargo binstall surrealkit --version 1.0.0-beta.1
```

## Move `database/seed.surql`

The single-file seed fallback was deprecated in 0.6, where it printed a warning on every run, and is now removed. Move it into the [seed directory](/docs/manage/schema-migration/seeding.md):

```bash
mkdir -p database/seed
git mv database/seed.surql database/seed/000_init.surql
```

Seed tracking is keyed by file path, so the moved file counts as a new one and runs again on the next `surrealkit seed`. If the seed is not idempotent, re-key it first rather than letting it re-run:

```surql
UPDATE __seed SET key = 'database/seed/000_init.surql'
  WHERE key = 'database/seed.surql';
```

## Rename the `DATABASE_*` variables {#rename-the-database-variables}

`DATABASE_HOST`, `DATABASE_NAME`, `DATABASE_NAMESPACE`, `DATABASE_USER`, `DATABASE_PASSWORD` and `DATABASE_AUTH_LEVEL` are no longer accepted. Rename each to its `SURREALDB_*` equivalent.

SurrealKit fails when it finds one set without its replacement, rather than ignoring it:

```text title="Error output"
the DATABASE_* environment variables were removed in SurrealKit 1.0, but these are still set with no SURREALDB_* replacement:
  DATABASE_HOST -> SURREALDB_HOST
  DATABASE_NAME -> SURREALDB_NAME
Rename them. They are rejected rather than ignored because ignoring them would silently fall back to the defaults and connect to the wrong database.
```

Failing is deliberate. An ignored `DATABASE_HOST` would fall back to the default endpoint, so a deployment would connect to the wrong database instead of stopping. Setting both spellings is accepted, and `SURREALDB_*` wins.

## Check whether you pass `--folder` {#check-whether-you-pass-folder}

`--folder` never took effect before 1.0: it was parsed and then discarded, so SurrealKit always used `SURREALDB_FOLDER` or `./database`. It works now.

A project that passed `--folder ./db` while SurrealKit was really syncing `./database` will now sync `./db`. If that directory holds no schema files, SurrealKit refuses before opening a connection rather than pruning the database to match:

```text title="Error output"
refusing filesystem sync: schema_module=default resolved_schema_dir=./db/schema source_count=0; check --folder / SURREALDB_FOLDER and module selection, or pass --allow-empty-prune if the empty source set is intentional
```

Point `--folder` at the right directory, drop the flag, or pass `--allow-empty-prune` when the empty source set is intentional.

## If you use the tester

### Missing paths and headers now fail

Through 0.7, an assertion on a JSON path or a response header that did not exist passed silently. The check compared "not found" against an unset `exists` field and matched, so the `equals`, `contains` or `regex` comparison was never reached:

```toml
[[cases.assertions]]
path = "0.owner"     # a typo, or a query that matched zero rows
equals = "user:alice"
```

On 0.7 that reported a pass. On 1.0 it fails with `path '0.owner' not found`. The same applies to `header_assertions` against a header the response never sent.

> [!IMPORTANT]
> If assertions go red on upgrade, they were most likely never being evaluated. Check the actual shape of the result before concluding that SurrealKit regressed.

To assert that something is genuinely absent, say so explicitly:

```toml
[[cases.assertions]]
path = "0.secret"
exists = false
```

`exists = false` is the only specification that passes on a missing path or header. There is no suite-level opt-out, and unknown keys in an assertion are rejected when the suite is parsed.

### Tests sync before they run

`surrealkit test` now performs a filesystem sync first, so the empty source set check applies to it as well. A suite whose fixtures own the complete schema, with nothing in `database/schema`, needs `--no-sync`.

### Permission matrices probe differently

`permissions_matrix` cases were reworked to handle row-level security, where a filtered result is indistinguishable from a rejected one. Create probes now recreate a record with the same fields, update probes write a marker field first, records created during the matrix are cleaned up, and a permission that skips is reported differently from one that throws. An existing matrix can report a different verdict on 1.0 as a result. Re-read any that change before adjusting them.

## If you use the Rust library

### `Rollout` no longer writes to disk

This is the one silent behaviour change for library users. `Rollout::start`, `Rollout::complete` and `Rollout::rollback` used to default to `./database` and create `./database/setup.surql` in the caller's working directory. They are now purely in-database:

```rust
// 1.0: writes nothing to disk
Rollout::new(spec, files).start(&db).await?;

// 1.0: opt back into the filesystem workflow
Rollout::new(spec, files).folder("database").start(&db).await?;
```

### Renamed and removed items

| 0.7 | 1.0 |
| --- | --- |
| `constants::deprecated_seed_surql_path` | Removed |
| `tester::build_filter_input` | `FilterInput::from_opts` |
| `rollout::run_baseline(db, folder)` | `run_baseline(db, folder, &module)` |
| `rollout::run_abandon_rollout(db, id)` | `run_abandon_rollout(db, &module, id)` |
| `SyncOpts { .. }` | Gains `module` and `allow_empty_prune` |

### The library is silent by default

Progress output moved to the `log` facade, so a library consumer sees nothing unless a logger is installed. Set `RUST_LOG=surrealkit=info` and initialise a logger to get the previous output back. The CLI is unaffected.

### A new `cli` cargo feature

`clap` and its supporting crates are now optional, behind a `cli` feature that is on by default. A library-only consumer can drop them:

```toml
[dependencies]
surrealkit = { version = "1.0.0-beta.1", default-features = false, features = ["kv-mem"] }
```

> [!WARNING]
> The `surrealkit` binary declares `cli` as a required feature. Installing with `--no-default-features` and without `cli` builds no binary at all.

## If you use the Vite plugin

- The package now declares `Apache-2.0`. It previously declared `Unlicense`, which did not match the repository.
- `@biomejs/biome` moved to `devDependencies`. It was never imported at runtime, and as a dependency it forced consumers to download the Biome binary. Your lockfile will change.
- New `schemas`, `targets` and `all` options map to `--schema`, `--target` and `--all`. The default watch globs now cover `database/modules/*/schema` as well as `database/schema`.

## Adopting the new features

Nothing below is required, and each is additive.

[Schema modules](/docs/manage/schema-migration/modules-and-targets.md) leave your existing schema in the default module with its metadata untouched, and new modules live alongside it:

```toml title="surrealkit.toml"
[schema.billing]
depends_on = ["core"]
```

[Targets](/docs/manage/schema-migration/modules-and-targets.md) name the databases to apply modules to, taking their passwords from the environment:

```toml title="surrealkit.toml"
[target.acme]
ns = "acme"
db = "prod"
pass_env = "ACME_DB_PASSWORD"
```

Both live in [`surrealkit.toml`](/docs/manage/schema-migration/configuration.md), which is now discovered by walking up from the working directory rather than being read from that directory alone.

## Next steps

- [Configuration](/docs/manage/schema-migration/configuration.md): the full `surrealkit.toml` key reference
- [Schema modules and targets](/docs/manage/schema-migration/modules-and-targets.md): several schemas across several databases
- [Seeding](/docs/manage/schema-migration/seeding.md): the seed directory and the tracking table

---

Source: https://surrealdb.com/docs/manage/self-hosted

# Self-hosted

SurrealDB deployment models on your own infrastructure. Containers, configuration, backups, monitoring, and upgrades.

Running SurrealDB yourself is quick to start and gives you full control over storage, configuration, and placement. Everything needed to start a server and to import and export data is in the [command-line tool](/docs/reference/cli/db.md), packaged and distributed as a single executable that can be downloaded, installed, or run from within Docker.

For initial installation, see the [installation guide](/docs/running/installation.md). If you would rather not operate the infrastructure, see [Instances](/docs/manage/instances.md) for the managed option.

## Deployment guides

| Guide | Typical use |
| --- | --- |
| [Deployment models](/docs/manage/self-hosted/deployment-models.md) | Choosing between single-node, multi-node, embedded, and managed |
| [Docker](/docs/running/docker.md) | Quickest path; RocksDB with a volume mount |
| [Kubernetes](/docs/manage/self-hosted/kubernetes.md) | Single SurrealDB pod with RocksDB on a persistent volume |
| [Managed Kubernetes](/docs/manage/self-hosted/managed-kubernetes.md) | Paths on Amazon EKS, Google GKE, and Azure AKS |

Most self-hosted workloads use **RocksDB** on disk - one SurrealDB process per database file.

## Operating a self-hosted instance

- [Configuration](/docs/manage/self-hosted/configuration.md) - server startup options, environment variables, and storage engine selection.
- [Backups and recovery](/docs/manage/self-hosted/backups-and-recovery.md) - export and import commands, backup strategies, and disaster recovery.
- [Monitoring and observability](/docs/manage/self-hosted/monitoring-and-observability.md) - health endpoints, metrics, and tracing integration.
- [Upgrades and patching](/docs/manage/self-hosted/upgrades-and-patching.md) - version upgrades, major-version data migration, and compatibility notes.

Once an instance is running, the [Observability](/docs/manage/observability.md) section covers the built-in metrics, audit logs and slow-query logs you can scrape over Prometheus or push over OTLP for production monitoring.

## Enterprise Edition

Self-hosted clusters can run [Enterprise Edition](/docs/manage/enterprise/product/database-enterprise.md), which adds distributed live queries, audit logging, FIPS-validated cryptography and support tiers on top of the same server.

---

Source: https://surrealdb.com/docs/manage/self-hosted/backups-and-recovery

# Backups & recovery

Create and restore SurrealQL backups with the CLI, plan backup strategies, and prepare for disaster recovery.

The CLI provides logical backups of a self-hosted server as SurrealQL using [`surreal db export`](/docs/reference/cli/db/commands/export.md), which dumps namespaces, databases, schemas, and data in a form you can version, diff, and store outside the database.

Schedule exports when traffic is lower if large datasets make exports lengthy, and ensure the export process has sufficient disk space and time to finish without overlapping the next run.

To **restore**, use [`surreal db import`](/docs/reference/cli/db/commands/import.md) against a running instance, pointing at the correct endpoint, credentials, and target namespace or database.

After import, run sanity checks: record counts, critical queries, and application smoke tests before switching production traffic. If you import into a shared cluster, coordinate with application owners so schema or data changes do not race with live writers.

**Backup strategies** usually combine scheduled exports with storage-engine or volume **snapshots** where available. Exports are portable and engine-agnostic; snapshots capture disk state quickly but depend on filesystem layout and binary compatibility. Use both when compliance, RPO, or RTO targets demand redundancy across failure modes.

**Point-in-time recovery** is not implicit in a single export: each file reflects one moment. Narrow your recovery window by exporting more often, maintaining replicas, or using journaled storage upstream of SurrealDB. Document which artefact (export time, snapshot ID, replica lag) corresponds to a given recovery objective.

For **disaster recovery**, keep copies off-site and logically separated from production credentials, encrypt dumps at rest, and test full restores onto clean hardware or namespaces regularly. Maintain runbooks with exact commands, verification queries, and escalation paths so restores succeed when primary systems are unavailable.

Validate backup **integrity** periodically: restore to a non-production instance and run application-level checks. Corruption or partial writes are easier to catch in rehearsal than during an incident.

Align retention with policy: keep enough history for audits and incident investigation, but prune or tier old exports so storage costs stay predictable. Where legal holds apply, flag those backups and exclude them from automated deletion.

## What a dump does not restore

An export holds the current state of each record and the definitions of the database, with two exceptions to plan for.

**Access signing keys.** An export writes each [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md) statement with `'[REDACTED]'` in place of its key, so no dump holds a key. From SurrealDB 3.3.1, importing one of these statements fails with an error naming the access, the access is not created, and the rest of the import carries on. After a restore, define each such access again with its real key, kept somewhere other than the database. An access that verifies tokens through a JWKS URL holds no key and restores normally.

> [!WARNING]
> Before SurrealDB 3.3.1, importing a dump created each access with the literal string `[REDACTED]` as its key. Anyone who knows that string can sign tokens for such an access. If a database was restored from a dump on an earlier version, define its accesses again with new keys.

**Record history.** An export holds no earlier versions of a record, even on a datastore that keeps them. From 3.3.1, `surreal db export --versions` fails rather than writing an ordinary dump.

Two other details affect who can restore a dump and how large its transactions are:

* A database with its own `COMMENT`, `CHANGEFEED` or `STRICT` clause is exported with a `DEFINE DATABASE OVERWRITE` statement that sets those clauses on the target database. That statement needs a namespace or root user, so import such a dump as one of those rather than as a database user.
* Where [`SURREAL_TRANSACTION_MAX_WRITE_KEYS`](/docs/reference/cli/db/environment-variables.md) is set, a dump from a version before 3.3.1 can exceed it on import, because its `INSERT` statements were sized without counting the keys a `REFERENCE` field writes. Take a new export after upgrading, or raise the limit while restoring the older dump.

## Backing up an embedded database

An application that embeds SurrealDB, rather than connecting to a server, makes the same export from its own code. The SDKs have export and import methods that write and read the same SurrealQL file as the CLI, such as `.export()` and `.import()` in the [Rust SDK](/docs/reference/rust/methods/export.md):

```rust
// Write a backup of the current namespace and database
db.export("backup.surql").await?;

// Restore it into another datastore
restored.import("backup.surql").await?;
```

To copy the files of a RocksDB or SurrealKV datastore directly instead, copy the directory only while no process has the datastore open. A copy taken while the application is writing can be missing writes that were still in memory.

---

Source: https://surrealdb.com/docs/manage/self-hosted/configuration

# Configuration

Configure a self-hosted SurrealDB server: CLI options, environment variables, storage, networking, auth, and TLS.

Most server behaviour is fixed at process start. The [`surreal db start`](/docs/reference/cli/db/commands/start.md) command is the primary entry point: pass flags for bind addresses, storage paths, authentication, TLS material, logging, and feature toggles.

Review the command reference whenever you upgrade, as defaults and supported flags can change between releases. Keep a short changelog of server flags per environment so rollbacks and comparisons stay straightforward.

Equivalent settings are often available as [environment variables](/docs/reference/cli/db/environment-variables.md), which suits containers, systemd units, and secret managers. Prefer one coherent source of truth (file, env, or flags) so configuration does not diverge silently across hosts.

**Running in the background.** `surreal db start` runs in the foreground and has no daemon mode of its own, so let the operating system's service manager start it, restart it after a failure, and stop it cleanly. In a container, `docker run -d` runs the same server in the background.

**Linux**

On Linux, let systemd run the server. Save the following as `/etc/systemd/system/surrealdb.service`, then run `sudo systemctl enable --now surrealdb`:

```ini title="/etc/systemd/system/surrealdb.service"
[Unit]
Description=SurrealDB server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=surrealdb
Group=surrealdb
EnvironmentFile=/etc/surrealdb/surrealdb.env
ExecStart=/usr/local/bin/surreal db start
Restart=on-failure
LimitNOFILE=infinity

[Install]
WantedBy=multi-user.target
```

The unit reads its settings from the environment file, which keeps the root password out of the unit file and the process list. The variable names are those on the [environment variables](/docs/reference/cli/db/environment-variables.md) page:

```ini title="/etc/surrealdb/surrealdb.env"
SURREAL_USER=root
SURREAL_PASS=secret
SURREAL_BIND=127.0.0.1:8000
SURREAL_PATH=rocksdb:///var/lib/surrealdb/data
```

Restrict the file with `chmod 600`, and make sure the `surrealdb` user owns the data directory. Use `journalctl -u surrealdb -f` to follow the server log.

**Windows**

`surreal.exe` is not written as a Windows service, so registering it directly with `sc.exe create` fails with error 1053 when the service starts. A service wrapper such as [WinSW](https://github.com/winsw/winsw) runs it as a service instead. Download `WinSW-x64.exe` from the WinSW releases, save it as `C:\SurrealDB\surrealdb-service.exe`, and save the following beside it as `surrealdb-service.xml`:

```xml title="C:\SurrealDB\surrealdb-service.xml"
<service>
  <id>surrealdb</id>
  <name>SurrealDB</name>
  <description>SurrealDB server</description>
  <executable>C:\Program Files\SurrealDB\surreal.exe</executable>
  <arguments>db start</arguments>
  <workingdirectory>C:\SurrealDB</workingdirectory>
  <env name="SURREAL_USER" value="root" />
  <env name="SURREAL_PASS" value="secret" />
  <env name="SURREAL_BIND" value="127.0.0.1:8000" />
  <env name="SURREAL_PATH" value="rocksdb://data" />
  <onfailure action="restart" delay="10 sec" />
  <log mode="roll" />
</service>
```

The `<env>` entries play the part of the Linux environment file, with the same [environment variables](/docs/reference/cli/db/environment-variables.md). The relative data path is resolved from the working directory, so the data is stored in `C:\SurrealDB\data`. Then, in a terminal opened as administrator, install and start the service:

```powershell
cd C:\SurrealDB
.\surrealdb-service.exe install
.\surrealdb-service.exe start
```

The service starts with Windows from then on. WinSW stops the server by sending it Ctrl+C, which SurrealDB handles as a clean shutdown, and writes the server's output to log files in `C:\SurrealDB`. The XML file holds the root password, so limit who can read it, for example with `icacls surrealdb-service.xml /inheritance:r /grant:r Administrators:F SYSTEM:R`.

**Storage engines** are selected by the path on [`surreal db start`](/docs/reference/cli/db/commands/start.md). For most self-hosted servers, use **RocksDB** (`rocksdb://…`) for on-disk production data. The default **`memory`** backend (SurrealMX) suits development and tests as well as solutions that make use of its Redis-like persistent storage, while the optional **SurrealKV** (`surrealkv://…`, beta) is an alternative single-node disk engine with a smaller tuning surface. Capacity planning, backups, and versioning options differ by engine - see the [Deployment models](/docs/manage/self-hosted/deployment-models.md) and [Run a single-node, on-disk server](/docs/running/file-backed.md) pages for more.

**Bind address and port** determine where clients and operators reach the server. Restrict admin interfaces to trusted networks, use a load balancer or service mesh for TLS termination if that matches your platform, and document ports for HTTP, WebSocket, and any separate metrics endpoints you expose.

Firewall rules should default to deny: allow only the subnets and ports your applications and operators require.

Set **authentication** and **root user** credentials explicitly in production. Root credentials grant broad access: rotate them on a schedule, store them in a vault, and avoid reusing development passwords.

**TLS**: supply certificates and keys via `surreal db start` or env vars so wire traffic is encrypted. Renew certificates before expiry, pin trusted CAs where appropriate, and consider a reverse proxy for advanced policies while keeping SurrealDB’s own TLS configuration minimal and auditable.

After changes, confirm the effective configuration by inspecting startup logs and probing only the intended listeners. In regulated environments, record who approved credential and TLS updates and when they took effect.

---

Source: https://surrealdb.com/docs/manage/self-hosted/deployment-models

# Deployment models

Deployment models for SurrealDB - managed instances, single-node RocksDB, multi-node clusters, and embedded runtimes - and how to choose between them.

SurrealDB separates the query engine (compute) from the underlying storage layer. The same SurrealQL, APIs, and client SDKs work across embedded devices, single-node servers, distributed clusters, and [managed instances](/docs/manage/instances.md) - so you can change how the database runs without rewriting application code.

This page explains the available deployment options, storage engines, and how to choose the right architecture for your workload. For how the compute and storage layers interact, see [Architecture](/docs/learn/data-models/architecture.md).

## Deployment models summary

| Deployment model | Storage engine(s) | Scaling | High availability | Versioning (`VERSION`) | Best for | Managed option |
| --- | --- | --- | --- | --- | --- | --- |
| Managed | Start (single-node) to Scale (multi-node cluster) | Vertical and horizontal (by plan) | Fully managed HA on Scale | Where enabled | Production without operating infrastructure | Yes |
| Single node | RocksDB (recommended for server workloads); SurrealKV | Vertical | Filesystem backups | Where enabled (see engine docs) | Development and single-node production | Self-hosted (Community Edition) |
| Multi-node | Distributed transactional storage | Horizontal; distributed storage | High (replication and consensus) | Where enabled on the storage tier | Large-scale production workloads | [Scale](https://surrealdb.com/pricing/scale) plan; self-hosted Enterprise |
| Embedded | SurrealMX (memory), SurrealKV, RocksDB, IndexedDB (browser) | Application-bound | Application-bound | Where enabled | Offline, edge, browser, and low-latency local apps | No |

## Architecture overview

SurrealDB consists of two major layers:

**Query layer**

- Parses and executes SurrealQL
- Authenticates connections and sessions
- Enforces table- and field-level permissions on reads and writes
- Plans index-backed queries, maintains index entries during writes, and coordinates transactions

**Storage layer**

- Handles persistence and durability
- Determines scalability, temporal versioning, replication, and fault tolerance

Because these layers are separated in the architecture, applications can move between deployment models without changing application code or queries. Embedded and browser deployments still use both layers, but they run in-process rather than as separate services.

## Managed instances

[Managed instances](/docs/manage/instances.md) provide a fully managed deployment platform built on scalable, fault-tolerant infrastructure. SurrealDB operates the clusters, so your team does not have to.

Plans range from **Start** (single-node, vertically scalable instances) to **Scale** (multi-node clusters on distributed storage, minimum three compute units). Start is enough for many workloads; Scale is aimed at business-critical production where a single-node outage would stop the application and where operating HA yourself (Kubernetes, replication, patching, and backups) would otherwise consume platform team time. See [Architecture](/docs/manage/instances/architecture.md) and [Pricing](https://surrealdb.com/pricing).

### Features

- Managed SurrealDB infrastructure
- Vertical and horizontal scaling without rebuilding the cluster
- High availability
- Managed backups
- Secure connectivity
- Multi-node distributed architecture on the Scale plan
- Production monitoring and operations

### Best for

- Managed infrastructure
- Rapid production deployment
- Scaling applications
- SaaS platforms
- Enterprise workloads

### Benefits compared to self-hosting

| Feature | Self-hosted | Managed |
| --- | --- | --- |
| Infrastructure management | Required | Managed |
| Backups | Manual | Managed |
| Scaling | Manual | Managed resize, no cluster rebuild |
| HA setup | Manual | Built-in |
| Upgrades | Manual | Managed |
| Cluster operations | Manual | Managed |

## Single-node (RocksDB)

Single-node deployments run SurrealDB as a standalone server process using [RocksDB](https://rocksdb.org/). This is the simplest and most widely used production architecture on disk. RocksDB is a high-performance LSM-tree key-value store optimised for high write throughput, SSD storage, and predictable persistence.

**Best for**

- Small to medium production workloads
- Internal tooling
- Development environments
- Applications without horizontal scaling or built-in cluster fault tolerance

### Limitations

- Vertical scaling only
- No built-in distributed fault tolerance

For setup examples, see [Run a single-node, on-disk server](/docs/running/file-backed.md).

### Common deployment methods

**CLI**

```bash
surreal db start rocksdb://path/to/database
```

**Docker**

```bash
docker run --rm \
  -p 8000:8000 \
  surrealdb/surrealdb:latest \
  start --user root --pass secret rocksdb://data/database.db
```

See also [Self-hosted](/docs/manage/self-hosted.md) for Docker, Kubernetes, and platform guides.

### SurrealKV

For single-node and embedded workloads, [SurrealKV](https://github.com/surrealdb/surrealkv) is SurrealDB’s own LSM-backed storage engine, developed together with the database rather than as a third-party dependency. As a result, SurrealKV exposes a comparatively small [configuration surface](/docs/reference/cli/db/commands/start.md#supported-parameters-for-surrealkv) and [environment variable set](/docs/reference/cli/db/environment-variables.md#surrealkv-environment-variables) next to RocksDB’s extensive tuning knobs, and is aimed at embedded and local-first scenarios.

The chart below shows each setting that the two engines read, one square for each, grouped by what it controls. RocksDB has 53 and SurrealKV has 10, not counting the `versioned`, `retention` and `sync` settings that both share. In the SurrealKV rows, a dashed square stands for a RocksDB setting that SurrealKV leaves to the engine, and the dashed square for RocksDB versioning shows how each engine keeps versions. Select a square to see its environment variable, or the reason no setting is needed. The [environment variables](/docs/reference/cli/db/environment-variables.md#storage-backend-environment-variables) reference describes each variable.

<DemoEmbed title="Storage engine settings" height={820} pages={[{ "label": "Settings per storage engine", "src": "/docs/engine-settings/settings.html" }]} />

In SurrealDB 3.3 and earlier, SurrealKV is in **beta**, and **RocksDB** is the recommended engine for production on-disk server deployments. For embedded deployments where smaller resident memory and in-process behaviour are priorities, SurrealKV is the path to evaluate first.

SurrealKV has since reached 1.0, which has a stable on-disk format. The upcoming SurrealDB 3.4 uses SurrealKV 1.0 and makes it the recommended on-disk engine. SurrealKV 1.0 also converts an existing RocksDB data directory the first time it opens it, so a server on 3.4 can change engines without an export and import. SurrealKV is still where new storage features are tried first: features such as temporal reads via the [`VERSION`](/docs/reference/query-language/statements/select.md#the-version-clause) clause were exercised on SurrealKV first and have since been extended to RocksDB. From 3.3.0, the in-memory [SurrealMX](/docs/running/in-memory.md) backend does not support versioned queries.

To try SurrealKV on a server, see the SurrealKV tab on [Run a single-node, on-disk server](/docs/running/file-backed.md) and the [`surreal db start`](/docs/reference/cli/db/commands/start.md) storage parameters.

## Multi-node clusters

For high availability and horizontal scalability, SurrealDB supports distributed deployments backed by a distributed transactional storage layer built for large-scale clusters.

### Architecture

In distributed deployments:

- Multiple query nodes scale horizontally against shared storage
- The storage layer manages replication, consensus, fault tolerance, and distributed transactions
- Object-storage backing (rolling out on Scale) places transactional data in commodity object storage while frequently accessed data stays on local disk

<img src="~/assets/img/image/cloud/light/cloud-multi-node-light.png" darkSrc="~/assets/img/image/cloud/cloud-multi-node-dark.png" alt="Diagram of a multi-node cluster: requests to [instance-id].surreal.cloud fan out across three nodes, all backed by centralised storage on AWS S3." />

This architecture enables zero-downtime scaling, resilient clusters, high-throughput workloads, geographically distributed applications, and (as Scale features roll out) database branching, instant replication and recovery, and lower storage costs at scale.

For managed multi-node clusters, use the [Scale](https://surrealdb.com/pricing/scale) plan on a [managed instance](/docs/manage/instances.md). For self-hosted multi-node clusters on Kubernetes, use [SurrealDB Enterprise](https://surrealdb.com/enterprise); the [Managed Kubernetes](/docs/manage/self-hosted/managed-kubernetes.md) page summarises options per cloud. See [Run a multi-node cluster](/docs/running/multi-node.md) for how the model itself works.

### Features

**Horizontal scalability** - Scale query and storage nodes independently.

**Fault tolerance** - Replication and consensus allow clusters to tolerate node failures.

**Distributed ACID transactions** - Strong transactional guarantees across distributed infrastructure.

**Large-scale storage** - Designed for very large datasets and high-concurrency production workloads.

### Best for

- Enterprise deployments
- High-availability applications
- Multi-region systems
- Large graph workloads
- Real-time platforms
- AI-native distributed systems

### Operational considerations

Distributed deployments add cluster orchestration, node management, monitoring, and replication management. Teams that want distributed scalability without operating that stack should consider [managed instances](/docs/manage/instances.md).

## Embedded deployments

Embedded deployments run SurrealDB inside your application process without a separate database server. The query and storage layers share the process (and, in the browser, the same runtime), which removes network latency between your app and the database. This model suits edge computing, mobile and desktop software, offline-first apps, browser PWAs, and AI workflows that need minimal latency.

For language-specific setup, see [Embedding SurrealDB](/docs/build/embedding.md) and [Storage engines](/docs/build/embedding/storage-engines.md).

### Supported runtimes

SurrealDB supports embedded operation in Rust, Go, JavaScript / TypeScript, WebAssembly, Python, and .NET. Capabilities vary by SDK and storage backend - check the embedding guide for your language.

### Storage options

**In-memory (SurrealMX)** - Default in-memory backend since SurrealDB 3.0, with optional snapshots or append-only persistence. From 3.3.0 it does not support versioned queries. See [Run a single-node, in-memory server](/docs/running/in-memory.md).

**RocksDB** - Persistent on-disk storage with mature tuning for write-heavy, SSD-backed server workloads. See [File-backed storage](/docs/running/file-backed.md).

**SurrealKV** - Same beta engine as single-node server deployments; aimed at embedded and local-first in-process workloads where smaller resident memory and simple operational behaviour matter most.

**IndexedDB (browser)** - Browser-native persistence with binary serialisation for PWAs and local-first web apps. See [Embedding SurrealDB](/docs/build/embedding.md) and the [Wasm engine](/docs/reference/javascript/engines/wasm.md).

## Choosing the right deployment model

**Use a managed instance when**

- You want managed infrastructure instead of operating clusters yourself
- You need production-ready HA quickly - especially on the **Scale** plan for multi-node fault tolerance
- Your team prefers building applications over provisioning servers, Kubernetes, replication, and upgrade runbooks

**Use single-node deployments with RocksDB when**

- Simplicity matters most
- Scaling requirements are moderate
- You run small-to-medium production workloads without cluster-level fault tolerance

**Use multi-node deployments when**

- High availability is required
- Workloads need horizontal scaling
- Infrastructure spans multiple nodes or availability zones

For managed clusters, use the [Scale](https://surrealdb.com/pricing/scale) plan. For self-hosted clusters, see [SurrealDB Enterprise](https://surrealdb.com/enterprise) and [Managed Kubernetes](/docs/manage/self-hosted/managed-kubernetes.md).

**Use embedded deployments when**

- You run on edge devices or in the browser
- Offline operation is required
- Minimising latency between app and database is critical

## Next steps

- [Managed instances](/docs/manage/instances.md) - Provision a managed SurrealDB instance with built-in monitoring, network access controls and operations tooling.

- [Self-hosted](/docs/manage/self-hosted.md) - Run SurrealDB on Docker, Kubernetes (AKS, EKS, GKE) or as a standalone binary with full control over storage and configuration.

- [Observability](/docs/manage/observability.md) - Metrics, OTLP and Prometheus access, audit logs and slow-query logs for both Community and Enterprise editions.

---

Source: https://surrealdb.com/docs/manage/self-hosted/kubernetes

# Kubernetes

Deploy SurrealDB to Kubernetes with RocksDB on a persistent volume.

This guide deploys SurrealDB to a local [KIND](https://kind.sigs.k8s.io/) cluster (Kubernetes in Docker) with **RocksDB** on a persistent volume. That is a **single-node** topology: one SurrealDB pod owns the database file. See [Deployment models](/docs/manage/self-hosted/deployment-models.md#single-node-rocksdb).

For **multi-node HA**, use the managed [Scale plan](https://surrealdb.com/pricing/scale) or [SurrealDB Enterprise](https://surrealdb.com/enterprise) on managed Kubernetes - see [Managed Kubernetes](/docs/manage/self-hosted/managed-kubernetes.md).

## Requirements

- [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
- [`helm`](https://helm.sh/docs/intro/install/)
- [KIND](https://kind.sigs.k8s.io/) and [Docker](https://www.docker.com/)
- [Surreal CLI](/docs/reference/cli/db.md)

## Create a KIND cluster

```bash
kind create cluster -n surreal-demo
kubectl config current-context   # kind-surreal-demo
kubectl get ns
```

## Deploy SurrealDB

Use the [SurrealDB Helm chart](https://github.com/surrealdb/helm-charts) with a **ReadWriteOnce** persistent volume. Keep **`replicaCount: 1`** - multiple pods must not share one RocksDB file.

### 1. Add the Helm repository

```bash
helm repo add surrealdb https://helm.surrealdb.com
helm repo update
```

### 2. Install with persistence

The chart mounts storage at `/home/nonroot` so the non-root container user can write to the volume:

```bash
cat <<'EOF' | helm install surrealdb-rocksdb surrealdb/surrealdb -f -
strategy:
  type: Recreate
replicaCount: 1
persistence:
  enabled: true
  mountPath: /home/nonroot
  size: 10Gi
surrealdb:
  path: rocksdb:///home/nonroot/data.db
  unauthenticated: true
EOF
```

### 3. Create initial credentials

Port-forward the service, define a root user, then re-enable authentication:

```bash
kubectl port-forward svc/surrealdb-rocksdb 8000:8000
```

In another shell:

```bash
surreal db sql -e http://localhost:8000
> DEFINE USER root ON ROOT PASSWORD 'StrongSecretPassword!' ROLES OWNER;
```

Upgrade the release without `unauthenticated`:

```bash
helm upgrade surrealdb-rocksdb surrealdb/surrealdb -f - <<'EOF'
strategy:
  type: Recreate
replicaCount: 1
persistence:
  enabled: true
  mountPath: /home/nonroot
  size: 10Gi
surrealdb:
  path: rocksdb:///home/nonroot/data.db
EOF
```

### 4. Verify persistence

```bash
surreal db sql -u root -p 'StrongSecretPassword!' -e http://localhost:8000
> USE NS ns DB db;
ns/db> CREATE record SET id = record:one;
ns/db> SELECT * FROM record;
```

Delete the SurrealDB pod and confirm data survives on the PVC:

```bash
kubectl get pod
kubectl delete pod <surrealdb-rocksdb-pod-name>
kubectl port-forward svc/surrealdb-rocksdb 8000:8000
surreal db sql -u root -p 'StrongSecretPassword!' -e http://localhost:8000
> USE NS ns DB db;
ns/db> SELECT * FROM record;
```

> [!NOTE]
> On a full Kubernetes cluster, set `ingress.enabled=true` when installing the chart to expose SurrealDB through a load balancer instead of port-forwarding.

## Next steps

- [Docker](/docs/running/docker.md) - single-node RocksDB without Kubernetes
- [Run a single-node, on-disk server](/docs/running/file-backed.md) - CLI startup options for RocksDB and SurrealKV
- [Deployment models](/docs/manage/self-hosted/deployment-models.md) - managed, single-node, and highly available options

---

Source: https://surrealdb.com/docs/manage/self-hosted/managed-kubernetes

# Managed Kubernetes

Options for running SurrealDB on Amazon EKS, Google GKE, and Azure AKS - managed Scale, self-hosted Enterprise clusters, or single-node RocksDB.

SurrealDB runs on Amazon EKS, Google GKE and Azure AKS. This page covers the three deployment models available on them - managed Scale, a self-hosted Enterprise cluster, and single-node RocksDB - and which one fits.

> [!IMPORTANT]
> Production **multi-node** SurrealDB uses shared distributed storage with replication and consensus. For managed HA, use the [Scale plan](https://surrealdb.com/pricing/scale). Self-hosted multi-node clusters on Kubernetes are available with [SurrealDB Enterprise](https://surrealdb.com/enterprise).

> [!NOTE]
> For a **single-node RocksDB** deployment on Kubernetes - including a cluster you create with EKS, GKE, or AKS - start with [Deploy on Kubernetes](/docs/manage/self-hosted/kubernetes.md). For how the multi-node model works in general, see [Run a multi-node cluster](/docs/running/multi-node.md).

## The managed control planes

Each of the three major clouds offers a managed Kubernetes control plane. The control plane is the only part they manage for you; how SurrealDB storage is provided is still your choice - single-node RocksDB on a persistent volume, or a multi-node cluster on distributed storage (Scale or Enterprise).

| Provider | Service |
| --- | --- |
| AWS | [Amazon Elastic Kubernetes Service (EKS)](https://docs.aws.amazon.com/eks/) |
| Google Cloud | [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine), including Autopilot |
| Microsoft Azure | [Azure Kubernetes Service (AKS)](https://azure.microsoft.com/products/kubernetes-service) |

## Choose a path

| Goal | Path |
| --- | --- |
| Managed multi-node HA (recommended for most teams) | A managed [Scale](/docs/manage/instances.md) cluster, with the storage layer operated for you |
| Self-hosted multi-node HA on EKS, GKE, or AKS | [SurrealDB Enterprise](https://surrealdb.com/enterprise) with distributed storage (operator and runbooks shipped with Enterprise) |
| Single SurrealDB pod, RocksDB on a volume | [Deploy on Kubernetes](/docs/manage/self-hosted/kubernetes.md) on a cluster you manage |

## Next steps

- [Deployment models](/docs/manage/self-hosted/deployment-models.md) - single-node vs multi-node vs managed
- [Instances](/docs/manage/instances.md) - the managed option
- [Scale](https://surrealdb.com/pricing/scale) - pricing for managed multi-node clusters
- [Observability](/docs/manage/observability.md) once an instance is running

---

Source: https://surrealdb.com/docs/manage/self-hosted/monitoring-and-observability

# Monitoring & observability

Health checks, OpenTelemetry metrics and traces, audit logs and slow-query logs, and integration with common observability stacks.

SurrealDB exposes a built-in **`/health`** HTTP endpoint suitable for load balancer and orchestrator probes. A successful response indicates the process is accepting requests. Combine `/health` with deeper checks so you detect partial failures - slow queries, disk pressure, replication lag - before probes alone would fire.

For everything beyond a liveness probe, the [Observability](/docs/manage/observability.md) section is the canonical reference and operator guide. The pages there cover metrics, telemetry access, audit logs and slow-query logs for both Community and Enterprise editions.

- [Observability overview](/docs/manage/observability.md) - Edition matrix (Community vs Enterprise), Prometheus and OTLP quickstart, what's new in 3.1, Tokio console, structured logging.

- [Metrics reference](/docs/manage/observability/metrics.md) - Access paths, label catalogue, ~60 metrics grouped by signal family, the public allowlist, and the 3.0 → 3.1 migration table.

- [Configuration reference](/docs/manage/observability/configuration.md) - Every telemetry, audit-log and slow-query environment variable, plus recommended configurations for local, production and multi-tenant deployments.

- [Audit logging (Enterprise)](/docs/manage/observability/audit-logging.md) - Events captured, record shape, rotation, hash chaining, redaction and pipeline self-metrics.

- [Slow-query logging (Enterprise)](/docs/manage/observability/slow-query-logging.md) - Threshold-based capture of long-running queries with the same file-sink, hash-chain and redaction options as the audit pipeline.

## Integrating with common observability stacks

The OpenTelemetry exporter speaks OTLP gRPC, so anything that ingests OTLP works. Two common patterns:

- **Prometheus pull.** Scrape the built-in `/metrics` endpoint. Anonymous scrapers see only the [public allowlist](/docs/manage/observability/metrics.md#public-metrics-allowlist); pass root credentials to unlock the full surface. Build Grafana dashboards on the resulting time series and pair with Alertmanager for the [alert hints](/docs/manage/observability/metrics.md#alert-hints) recommended for production.
- **OTLP push.** Point SurrealDB at an OpenTelemetry collector and route from there into Prometheus remote-write, Tempo / Jaeger for traces, and Loki or a SIEM for logs (including audit and slow-query records when their OTel export is opted in). Label streams by environment (`production`, `staging`, `dev`) at the collector so dashboards do not mix traffic accidentally.

Either path uses the same `surrealdb.*` instrument namespace and the same `service.edition` resource attribute, so the same dashboards work across deployments.

## Key signals to watch

The full alert-hints starter set lives on the [metrics page](/docs/manage/observability/metrics.md#alert-hints), but the must-watch signals for any production deployment are:

- **Error rate** - `rate(surrealdb_statement_total{outcome="error"}[5m])` rising sharply against baseline.
- **Transaction conflicts** - `rate(surrealdb_transaction_conflicts_total[5m])`, which doubles as the retry-pressure signal.
- **Latency tails** - histogram percentiles on `surrealdb_statement_duration_seconds`, `surrealdb_query_duration_seconds`, `surrealdb_http_request_duration_seconds`.
- **Active sessions and live queries** - `surrealdb_session_active`, `surrealdb_live_query_active` for capacity planning.
- **Audit pipeline health (Enterprise)** - `surrealdb_audit_dropped`, `surrealdb_audit_append_errors`, `surrealdb_audit_queue_depth` to catch lost or delayed audit records.

Define SLOs where appropriate - latency and availability targets - and use burn-rate alerts on the resulting error budgets rather than relying on one-off thresholds alone.

---

Source: https://surrealdb.com/docs/manage/self-hosted/upgrades-and-patching

# Upgrades & patching

Upgrade SurrealDB safely: binary replacement, major-version data migration, automatic data migrations, and cluster rolling upgrades.

**Routine upgrades** typically follow: quiesce or drain clients if your SLA allows, **stop** the server gracefully, **replace** the `surreal` binary with the new release, then **start** with unchanged data paths and reviewed configuration.

Read the release notes for breaking changes, new defaults, or removed flags before you cut over. Re-run integration tests against the new version before promoting the change across your organisation.

Across **major versions**, on-disk formats may change. Follow [Migrating from older SurrealDB versions](/docs/build/migrating/from-old-surrealdb-versions/overview.md) and the guide for your pair of major versions. Moving from 2.x to 3.x uses a v3-compatible export from the 2.x data, imported into 3.x, as described in [Migrating from 2.x to 3.x](/docs/build/migrating/from-old-surrealdb-versions/2x-to-3x.md). https://github.com/orgs/surrealdb/discussions/4809
Moving from 1.x to 2.x is described in [Migrating from 1.x to 2.x](/docs/build/migrating/from-old-surrealdb-versions/1x-to-2x.md), which also lists the breaking changes in SurrealQL and the SDKs. [`surreal db fix`](/docs/reference/cli/db/commands/fix.md) converts 1.x data to the 2.x layout, and only 2.x binaries implement it.
--END--

**Rolling upgrades** in **clustered** setups usually upgrade one node at a time: verify cluster health, upgrade a member, wait for replication or quorum to stabilise, then continue.

Never skip staging validation for production-like data volumes. If the cluster spans regions, plan maintenance windows that respect dependency order between tiers.

**Before any upgrade**, take a fresh backup - [`surreal db export`](/docs/reference/cli/db/commands/export.md) for a logical copy and/or a storage snapshot - so you can revert if migration or client incompatibility surfaces after deploy. Some minor upgrades can only be reverted this way, including [2.6 to 2.7](/docs/build/migrating/from-old-surrealdb-versions/26-to-27.md#rolling-back-to-26) and [3.2 to 3.3](/docs/build/migrating/from-old-surrealdb-versions/32-to-33.md#rolling-back-to-32).

Patch **security** releases promptly: subscribe to SurrealDB advisories, test the patch build in staging, then roll out using the same stop - replace - start or rolling pattern your architecture supports. Document the upgraded version in your asset inventory for compliance reviews.

## Automatic data migrations

_(since v3.3.0)_

From SurrealDB 3.3.0, a datastore records the version that last opened it and applies any pending data migrations on startup, before it serves the first query. Migrations are recorded in a cluster-wide ledger, so each one runs once no matter how many nodes start against the same datastore, and an interrupted run resumes rather than restarts. A migration that fails aborts startup.

A datastore created on 3.3.0 or later records the full migration set as applied when it is created, so it never runs a historical migration.

Two consequences for planning an upgrade:

- **Reverting past a migration needs an export.** From 3.3.0, a build that lacks a migration the datastore has recorded refuses to start and names the migration. A build older than 3.3.0 records no migrations and checks only the major version, so it starts on a migrated datastore and reads it wrongly. In both cases the way back is to restore a [`surreal db export`](/docs/reference/cli/db/commands/export.md) taken before the upgrade into a datastore created by the older version. See [Rolling back to 3.2](/docs/build/migrating/from-old-surrealdb-versions/32-to-33.md#rolling-back-to-32).
- **Define new sequences once every node runs 3.3.0.** The migration copies each sequence definition to a new key layout and leaves the original in place, so existing sequences keep working on nodes of either version throughout the rollout. A sequence created on an upgraded node exists only in the new layout, so a node still on the previous release cannot see it.

> [!NOTE]
> The first migration fixes a key layout in which `DEFINE SEQUENCE` definitions overlapped tables whose names begin with `sq`. On 3.0 to 3.2.4, a database containing such a table fails `INFO FOR DB`, fails `REMOVE DATABASE` - leaving the database undroppable - and fails an export that includes sequences. No sequence needs to exist for this to happen. SurrealDB 3.2.5 skips such a table when it lists sequence definitions, so these operations work again. Upgrading to 3.3.0 repairs the key layout.

---

Source: https://surrealdb.com/docs/reference/cli

# CLI tools

Overview of every command in the surreal launcher, surreal db, surreal cloud and surreal mod, with links to each reference page, plus the separate surqlfmt formatter.

SurrealDB has one command line, `surreal`. The `surreal` launcher installs once, then installs, versions and runs **components**, each with its own commands: the database (`surreal db`), SurrealDB Cloud (`surreal cloud`) and WebAssembly modules (`surreal mod`). This section is the reference for all of them. For how they fit together, start with the [SurrealDB CLI guide](/docs/explore/cli.md).

```bash
surreal install cloud
surreal db start --user root --pass secret
surreal cloud instance list
```

| Reference | Covers |
| --- | --- |
| [Launcher commands](/docs/reference/cli/launcher.md) | Installing, upgrading, pinning and removing components, version names, `+VERSION`, and the launcher's environment variables |
| [`surreal db`](/docs/reference/cli/db.md) | The database: run a server, query it, and move data in and out |
| [`surreal cloud`](/docs/reference/cli/cloud.md) | SurrealDB Cloud: organisations, instances, backups, members and tokens |
| [`surreal mod`](/docs/reference/cli/modules.md) | WebAssembly modules |
| [`surqlfmt`](/docs/reference/cli/formatter/overview.md) | Formatting `.surql` files, distributed separately as an npm package |

## Launcher commands

These commands belong to the launcher, and nothing else installs or changes component versions. See [Launcher commands](/docs/reference/cli/launcher.md) for options and examples.

| Command | Purpose |
| --- | --- |
| `surreal install <COMPONENT[@VERSION]>...` | Install components, or another version of one beside those installed |
| `surreal upgrade [<COMPONENT[@VERSION]> \| cli]...` | Upgrade the launcher and every installed component, or those named |
| `surreal use <COMPONENT@VERSION>` | Make an installed version the default |
| `surreal uninstall <COMPONENT[@VERSION]>...` | Remove a version, or a component entirely |
| `surreal version` | Show each component, the version that runs, and where it runs from |
| `surreal --version` | Print the launcher's version and each component's |
| `surreal +VERSION <COMPONENT> ...` | Run another version for one command |
| `surreal help` | Print the launcher's help |

## surreal db

The database component. Everything after `db` goes to the database, so the same flags and `SURREAL_*` variables apply. Each command has its own page under [database commands](/docs/reference/cli/db/commands.md).

| Command | Purpose |
| --- | --- |
| [`surreal db start`](/docs/reference/cli/db/commands/start.md) | Run a SurrealDB server |
| [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) | Open an interactive SurrealQL shell |
| [`surreal db import`](/docs/reference/cli/db/commands/import.md) | Load a SurrealQL file into a database |
| [`surreal db export`](/docs/reference/cli/db/commands/export.md) | Dump a database to SurrealQL |
| [`surreal db is-ready`](/docs/reference/cli/db/commands/isready.md) | Check that a server accepts connections |
| [`surreal db validate`](/docs/reference/cli/db/commands/validate.md) | Validate SurrealQL files |
| [`surreal db fix`](/docs/reference/cli/db/commands/fix.md) | Repair storage issues |
| [`surreal db ml`](/docs/reference/cli/db/commands/ml.md) | Import and export machine-learning models |
| [`surreal db mcp`](/docs/reference/cli/db/commands/mcp.md) | Run the Model Context Protocol server over stdio |
| [`surreal db module`](/docs/reference/cli/db/commands/module.md) | Build and run WebAssembly modules |
| [`surreal db version`](/docs/reference/cli/db/commands/version.md) | Print the database or server version |

The old top-level forms, such as `surreal start`, still run and print a deprecation warning. Upgrading the database is `surreal upgrade db`, a launcher command.

## surreal cloud

The SurrealDB Cloud component. Every command takes `--json`, and sends its data to stdout and everything else to stderr. See [global flags](/docs/reference/cli/cloud/global-flags.md) and [output and exit codes](/docs/reference/cli/cloud/output-and-exit-codes.md).

| Command | Purpose |
| --- | --- |
| [`surreal cloud auth`](/docs/reference/cli/cloud/commands/auth.md) | Sign in, sign out, and inspect credentials |
| [`surreal cloud org`](/docs/reference/cli/cloud/commands/org.md) | Manage organisations, usage, spend and plans |
| [`surreal cloud instance`](/docs/reference/cli/cloud/commands/instance.md) | Create, scale, pause, back up, connect to and delete instances |
| [`surreal cloud team`](/docs/reference/cli/cloud/commands/team.md) | Manage organisation members |
| [`surreal cloud invite`](/docs/reference/cli/cloud/commands/invite.md) | Manage organisation invitations |
| [`surreal cloud token`](/docs/reference/cli/cloud/commands/token.md) | Manage personal access tokens |
| [`surreal cloud catalog`](/docs/reference/cli/cloud/commands/catalog.md) | Browse regions, instance types and versions |
| [`surreal cloud config`](/docs/reference/cli/cloud/commands/config.md) | Read and write configuration |
| [`surreal cloud context`](/docs/reference/cli/cloud/commands/context.md) | Inspect and switch profiles |
| [`surreal cloud whoami`, `api`, `open`, `status`, `version`, `completion`](/docs/reference/cli/cloud/commands/misc.md) | Identity, raw API calls, dashboard links, diagnostics and shell completions |

## surreal mod

The WebAssembly module component. Its first release is not published yet, and the same workflow is available today as `surreal db module`. See [`surreal mod`](/docs/reference/cli/modules.md).

## SurrealQL formatter

`surqlfmt` reformats `.surql` files to a consistent style. It is distributed as an npm package rather than as a component of `surreal`, which makes it easy to pin in a project and run in CI.

```bash
npm install -g @surrealdb/surql-fmt
surqlfmt --check ./src/**/*.surql
```

See the [SurrealQL formatter reference](/docs/reference/cli/formatter/overview.md) for its options and usage patterns.

---

Source: https://surrealdb.com/docs/reference/cli/cloud

# surreal cloud reference

What surreal cloud is, where the control plane ends and the database commands begin, how it relates to the former surrealctl, the command grammar, and how this reference is organised.

`surreal cloud` is the component of the [`surreal` launcher](/docs/reference/cli/launcher.md) that manages SurrealDB Cloud from the command line. It is the control plane: it creates and scales instances, reads logs and metrics, manages organisation members and invitations, and mints credentials, all through the SurrealDB API at `https://api.surrealdb.com`.

This reference is for operators, CI pipelines and agents. Every command emits machine-readable JSON on request, keeps its data payload on stdout, and returns an exit code precise enough to branch on.

For installing the launcher, see [Install](/docs/explore/cli/install.md). The launcher offers to fetch `surreal cloud` the first time you run it, or you can install it ahead of time with `surreal install cloud`. For a first walkthrough, see the [guide](/docs/explore/cli/cloud.md).

## Coming from surrealctl

`surreal cloud` was previously the standalone `surrealctl` tool. The commands and flags are the same, so a script written for `surrealctl` works after replacing the program name with `surreal cloud`. Two things migrate automatically:

- **Configuration and login.** On its first run, `surreal cloud` copies your configuration and stored login from `~/.config/surrealctl` (`%APPDATA%\surrealctl` on Windows) to `~/.surrealdb/cloud`. The original is left in place, and from then on the two are independent.
- **Environment variables.** `SURREALCTL_*` variables still work as deprecated aliases for their `SURREAL_CLOUD_*` replacements. When one is set, a single warning on stderr lists each old name with its replacement. The new name wins when both are set.

The `--json` contract and the exit codes are unchanged.

## The control plane and the data plane

`surreal cloud` owns the **control plane**: the resources around a database - instances, organisations, members, tokens, capabilities, backups. The database commands, `surreal db ...`, own the **data plane**: queries, imports, exports, and running a server. Both are reached through the same `surreal` launcher, and `surreal cloud` follows the same conventions so you can move between them without relearning anything.

| Task | Command |
| --- | --- |
| Create, scale, pause or delete a Cloud instance | `surreal cloud instance` |
| Read an instance's logs, metrics or usage | `surreal cloud instance` |
| Invite a colleague, change their role | `surreal cloud team`, `surreal cloud invite` |
| Mint a database token or fetch a key set | `surreal cloud instance token`, `surreal cloud instance jwks` |
| Run a SurrealQL query | [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) |
| Run a local server | [`surreal db start`](/docs/reference/cli/db/commands/start.md) |

Three commands cross the boundary. [`instance sql`](/docs/reference/cli/cloud/commands/instance.md#instance-sql), [`instance import`](/docs/reference/cli/cloud/commands/instance.md#instance-import) and [`instance export`](/docs/reference/cli/cloud/commands/instance.md#instance-export) resolve the instance, mint a database token, and hand off to `surreal db sql`, `surreal db import` or `surreal db export` through the launcher rather than reimplementing SurrealQL. Everything after a `--` separator is forwarded to the database command verbatim.

`surreal cloud` never installs or locates the database itself. The launcher does, and it offers to install the database if it is missing. Run on its own, outside the launcher, the three handoff commands refuse and say how to run them. See [Components](/docs/explore/cli/components.md) for how the launcher runs them.

## Command grammar

The grammar is regular, so it only has to be learned once:

- **Groups are singular nouns.** `instance`, not `instances` - the plural is a visible alias.
- **Verbs are `list`, `get`, `create`, `update`, `delete`.** The only aliases are `ls` for `list` and `rm` for `delete`.
- **Destructive commands take `--force`**, never `--skip-confirmations`. The global `--yes` pre-answers every confirmation in one invocation.
- **`--json` is the only encoding switch.** There is no `--format`.
- **Two deliberate exceptions**, both in `team`: `team remove` ends a membership rather than deleting a person, and `team invite` sends an invitation, which no house verb covers.

Any noun group invoked on its own prints its own help, and `help` works at every level:

```bash
surreal cloud instance
surreal cloud help instance create
```

## How this reference is organised

The cross-cutting pages describe behaviour shared by every command. Read them once.

| Page | Covers |
| --- | --- |
| [Authentication](/docs/reference/cli/cloud/authentication.md) | Login sessions, personal access tokens, and where credentials are stored |
| [Global flags](/docs/reference/cli/cloud/global-flags.md) | The 16 flags every command accepts, their environment variables, and the precedence chain |
| [Output and exit codes](/docs/reference/cli/cloud/output-and-exit-codes.md) | The `--json` contract, the stream split, list presentation flags, and every exit code |
| [Long-running operations](/docs/reference/cli/cloud/long-running-operations.md) | `--wait`, `--no-wait`, polling behaviour, and exit code `10` |

The command pages document every flag, argument and refusal, one page per group. The [commands index](/docs/reference/cli/cloud/commands.md) lists every command:

| Group | Purpose |
| --- | --- |
| [`auth`](/docs/reference/cli/cloud/commands/auth.md) | Sign in, sign out, and inspect credentials |
| [`org`](/docs/reference/cli/cloud/commands/org.md) | Manage organisations, roles, usage and spend |
| [`instance`](/docs/reference/cli/cloud/commands/instance.md) | Manage instances, capabilities and backups |
| [`team`](/docs/reference/cli/cloud/commands/team.md) | Manage organisation members |
| [`invite`](/docs/reference/cli/cloud/commands/invite.md) | Manage organisation invitations |
| [`token`](/docs/reference/cli/cloud/commands/token.md) | Manage personal access tokens |
| [`catalog`](/docs/reference/cli/cloud/commands/catalog.md) | Browse the platform catalogues |
| [`config`](/docs/reference/cli/cloud/commands/config.md) | Read and write configuration |
| [`context`](/docs/reference/cli/cloud/commands/context.md) | Inspect and switch profiles |
| [`whoami`, `api`, `open`, `status`, `version`, `completion`](/docs/reference/cli/cloud/commands/misc.md) | Identity, the raw API escape hatch, and diagnostics |

## Reference syntax

Usage blocks in this reference use one notation throughout:

| Notation | Meaning |
| --- | --- |
| `<NAME>` | A required value you supply |
| `[NAME]` | An optional value you supply |
| `[OPTIONS]` | Zero or more flags |
| `...` | The preceding item may be repeated |
| `--` | Everything after this separator is passed to another program |

Usage blocks are notation, not commands - they carry brackets a shell will not accept. Copy from the examples instead.

## Resource references

Most commands accept a resource by more than one spelling. An organisation is named by id or name. An instance is named by id, slug, name, or `org/name`. A bare id is used without a lookup; anything else is resolved against the organisation in force, and an unrecognised name is answered with a did-you-mean list.

Omit the reference entirely and, on a terminal, the command offers an interactive picker. In a non-interactive session it exits `2` and lists what was available, so a CI failure names the fix.

```bash
surreal cloud instance get production
surreal cloud instance get acme/production
surreal cloud instance get 67upif0m8sh1cn1p2c8t
```

---

Source: https://surrealdb.com/docs/reference/cli/cloud/authentication

# surreal cloud authentication

How surreal cloud signs in, the difference between a login session and a personal access token, what a token cannot do, and where credentials are stored on disk.

`surreal cloud` holds one credential per profile. There are two kinds, and they differ in what they can *write* rather than in what they can reach.

| Credential | Created by | Suited to |
| --- | --- | --- |
| **Login session** | `surreal cloud auth login` | Interactive use. Can do everything your role allows. |
| **Personal access token** | `surreal cloud token create`, or the dashboard | CI and automation. Read-only on the Cloud surface. |

A login session is an OAuth 2.0 authorisation-code flow with PKCE against `https://auth.surrealdb.com/`. It stores a refresh token and one access token per audience, and renews them as they expire. A personal access token is a long-lived `sdbp_…` string that carries a fixed set of scopes and never expires on a schedule.

## Signing in

```bash
surreal cloud auth login
```

The command tries three flows in order, and stops at the first one that can run.

| Flow | Used when |
| --- | --- |
| Browser loopback | A browser is available and a `127.0.0.1` port can be bound |
| Device code | `--flow device`, or no browser is available |
| Paste | `--flow paste`, or nothing else could run |

Only *mechanical* failures fall through to the next flow - specifically, the loopback listener failing to bind or serve. If you decline the consent screen, or your email address is unverified, the command says so rather than asking again in a different form. An incomplete interactive flow exits `30`.

Naming a flow explicitly means it is used or it fails; there is no silent substitution.

```bash title="Sign in on a machine with no browser"
surreal cloud auth login --flow device
```

The browser flow is skipped automatically when `SSH_CONNECTION` or `SSH_TTY` is set, because a browser at the far end of an SSH connection opens on the wrong machine.

Signing in when the profile already holds a credential is a no-op that exits `0`. Pass `--force` to replace it.

```bash title="Sign in to a second profile"
surreal cloud --profile staging auth login
```

### How the browser flow is hardened

These details matter when a corporate network is in the way:

- The listener binds the literal `127.0.0.1`, never `localhost`, which resolves through `/etc/hosts` and DNS and can answer on `::1`.
- One of four ports is used - `9375`, `9376`, `9377`, `9378` - because the identity provider matches callback URLs exactly.
- Only `/callback` is answered; anything else gets a 404.
- The `state` parameter is compared in constant time, and the flow has a 180-second deadline.
- The device flow gets 600 seconds, because a device code is typed on a phone.

## Personal access tokens

A token is supplied per invocation and is never persisted:

**Bash**

```bash title="From the environment - the usual choice in CI"
export SURREAL_CLOUD_TOKEN=sdbp_...
surreal cloud instance list
```

**PowerShell**

```powershell title="From the environment - the usual choice in CI"
$env:SURREAL_CLOUD_TOKEN = "sdbp_..."
surreal cloud instance list
```

```bash title="From a file, or from stdin"
surreal cloud --token-file /run/secrets/surreal-cloud instance list
cat /run/secrets/surreal-cloud | surreal cloud --token-file - instance list
```

`--token` and `--token-file` are mutually exclusive: a credential comes from a flag or from a file, never both. `--token` deliberately has no short form, because `-t` reads as `--type` on the instance commands, and because a credential belongs in the environment or a file rather than in `argv`, where `ps` and shell history can both see it.

The only way to *store* a token is `auth login --with-token`, which reads it from stdin:

```bash
echo "$SURREAL_CLOUD_TOKEN" | surreal cloud auth login --with-token --label "ci runner"
```

### What a personal access token cannot do

**It cannot manage personal access tokens.** All four verbs - `token list`, `token create`, `token delete` and `token scopes` - are refused locally, before any request is sent, and exit `4`. A leaked token must not be able to mint its own replacements, nor revoke the one an operator would use to clean up after it.

```text
Managing personal access tokens needs an interactive login session, so nothing was sent.

Sign in with:  surreal cloud auth login
```

**It cannot make Cloud writes.** The gateway refuses a token on every non-`GET` Cloud route, so the mutating verbs - `instance create`, `org update` and the rest - answer 403, exit `4`, even when the token carries the matching write scope. Use a login session for those.

**It cannot exceed its scopes.** A missing scope is a 403 naming the scope, relayed rather than predicted.

Everything else works, including `org list`, `instance list`, `instance endpoint`, `instance token`, `auth status`, `auth scopes` and `auth logout`.

### Scopes

The scope vocabulary is shared with the Cloud MCP tools, and the set offered depends on the account. The common ones are:

| Scope | Grants |
| --- | --- |
| `read:cloud` | Read access across the Cloud surface |
| `write:cloud-instances` | Create, update and delete instances |
| `write:cloud-organization` | Change organisation settings and membership |
| `write:cloud-billing` | Change billing details |

Two commands answer two different questions. [`token scopes`](/docs/reference/cli/cloud/commands/token.md#token-scopes) is the catalogue of what *can* be granted; [`auth scopes`](/docs/reference/cli/cloud/commands/auth.md#auth-scopes) is what the credential in hand *carries*.

`auth scopes` on a login session returns an empty list and points you at [`org permissions`](/docs/reference/cli/cloud/commands/org.md#org-permissions), because OAuth identity scopes are not an authorisation model - your role in the organisation is.

## What each `auth` verb does when nothing is signed in

| Verb | Behaviour |
| --- | --- |
| `auth status` | Succeeds, exit `0` |
| `auth logout` | Succeeds, exit `0`, and still emits a document |
| `auth scopes` | Exits `3` |
| `auth refresh` | Exits `3` |

A diagnostic that errors because there is no credential cannot help you fix it, and logging out of nothing is a no-op rather than a failure - a teardown script must not fail on its second run. `scopes` and `refresh` each answer a question *about* a credential, and with none there is no answer to give.

`auth status` and `auth scopes` touch no network at all, so they work on a plane, in a container with no egress, and in a CI job that is about to fail for a different reason. [`whoami`](/docs/reference/cli/cloud/commands/misc.md#whoami) is the command that asks the API who you are.

## Where credentials are stored

One directory holds everything:

| Path | Holds |
| --- | --- |
| `config.toml` | Non-secret configuration: profiles, the persisted organisation |
| `credentials.json` | Tokens, the Cloud user id, and the observed clock skew |
| `credentials.lock` | A zero-byte advisory lock file |

The directory is created mode `0700`, and `credentials.json` mode `0600`. The split between the two files is about **atomicity, not sensitivity**: the few non-secret values that must be replaced in the same transaction as a token live beside it in `credentials.json`.

`config.toml` is safe to commit or to sync into a dotfiles repository. `credentials.json` is not.

### Paths

The directory is resolved in this order:

1. The path given to `--config` (or `SURREAL_CLOUD_CONFIG`): the directory itself, or the directory containing a file
2. `SURREAL_CLOUD_CONFIG_DIR`
3. `cloud` inside the SurrealDB home: `~/.surrealdb/cloud`, or `$SURREAL_CLI_HOME/cloud` when `SURREAL_CLI_HOME` is set to an absolute path

On Windows the default is `%USERPROFILE%\.surrealdb\cloud`.

On its first run, when the default directory does not exist and the one used by the former `surrealctl` does, `surreal cloud` copies `~/.config/surrealctl` (`%APPDATA%\surrealctl` on Windows) into it, stored login included, and prints one note saying so. The original is never moved or removed, so a `surrealctl` still installed keeps working, and from then on the two are independent: signing in or out of one does not change the other. An explicit `--config` or `SURREAL_CLOUD_CONFIG_DIR` is left alone, and an existing new directory is never touched.

```bash title="Print the resolved path"
surreal cloud config path
```

### Permissions are enforced, not warned about

Reading `credentials.json` with any group or other bit set is a hard error:

```text
/Users/ana/.surrealdb/cloud/credentials.json is mode 644 and holds a refresh token.
Fix it with:  chmod 600 /Users/ana/.surrealdb/cloud/credentials.json
```

The file is created with its final mode, so there is no window in which it is readable by anyone else. Windows has no file mode, and the check does not apply there.

### Why a file, and not the OS keyring

This reverses the obvious default deliberately. macOS keychain ACLs are bound to the requesting binary's code signature, so a CLI installed by `cargo install` or Homebrew re-prompts *"surreal-cloud wants to use your confidential information"* after every upgrade - unanswerable inside a CI job. Headless Linux and containers have no Secret Service at all. `gh`, `aws`, `gcloud` and `flyctl` are all file-based for the same reasons.

There is **no keyring backend**. If one is ever added, the rule it has to follow is that selecting it and finding it unavailable fails loudly rather than silently downgrading to the file.

### Concurrent invocations

Refresh-token rotation makes a race genuinely dangerous: a losing racer presents an already-consumed refresh token, and the identity provider's breach detection may then invalidate the whole token family, signing you out everywhere.

So every mutation takes an exclusive advisory lock on `credentials.lock`, re-reads under the lock in case a sibling has already refreshed, performs the network call inside the lock, and writes through a temporary file in the same directory followed by an atomic rename. Readers take no lock at all, because a rename is atomic - a reader sees either the old file or the new one, never a mix.

The lock budget is 30 seconds. Past that:

```text
another surreal-cloud is updating credentials (waited 30s for /Users/ana/.surrealdb/cloud/credentials.lock).
Retry in a moment.
```

### Clock skew

Skew is learned from the `Date` header on every response, successful or not - a 401 caused by a bad clock is exactly the case this fixes - and folded into the next write rather than triggering one. `auth status` warns when the observed skew exceeds five minutes.

Only an `invalid_grant` from the identity provider ever deletes a stored session. Local time is an input to scheduling, never to invalidation, which is what stops a broken clock from becoming a lockout.

## Renewing and signing out

```bash title="Renew now, rather than waiting for expiry"
surreal cloud auth refresh
```

`auth refresh` forces both the access token and the Cloud session. `--all` additionally renews the token used for the account-management routes. On a personal access token it reports that a token does not expire on a schedule and cannot be refreshed, then exits `0` without sending anything.

```bash title="Remove the stored credential for this profile"
surreal cloud auth logout
```

`auth logout` revokes the refresh token at the identity provider on a best-effort basis and removes only this profile's entry. A failed revocation never blocks the local wipe, but does produce a warning:

```text
warning: The refresh token could not be revoked at the identity provider. It was removed from this
machine, but may still be usable elsewhere.
```

## What is sent on the wire

Two headers carry the credential, and they are attached in one place in the client rather than by individual commands:

| Header | Value |
| --- | --- |
| `Authorization` | `Bearer <access token or sdbp_… token>` |
| `X-Cloud-Token` | The bare Cloud session JWT, with no `Bearer ` prefix - added only for routes that need it |

Every request also carries `X-Request-Id` (a client-minted identifier, echoed back and reported in error output), `X-Client-Name`, `X-Client-Version` and a `User-Agent` naming the version and build target.

`--debug` logs every request and response to stderr with the credential reduced to a digest - `Authorization: Bearer <sha256:8f3a91c4>` - so a debug transcript can be pasted into an issue.

## Advanced environment variables

Three variables exist for support and for non-production tenants. They are absent from `--help` on purpose.

| Variable | Purpose |
| --- | --- |
| `SURREAL_CLOUD_SESSION_TOKEN` | Supply an already-minted Cloud session token, bypassing sign-in. For debugging and support reproduction only: the session lives about an hour, so it is no use as a CI credential. `auth status` reports when it is set. |
| `SURREAL_CLOUD_AUTH_CLIENT_ID` | Override the OAuth client id, for a non-production tenant |
| `SURREAL_CLOUD_AUTH_ISSUER` | Override the OAuth issuer, for a non-production tenant |

Use a [personal access token](#personal-access-tokens) for automation. It is the only credential designed to be handed to a machine.

## Related pages

- [`auth` commands](/docs/reference/cli/cloud/commands/auth.md) - every flag on every verb
- [`token` commands](/docs/reference/cli/cloud/commands/token.md) - creating and revoking tokens
- [Global flags](/docs/reference/cli/cloud/global-flags.md) - `--token`, `--token-file`, `--profile`
- [surreal cloud reference](/docs/reference/cli/cloud.md) - the rest of the reference
- [Database CLI](/docs/reference/cli/db.md) - database credentials, which are a separate concern from these

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands

# surreal cloud commands

A map of every surreal cloud command group and leaf command, with links to the page documenting each one.

`surreal cloud` groups its commands by noun. A group on its own prints its own help; the leaf commands below do the work. Every command accepts the [global flags](/docs/reference/cli/cloud/global-flags.md), and every command except [`token create`](/docs/reference/cli/cloud/commands/token.md#token-create) accepts `--json`.

| Group | Leaf commands |
| --- | --- |
| [`auth`](/docs/reference/cli/cloud/commands/auth.md) | `login`, `logout`, `status`, `refresh`, `scopes` |
| [`org`](/docs/reference/cli/cloud/commands/org.md) | `list`, `get`, `create`, `update`, `archive`, `use`, `roles`, `permissions`, `usage`, `spend`, `plans` |
| [`instance`](/docs/reference/cli/cloud/commands/instance.md) | `list`, `get`, `create`, `update`, `delete`, `pause`, `resume`, `watch`, `status`, `endpoint`, `token`, `jwks`, `sql`, `import`, `export`, `metrics`, `logs`, `usage`, `estimate`, `capabilities get`, `capabilities set`, `backup list`, `backup create`, `backup policy get`, `backup policy set` |
| [`team`](/docs/reference/cli/cloud/commands/team.md) | `list`, `get`, `invite`, `update`, `remove` |
| [`invite`](/docs/reference/cli/cloud/commands/invite.md) | `list`, `create`, `delete` |
| [`token`](/docs/reference/cli/cloud/commands/token.md) | `list`, `create`, `delete`, `scopes` |
| [`catalog`](/docs/reference/cli/cloud/commands/catalog.md) | `regions`, `instance-types`, `storage-types`, `instance-versions`, `billing-countries` |
| [`config`](/docs/reference/cli/cloud/commands/config.md) | `list`, `get`, `set`, `unset`, `path`, `edit` |
| [`context`](/docs/reference/cli/cloud/commands/context.md) | `show`, `use`, `list` |
| [Other commands](/docs/reference/cli/cloud/commands/misc.md) | `whoami`, `api`, `open`, `status`, `version`, `completion` |

## Aliases

Plural spellings of the group nouns work, and so do two verb aliases. Nothing else is aliased.

| Alias | Means |
| --- | --- |
| `orgs`, `instances`, `teams`, `invites`, `tokens`, `contexts` | The singular group |
| `ls` | `list` |
| `rm` | `delete` - and `team remove` |
| `doctor` | `status` |

## Reading these pages

Each leaf command gets a usage block, a table of its arguments, a table of its own options, at least one runnable example, and a note on how it refuses.

Usage blocks are notation, not commands: `<NAME>` is a value you must supply, `[NAME]` one you may, `[OPTIONS]` any number of flags, and `...` marks something repeatable. Copy from the examples instead.

Flags shared across many commands are documented once:

- The [global flags](/docs/reference/cli/cloud/global-flags.md), accepted everywhere.
- The [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags) - `--columns`, `--wide`, `--no-header`, `--sort`, `--reverse` and `--limit` - carried by every list-shaped command.
- The [wait flags](/docs/reference/cli/cloud/long-running-operations.md#the-wait-flags) - `--wait`, `--no-wait` and `--wait-timeout` - carried by five `instance` commands.

For queries, imports, exports and running a server, use the [database commands](/docs/reference/cli/db.md) (`surreal db ...`) instead. See the [overview](/docs/reference/cli/cloud.md) for where that boundary sits.

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/auth

# surreal cloud auth

Reference for surreal cloud auth - signing in, signing out, inspecting the stored credential, renewing it, and listing what it is permitted to do.

`surreal cloud auth` signs in, signs out, and inspects credentials. Four of its five verbs work entirely from the local credential store; only `login`, `refresh` and `auth status --verify` touch the network.

<Synopsis>
surreal cloud auth <COMMAND> [OPTIONS]
</Synopsis>

| Verb | Purpose |
| --- | --- |
| [`login`](#auth-login) | Sign in to SurrealDB Cloud |
| [`logout`](#auth-logout) | Remove the stored credentials for a profile |
| [`status`](#auth-status) | Show which credentials are in use |
| [`refresh`](#auth-refresh) | Renew the stored credentials now |
| [`scopes`](#auth-scopes) | Show what the current credential is permitted to do |

Behaviour when nothing is signed in is deliberate and differs by verb: `status` and `logout` succeed with exit `0`, while `scopes` and `refresh` exit `3`. See [Authentication](/docs/reference/cli/cloud/authentication.md#what-each-auth-verb-does-when-nothing-is-signed-in) for why.

## surreal cloud auth login {#auth-login}

Sign in to SurrealDB Cloud, storing a credential for the active profile.

<Synopsis>
surreal cloud auth login [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--with-token",
            "description": "Read a personal access token from standard input instead of signing in. This is the only way to store a token."
        },
        {
            "name": "--label",
            "value": "<NAME>",
            "description": "A label to remember this personal access token by. Requires `--with-token`."
        },
        {
            "name": "--flow",
            "value": "<FLOW>",
            "description": "Which sign-in flow to use: `browser`, `device` or `paste`. Conflicts with `--with-token`."
        },
        {
            "name": "--no-browser",
            "description": "Do not open a browser; show a code or a URL instead. Conflicts with `--with-token` and `--flow`."
        },
        {
            "name": "--force",
            "description": "Sign in again even if this profile already has a credential."
        }
    ]}
/>

| `--flow` value | Behaviour |
| --- | --- |
| `browser` | Open a browser and listen on `127.0.0.1` for the redirect |
| `device` | Show a code to type on another device, following RFC 8628 |
| `paste` | Print a URL and take back the address the browser was redirected to |

Left to itself the command tries browser, then device, then paste, and stops at the first one that can run. A flow named explicitly is used or it fails; there is no silent substitution.

```bash title="Sign in interactively"
surreal cloud auth login
```

```bash title="Sign in over SSH, where a local browser is no use"
surreal cloud auth login --flow device
```

```bash title="Store a personal access token for a profile"
echo "$CI_TOKEN" | surreal cloud --profile ci auth login --with-token --label "build runner"
```

**Refusals and short-circuits.** A profile that already holds a credential is a no-op that exits `0` and names the remedy:

```text
Profile `default` is already signed in as ana@acme.example.
Replace it with:
  surreal cloud auth login --force
```

An interactive flow that does not complete exits `30`. A session with nobody present and no way to prompt cannot start a flow at all, and says so rather than printing a device code nobody will read.

Only `--with-token` stores a credential. `--token`, `--token-file` and `SURREAL_CLOUD_TOKEN` are request-scoped and never written to disk.

## surreal cloud auth logout {#auth-logout}

Remove the stored credentials for a profile.

<Synopsis>
surreal cloud auth logout [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Do not ask for confirmation."
        }
    ]}
/>

```bash title="Sign out of the active profile"
surreal cloud auth logout
```

```bash title="Tear down a profile in a script"
surreal cloud --profile ci auth logout --force --json
```

Logging out revokes the refresh token at the identity provider on a best-effort basis and removes only this profile's entry. A failed revocation warns but never blocks the local wipe.

Signing out when nothing is signed in still succeeds and still emits a document, so `auth logout --json` in a teardown script has the same shape on its second run as on its first:

```json title="Output"
{
  "profile": "ci",
  "removed": false,
  "revoked": false
}
```

Declining the confirmation prints `Nothing was changed.` and exits `0`.

## surreal cloud auth status {#auth-status}

Show which credentials are in use, without touching the network.

<Synopsis>
surreal cloud auth status [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--verify",
            "description": "Also check the credential against the API. Adds one request."
        }
    ]}
/>

```bash title="What am I signed in as?"
surreal cloud auth status
```

```bash title="Confirm the credential still works"
surreal cloud auth status --verify
```

The document has the same shape signed in and signed out, so a script can read one field without branching first. `status` warns when the machine's clock is more than five minutes out, and when `SURREAL_CLOUD_SESSION_TOKEN` is supplying a session token.

For the API's own view of your identity, use [`whoami`](/docs/reference/cli/cloud/commands/misc.md#whoami), which spends a request.

## surreal cloud auth refresh {#auth-refresh}

Renew the stored credentials now, rather than waiting for them to expire.

<Synopsis>
surreal cloud auth refresh [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--all",
            "description": "Also renew the token for the account-management routes."
        }
    ]}
/>

```bash
surreal cloud auth refresh
```

The command forces both the access token and the Cloud session. `--all` additionally renews the token used for the account-management routes, which is the one the [`token` commands](/docs/reference/cli/cloud/commands/token.md) need.

**Refusals.** With nothing signed in, this exits `3`. On a personal access token it reports that a token does not expire on a schedule and cannot be refreshed, then exits `0` without sending anything.

## surreal cloud auth scopes {#auth-scopes}

Show what the current credential is permitted to do.

<Synopsis>
surreal cloud auth scopes [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--no-header",
            "description": "Omit the header row."
        }
    ]}
/>

> [!NOTE]
> This is the one list-shaped command that carries only `--no-header`. It has no `--columns`, `--wide`, `--sort`, `--reverse` or `--limit`.

```bash title="What may this token do?"
surreal cloud auth scopes
```

```text title="Output"
SCOPE
read:cloud
write:cloud-instances
```

The single column is `scope`.

**Refusals.** With nothing signed in, this exits `3` - a script asking "what may this credential do" and getting an empty list would read it as "nothing is permitted" rather than "nothing is signed in".

On a login session the list is empty, and a sentence on stderr points at [`org permissions`](/docs/reference/cli/cloud/commands/org.md#org-permissions): identity scopes are not an authorisation model, and your role in the organisation is what decides what you may do.

For the catalogue of scopes a token *can* be granted, use [`token scopes`](/docs/reference/cli/cloud/commands/token.md#token-scopes).

## Related pages

- [Authentication](/docs/reference/cli/cloud/authentication.md) - the credential model, login flows and credential storage
- [`token` commands](/docs/reference/cli/cloud/commands/token.md) - creating and revoking personal access tokens
- [`context` commands](/docs/reference/cli/cloud/commands/context.md) - switching between profiles
- [surreal cloud reference](/docs/reference/cli/cloud.md) - the rest of the reference
- [Database authentication](/docs/reference/cli/db.md) - database-level authentication, which these credentials do not cover

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/catalog

# surreal cloud catalog

Reference for surreal cloud catalog - the platform-wide lists of regions, instance types, storage types, SurrealDB versions and billing countries.

`surreal cloud catalog` browses the platform catalogues: the regions, instance types, versions and billing countries SurrealDB Cloud offers. These are the slugs [`instance create`](/docs/reference/cli/cloud/commands/instance.md#instance-create) and [`instance update`](/docs/reference/cli/cloud/commands/instance.md#instance-update) expect.

<Synopsis>
surreal cloud catalog <COMMAND> [OPTIONS]
</Synopsis>

| Command | Purpose |
| --- | --- |
| [`regions`](#catalog-regions) | List the regions instances can be deployed in |
| [`instance-types`](#catalog-instance-types) | List the instance types available on the platform |
| [`storage-types`](#catalog-storage-types) | List the storage instance types available on the platform |
| [`instance-versions`](#catalog-instance-versions) | List the SurrealDB versions instances can run |
| [`billing-countries`](#catalog-billing-countries) | List the countries billing details can be registered in |

These are nouns rather than `list` verbs, because a catalogue has exactly one thing you can do with it. None takes a positional argument, none takes `--org`, and none carries any flag beyond the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags).

> [!IMPORTANT]
> These lists are **global**. What an organisation may actually deploy is narrower, and [`org plans`](/docs/reference/cli/cloud/commands/org.md#org-plans) is the organisation-scoped answer - the one to trust before a create.

## surreal cloud catalog regions {#catalog-regions}

List the regions instances can be deployed in.

<Synopsis>
surreal cloud catalog regions [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own. Column ids are `slug` and `description`, with `flag` under `--wide`.

```bash
surreal cloud catalog regions
```

```text title="Output"
SLUG        DESCRIPTION
aws-euw1    Europe (Ireland)
aws-use1    US East (N. Virginia)
aws-usw2    US West (Oregon)
```

```bash title="Just the slugs"
surreal cloud catalog regions --columns slug --no-header
```

The `slug` is what `--region` accepts.

## surreal cloud catalog instance-types {#catalog-instance-types}

List the instance types available on the platform.

<Synopsis>
surreal cloud catalog instance-types [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own. Column ids are `slug`, `category`, `cpu`, `memory`, `units`, `price_hour`, `storage` and `enabled`, with `display_name`, `restricted` and `description` under `--wide`.

```bash
surreal cloud catalog instance-types
```

```bash title="Cheapest first"
surreal cloud catalog instance-types --sort price_hour
```

The `slug` is what `--type` accepts. Prices stay in integer minor units under `--json`.

The `enabled` column is left unset here, because whether a type is available to you is a property of your organisation's plan rather than of the platform - check [`org plans`](/docs/reference/cli/cloud/commands/org.md#org-plans), or price a specific combination with [`instance estimate`](/docs/reference/cli/cloud/commands/instance.md#instance-estimate).

## surreal cloud catalog storage-types {#catalog-storage-types}

List the storage instance types available on the platform.

<Synopsis>
surreal cloud catalog storage-types [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own. The columns are the same as [`instance-types`](#catalog-instance-types).

```bash
surreal cloud catalog storage-types
```

An empty list is a success, and says why on stderr:

```text
No storage instance types are offered. Distributed storage may not be available here.
```

## surreal cloud catalog instance-versions {#catalog-instance-versions}

List the SurrealDB versions instances can run.

<Synopsis>
surreal cloud catalog instance-versions [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own. The single column id is `version`.

```bash
surreal cloud catalog instance-versions
```

```bash title="The version an update would move to"
surreal cloud catalog instance-versions --limit 1 --no-header
```

The API's own order is newest-first and is left alone. `--sort version` sorts as strings, so `3.10.0` would come before `3.9.0` - prefer the default order when you want the newest release.

An empty list should not happen, and says so:

```text
The API returned no versions, which should not happen. Try again, or report it.
```

## surreal cloud catalog billing-countries {#catalog-billing-countries}

List the countries billing details can be registered in.

<Synopsis>
surreal cloud catalog billing-countries [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own. Column ids are `code` and `name`.

```bash
surreal cloud catalog billing-countries
```

```bash title="Check one country is supported"
surreal cloud catalog billing-countries --json | jq -e '.[] | select(.code == "IE")' > /dev/null
```

## Related pages

- [`instance create`](/docs/reference/cli/cloud/commands/instance.md#instance-create) - where these slugs are used
- [`org plans`](/docs/reference/cli/cloud/commands/org.md#org-plans) - the organisation-scoped subset
- [`instance estimate`](/docs/reference/cli/cloud/commands/instance.md#instance-estimate) - pricing a type and region before committing to it
- [surreal cloud reference](/docs/reference/cli/cloud.md) - the rest of the reference
- [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/config

# surreal cloud config

Reference for surreal cloud config - listing, reading, setting and clearing configuration values, printing the file path, and opening it in an editor.

`surreal cloud config` reads and writes the CLI's own configuration file. Nothing here sends a request: the file never leaves the machine.

<Synopsis>
surreal cloud config <COMMAND> [OPTIONS]
</Synopsis>

| Verb | Purpose | Alias |
| --- | --- | --- |
| [`list`](#config-list) | List every configuration key and its value | `ls` |
| [`get`](#config-get) | Show one configuration value | |
| [`set`](#config-set) | Set a configuration value | |
| [`unset`](#config-unset) | Clear a configuration value | |
| [`path`](#config-path) | Print the path to the configuration file | |
| [`edit`](#config-edit) | Open the configuration file in your editor | |

Every verb works on **one profile** - whichever `--profile`, `SURREAL_CLOUD_PROFILE` and the file's own `active_profile` settle on - and each one says which profile it touched.

## The keys

Four keys are recognised. Each is outranked by an environment variable, and by a command-line flag above that; see the [precedence chain](/docs/reference/cli/cloud/global-flags.md#the-precedence-chain).

| Key | Type | Description | Outranked by |
| --- | --- | --- | --- |
| `org` | Text | Default organisation for this profile | `SURREAL_CLOUD_ORG` |
| `api` | URL | Base URL of the SurrealDB API | `SURREAL_CLOUD_API` |
| `json` | Boolean | Always emit machine-readable JSON | `SURREAL_CLOUD_JSON` |
| `plain` | Boolean | Always disable tables, spinners and relative times | `SURREAL_CLOUD_PLAIN` |

The persisted context - what [`org use`](/docs/reference/cli/cloud/commands/org.md#org-use) records - is **not** reachable from here. There is no `config set context.org`. The two live in separate tables of the same file so that `org use` never overwrites an `org` value you set by hand, and the hand-written one wins.

```toml title="config.toml"
active_profile = "work"

[profile.work]
org = "acme"          # written by a person

[profile.work.context]
org = "67upif0m8sh1cn1p2c8t"   # written by `org use`
```

Keys this build has never heard of are preserved on write, so an older binary editing a newer file does not discard anything.

## surreal cloud config list {#config-list}

List every configuration key and its value.

<Synopsis>
surreal cloud config list [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `key`, `value`, `set` and `description`, with `kind` under `--wide`.

```bash
surreal cloud config list
```

```text title="Output"
KEY              VALUE   SET  DESCRIPTION
org              acme    yes  Default organization for this profile
api                      no   Base URL of the SurrealDB API
json                     no   Always emit machine-readable JSON
plain                    no   Always disable tables, spinners and relative times
```

Every key is listed whether or not it is set, and the `set` column is what distinguishes a configured value from a default. Only a key this profile has actually set carries a value, so an unset key - `api` above, which falls back to the built-in default - has an empty `value` cell. A note on stderr reports the profile and the file path.

## surreal cloud config get {#config-get}

Show one configuration value.

<Synopsis>
surreal cloud config get [OPTIONS] <KEY>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<KEY>",
            "required": true,
            "description": "The configuration key."
        }
    ]}
/>

This command has no options of its own.

The output is the value alone plus a newline, with no label and no styling, so `$(…)` captures exactly the value. An unset key writes **zero bytes**, so `[ -z "$(surreal cloud config get org)" ]` and `wc -l` agree with each other.

```bash
ORG=$(surreal cloud config get org)
```

```bash title="Branch on whether a key is set"
if [ -z "$(surreal cloud config get org)" ]; then
    echo "no default organisation for this profile"
fi
```

**Refusals.** An unrecognised key is a usage error, exit `2`, and lists the keys that exist.

## surreal cloud config set {#config-set}

Set a configuration value.

<Synopsis>
surreal cloud config set [OPTIONS] <KEY> <VALUE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<KEY>",
            "required": true,
            "description": "The configuration key."
        },
        {
            "name": "<VALUE>",
            "required": true,
            "description": "The value to store."
        }
    ]}
/>

This command has no options of its own.

```bash title="Always emit JSON from this profile"
surreal cloud config set json true
```

```bash title="Point at a different API"
surreal cloud config set api https://api.example.com
```

```bash title="Configure a second profile"
surreal cloud --profile staging config set org contoso
```

**Warnings.** When the corresponding environment variable is exported, the command says so, because the variable silently outranks what you just wrote:

```text
warning: SURREAL_CLOUD_JSON is set and takes precedence over this. Unset it for the configured value to apply.
```

**Refusals**, exit `2`: an unrecognised key, or a value the key's type rejects - a non-boolean for `json`, or a URL with a path for `api`.

## surreal cloud config unset {#config-unset}

Clear a configuration value.

<Synopsis>
surreal cloud config unset [OPTIONS] <KEY>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<KEY>",
            "required": true,
            "description": "The configuration key."
        }
    ]}
/>

This command has no options of its own.

```bash
surreal cloud config unset json
```

Clearing a key restores the CLI's default rather than falling back to another profile. Clearing an already-unset key is a no-op that succeeds.

## surreal cloud config path {#config-path}

Print the path to the configuration file.

<Synopsis>
surreal cloud config path [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

The output is the path alone plus a newline.

```bash
surreal cloud config path
```

```text title="Output"
/Users/ana/.surrealdb/cloud/config.toml
```

```bash title="Back the file up before editing it"
cp "$(surreal cloud config path)" "$(surreal cloud config path).bak"
```

`credentials.json` lives beside it in the same directory. See [credential storage](/docs/reference/cli/cloud/authentication.md#where-credentials-are-stored) for how that directory is chosen.

## surreal cloud config edit {#config-edit}

Open the configuration file in your editor.

<Synopsis>
surreal cloud config edit [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

```bash
surreal cloud config edit
```

The file is opened with `$VISUAL`, or `$EDITOR` when that is unset. A value such as `code --wait` works, because it is split on whitespace. The file is created first if it does not exist yet, and re-read when the editor exits, so a mistake is reported while the editor is still fresh in mind: `<path> is valid, with N profiles.` on success, and the parse error on failure. A broken file is left in place for you to fix.

With no terminal there is nothing to attach an editor to, so in a non-interactive session the command exits `2` rather than hanging; use [`config set`](#config-set) instead. It also exits `2` when neither variable is set.

Editing the file by hand is supported - [`config list`](#config-list) will show whatever you wrote, and unknown keys survive - but a file written by a newer `surreal cloud` is refused rather than migrated:

```text
<path> was written by a newer surreal-cloud (file version 2, this build understands 1).
Refusing to touch it - upgrade surreal-cloud, or point --config elsewhere.
```

## Related pages

- [Global flags](/docs/reference/cli/cloud/global-flags.md) - the flags and environment variables these keys sit beneath
- [`context` commands](/docs/reference/cli/cloud/commands/context.md) - switching between profiles
- [`org use`](/docs/reference/cli/cloud/commands/org.md#org-use) - remembering an organisation within a profile
- [Authentication](/docs/reference/cli/cloud/authentication.md#where-credentials-are-stored) - the credential file beside this one
- [surreal cloud reference](/docs/reference/cli/cloud.md) - the rest of the reference

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/context

# surreal cloud context

Reference for surreal cloud context - showing what an invocation resolved, switching between profiles, and listing the profiles that are configured.

`surreal cloud context` inspects and switches profiles. A profile bundles a credential, an API base URL and a set of configuration values, so switching profile changes all three in one move.

<Synopsis>
surreal cloud context <COMMAND> [OPTIONS]
surreal cloud contexts <COMMAND> [OPTIONS]
</Synopsis>

| Verb | Purpose | Alias |
| --- | --- | --- |
| [`show`](#context-show) | Show the context this invocation resolved | |
| [`use`](#context-use) | Switch to a different profile | |
| [`list`](#context-list) | List the configured profiles | `ls` |

`context use` switches *profile*. [`org use`](/docs/reference/cli/cloud/commands/org.md#org-use) remembers an organisation *within* a profile. Neither is expressible as the other, and you will usually want both: one profile per account or tenant, one remembered organisation inside each.

## surreal cloud context show {#context-show}

Show the context this invocation resolved.

<Synopsis>
surreal cloud context show [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

```bash
surreal cloud context show
```

```text title="Output"
Profile         work
Organization    acme
Id              67upif0m8sh1cn1p2c8t
Source          persisted context (`org use`)
API             https://api.surrealdb.com
Config          /Users/ana/.surrealdb/cloud/config.toml
Store           /Users/ana/.surrealdb/cloud/credentials.json
```

The command resolves through the **same chain every other command uses**, including the interactive picker when several organisations are available and nothing has chosen - so what it reports is exactly what the next command will do.

A failure to resolve an organisation is reported as *part of the answer* rather than as an error: the profile, the API base and both file paths are still printed, followed by a note and the hint `surreal cloud org use <name>`.

```bash title="Which organisation would this command hit?"
surreal cloud context show --json | jq -r '.organization.name + " via " + .source'
```

## surreal cloud context use {#context-use}

Switch to a different profile.

<Synopsis>
surreal cloud context use [OPTIONS] <PROFILE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<PROFILE>",
            "required": true,
            "description": "The profile to make active."
        }
    ]}
/>

This command has no options of its own.

```bash title="Switch"
surreal cloud context use staging
```

```bash title="Create a profile and sign into it"
surreal cloud context use staging
surreal cloud auth login
```

The profile is created if it does not exist. A **new** profile gets a note that it has no stored credentials, plus the hint naming the login command for it.

**Refusals.** An empty or whitespace-only name is a usage error, exit `2`:

```text
Ensure the profile name is not empty.
```

**Warnings.** The command warns when `SURREAL_CLOUD_PROFILE` is set, because the variable outranks the file it just wrote.

For a single command against a different profile, pass `--profile` rather than switching:

```bash
surreal cloud --profile staging instance list
```

## surreal cloud context list {#context-list}

List the configured profiles.

<Synopsis>
surreal cloud context list [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `profile`, `active`, `org` and `api`, with `source` under `--wide`.

```bash
surreal cloud context list
```

```text title="Output"
PROFILE   ACTIVE  ORGANIZATION  API
default   no      contoso       https://api.surrealdb.com
work      yes     acme          https://api.surrealdb.com
```

```bash title="See where each organisation came from"
surreal cloud context list --wide
```

The profile in force is always listed, even when the file has no entry for it - so `--profile scratch context list` shows `scratch`. The order is stable between runs.

## Related pages

- [Global flags](/docs/reference/cli/cloud/global-flags.md#the-precedence-chain) - the chain `context show` reports on
- [`config` commands](/docs/reference/cli/cloud/commands/config.md) - the values a profile holds
- [`org use`](/docs/reference/cli/cloud/commands/org.md#org-use) - remembering an organisation within a profile
- [`auth` commands](/docs/reference/cli/cloud/commands/auth.md) - signing a profile in
- [surreal cloud reference](/docs/reference/cli/cloud.md) - the rest of the reference

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/instance

# surreal cloud instance

Reference for surreal cloud instance - create, update, pause, resume, delete and watch instances, read status, endpoint, logs, metrics and usage, mint database tokens, open sql, import and export through surreal db, and manage capabilities and backups.

`surreal cloud instance` manages instances: the databases themselves. It is the largest group in the CLI, and covers provisioning, scaling, observability, credentials, capabilities and backups. Three of its commands, `sql`, `import` and `export`, run the database through the `surreal` launcher as [`surreal db sql`](/docs/reference/cli/db/commands/sql.md), [`surreal db import`](/docs/reference/cli/db/commands/import.md) and [`surreal db export`](/docs/reference/cli/db/commands/export.md).

This group declares the scopes `read:cloud` and `write:cloud-instances`. A personal access token is read-only here: the gateway refuses every non-GET Cloud route for one, so the commands that change an instance answer 403 and exit `4` regardless of the token's scopes. See [Authentication](/docs/reference/cli/cloud/authentication.md#what-a-personal-access-token-cannot-do).

<Synopsis>
surreal cloud instance <COMMAND> [OPTIONS]
surreal cloud instances <COMMAND> [OPTIONS]
</Synopsis>

| Verb | Purpose | Alias |
| --- | --- | --- |
| [`list`](#instance-list) | List the instances in an organisation | `ls` |
| [`get`](#instance-get) | Show one instance | |
| [`create`](#instance-create) | Create an instance | |
| [`update`](#instance-update) | Change an instance's type, size, or version | |
| [`delete`](#instance-delete) | Delete an instance and everything in it | `rm` |
| [`pause`](#instance-pause) | Pause a running instance | |
| [`resume`](#instance-resume) | Resume a paused instance | |
| [`watch`](#instance-watch) | Watch an operation that was started elsewhere | |
| [`status`](#instance-status) | Show an instance's deployment phase and restorable snapshots | |
| [`endpoint`](#instance-endpoint) | Print the endpoint a client connects to | |
| [`token`](#instance-token) | Mint a database token for an instance | |
| [`jwks`](#instance-jwks) | Fetch the key set that verifies an instance's tokens | |
| [`sql`](#instance-sql) | Open a SurrealQL session against an instance | |
| [`import`](#instance-import) | Import a file into an instance | |
| [`export`](#instance-export) | Export an instance to a file | |
| [`metrics`](#instance-metrics) | Show an instance's resource metrics | |
| [`logs`](#instance-logs) | Show an instance's logs | |
| [`usage`](#instance-usage) | Show what an instance has consumed | |
| [`estimate`](#instance-estimate) | Estimate what an instance would cost | |
| [`capabilities get`](#instance-capabilities-get) | Show an instance's capability configuration | |
| [`capabilities set`](#instance-capabilities-set) | Change an instance's capability configuration | |
| [`backup list`](#instance-backup-list) | List the snapshots an instance can be restored from | `ls` |
| [`backup create`](#instance-backup-create) | Take a backup of an instance now | |
| [`backup policy get`](#instance-backup-policy-get) | Show the backup retention policy | |
| [`backup policy set`](#instance-backup-policy-set) | Change the backup retention policy | |

## Naming an instance

Four spellings are accepted: the id, the slug, the name, or `org/name`. Only a bare id avoids a lookup against the organisation. A slug always contains a hyphen, so it can never be mistaken for an id.

```bash
surreal cloud instance get production
surreal cloud instance get production-6xk2
surreal cloud instance get acme/production
surreal cloud instance get 67upif0m8sh1cn1p2c8t
```

Omit the reference entirely and, on a terminal, you get a picker. Elsewhere it is a usage error, exit `2`, listing what was available:

```text
No instance was given and this session cannot prompt.
Name one, or pass --org to change which organization is searched.

Available:
  production
  staging
```

There is deliberately no `instance use`, and no default instance: a sticky implicit target is fine for a noun whose verbs are mostly reads, and wrong for one whose verbs include `delete`. The organisation searched comes from `--org` and the [precedence chain](/docs/reference/cli/cloud/global-flags.md#the-precedence-chain), and [`org use`](/docs/reference/cli/cloud/commands/org.md#org-use) can remember one.

Every `[INSTANCE]` argument below behaves this way: only the organisation is resolved from context, so without an instance argument a command prompts on a terminal and refuses elsewhere.

A command that waits for the instance (`create`, `update`, `delete`, `pause`, `resume` and `watch`) follows the rules in [Long-running operations](/docs/reference/cli/cloud/long-running-operations.md). The waiting default is on, the timeout is 15 minutes, a timeout exits `10`, and an instance that enters a failed state exits `6`. `--wait-timeout` takes a duration such as `30s`, `10m` or `2h`, and a bare number is seconds.

## surreal cloud instance list {#instance-list}

List the instances in an organisation.

<Synopsis>
surreal cloud instance list [OPTIONS]
</Synopsis>

This command takes no positional argument, deliberately: a positional here would read as an *instance*. The organisation comes from `--org` and the [precedence chain](/docs/reference/cli/cloud/global-flags.md#the-precedence-chain).

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags) and nothing else. Column ids are `name`, `state`, `type`, `version`, `region`, `compute_units` and `storage`, with `id`, `slug`, `host`, `access_type` and `organization_id` under `--wide`. An organisation with no instances is a success: exit `0`, a note, and an empty array under `--json`.

```bash
surreal cloud instance list
```

```text title="Output"
NAME        STATE   TYPE          VERSION  REGION     UNITS  STORAGE
production  ready   production-2  3.2.4    aws-euw1       4   100 GB
staging     paused  shared-1      3.2.4    aws-euw1       1    10 GB
```

```bash title="Ready instances only, as names"
surreal cloud instance list --json | jq -r '.[] | select(.state == "ready") | .name'
```

## surreal cloud instance get {#instance-get}

Show one instance.

<Synopsis>
surreal cloud instance get [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

This command has no options of its own.

```bash
surreal cloud instance get production
```

```bash title="One field, for a script"
surreal cloud instance get production --json | jq -r .state
```

The detail view reports the id, slug, state, type, version, region, compute units, storage, access type, endpoint, private host when there is one, and the owning organisation. It adds the storage cool-off when storage cannot be changed yet, the storage backend and backup retention when the platform reports them, and the versions the instance can upgrade to.

## surreal cloud instance create {#instance-create}

Create an instance.

<Synopsis>
surreal cloud instance create [OPTIONS] --type <SLUG> --region <SLUG> <NAME>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<NAME>",
            "required": true,
            "description": "A name for the instance. Between 1 and 30 characters, counted after trimming."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--type",
            "value": "<SLUG>",
            "required": true,
            "description": "The instance type, by slug. See `surreal cloud catalog instance-types`. Must be one the organisation's plan offers."
        },
        {
            "name": "--region",
            "value": "<SLUG>",
            "required": true,
            "description": "The region to deploy into, by slug. See `surreal cloud catalog regions`. Must be one the organisation's plan offers."
        },
        {
            "name": "--version",
            "value": "<VERSION>",
            "description": "The SurrealDB version to run. Defaults to the platform's current one."
        },
        {
            "name": "--compute-units",
            "value": "<N>",
            "description": "How many compute units to run, for the types that scale. Minimum 1."
        },
        {
            "name": "--storage",
            "value": "<GB>",
            "description": "Storage to allocate, in gigabytes. Minimum 1."
        },
        {
            "name": "--access-type",
            "value": "<ACCESS>",
            "description": "How the instance may be reached: `public`, `private` or `dual`."
        },
        {
            "name": "--restore-from",
            "value": "<INSTANCE/SNAPSHOT>",
            "description": "Restore from a snapshot, as `<instance>/<snapshot>`."
        },
        {
            "name": "--wait",
            "description": "Wait for the instance to become ready. On by default."
        },
        {
            "name": "--no-wait",
            "description": "Return as soon as the API accepts the request."
        },
        {
            "name": "--wait-timeout",
            "value": "<DURATION>",
            "default": "15m",
            "description": "How long to wait before giving up. A duration such as `30s`, `10m` or `2h`."
        }
    ]}
/>

| `--access-type` value | Meaning |
| --- | --- |
| `public` | Reachable over the public internet. The API's own default |
| `private` | Reachable over PrivateLink only. Needs PrivateLink on the organisation |
| `dual` | Reachable both ways |

```bash title="Create and wait for it to be ready"
surreal cloud instance create api --type shared-1 --region aws-euw1
```

```bash title="A production instance with room to grow"
surreal cloud instance create production \
    --type production-2 \
    --region aws-euw1 \
    --compute-units 4 \
    --storage 100
```

```bash title="Clone from a snapshot"
surreal cloud instance create staging \
    --type shared-1 \
    --region aws-euw1 \
    --restore-from production/rsnapshot-20260811-150405
```

The command prints `Creating <name> (<slug>).` on stderr as soon as the API accepts the request. With the wait, it then reports progress and emits a fresh detail view of the ready instance. With `--no-wait` it emits the accepted request and hints at `surreal cloud instance watch <name>`.

The organisation comes from `--org` and the [precedence chain](/docs/reference/cli/cloud/global-flags.md#the-precedence-chain).

**Refusals**, all exit `2` and all before any request:

- A name outside 1 to 30 characters, with the length it counted.
- `--compute-units` below 1 (`Compute units start at 1.`), or `--storage` below 1 (`Storage starts at 1 GB.`).
- A `--type` or `--region` that the organisation's plan does not offer. The message suggests the closest match and lists what is available. This check is skipped when the plan lists none.
- A malformed `--restore-from`, which is split on the last `/`:

```text
`prod-snap-1` is not a snapshot reference. Use <instance>/<snapshot>, for example
production/rsnapshot-20250128-150405.
Run `surreal cloud instance status <instance>` to list the snapshots an instance can be restored from.
```

> [!WARNING]
> The create route accepts no idempotency key, so a retried create is a second instance and a second bill. `surreal cloud` does not retry it after a timeout or a `502`, because the request may already have been processed. It replays only a `429` or `401`, which the gateway answers before any handler runs. If a create times out, run [`instance list`](#instance-list) before running it again.

## surreal cloud instance update {#instance-update}

Change an instance's type, size, or version.

<Synopsis>
surreal cloud instance update [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--type",
            "value": "<SLUG>",
            "description": "Move to this instance type, by slug."
        },
        {
            "name": "--compute-units",
            "value": "<N>",
            "description": "Scale to this many compute units. Minimum 1."
        },
        {
            "name": "--storage",
            "value": "<GB>",
            "description": "Grow storage to this many gigabytes. Minimum 1."
        },
        {
            "name": "--version",
            "value": "<VERSION>",
            "description": "Upgrade to this SurrealDB version."
        },
        {
            "name": "--access-type",
            "value": "<ACCESS>",
            "description": "Change how the instance may be reached: `public`, `private` or `dual`."
        },
        {
            "name": "--wait",
            "description": "Wait for the instance to become ready again. On by default."
        },
        {
            "name": "--no-wait",
            "description": "Return as soon as the API accepts the last change."
        },
        {
            "name": "--wait-timeout",
            "value": "<DURATION>",
            "default": "15m",
            "description": "How long to wait before giving up. A duration such as `30s`, `10m` or `2h`."
        }
    ]}
/>

There is no single update route on the API - each field has its own - so one invocation is several requests. They are applied in a fixed order and stop at the first failure:

1. `--type`
2. `--compute-units`
3. `--storage`
4. `--version`
5. `--access-type`

> [!IMPORTANT]
> Every one of these restarts the instance. A single `instance update` that changes three fields restarts it once per field, in the order above.

```bash title="Scale up"
surreal cloud instance update production --compute-units 8
```

```bash title="Upgrade the SurrealDB version"
surreal cloud instance update production --version 3.2.4
```

```bash title="Move to a larger instance type"
surreal cloud instance update production --type production-4
```

Each change prints `Set the <field> to <value>.` on stderr as it is accepted. The command stops at the first request the API refuses, so earlier changes stay applied. On the platform a change of instance type counts as a storage change, so passing `--type` and `--storage` together can be refused at the storage step after the type has already changed. The [Storage limits on Start](/docs/manage/instances/scaling.md#storage-limits-on-start) page lists the storage each type starts with.

With the wait, the command then waits for the instance to be ready again and emits a fresh detail view. With `--no-wait` it hints at `surreal cloud instance watch <name>` and emits the instance as it stands.

**Refusals**, all exit `2`:

- No flags at all:

```text
Nothing to update. Pass at least one of --type, --compute-units, --storage, --version, or --access-type.
```

- `--compute-units` below 1 (`Compute units start at 1.`), or `--storage` below 1 (`Storage starts at 1 GB.`).
- `--storage` while the storage cool-off is in force, naming the interval and, when known, when storage was last changed:

```text
`production` cannot be resized yet: storage may be changed only once every 6 hours.
It was last changed at 2026-08-11T10:00:00Z.
```

- `--compute-units` outside the current type's range, when `--type` is not also being changed:

```text
`shared-1` accepts 1-2 compute units; 8 is outside that.
```

## surreal cloud instance delete {#instance-delete}

Delete an instance and everything in it.

<Synopsis>
surreal cloud instance delete [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Delete without confirming."
        },
        {
            "name": "--wait",
            "description": "Wait for the instance to disappear. On by default."
        },
        {
            "name": "--no-wait",
            "description": "Return as soon as the API accepts the request."
        },
        {
            "name": "--wait-timeout",
            "value": "<DURATION>",
            "default": "15m",
            "description": "How long to wait before giving up."
        }
    ]}
/>

```bash title="Delete, with a confirmation"
surreal cloud instance delete staging
```

```bash title="Delete from a teardown script"
surreal cloud instance delete staging --force --json
```

The confirmation names the instance, its slug and its region, so the wrong terminal tab is caught before the request. Declining prints `Nothing was deleted.` and exits `0` with no document. In a non-interactive session without `--force` or `--yes`, it exits `2` having sent nothing.

The delete route answers with no body, so the emitted document is synthesised. Its `state` is `deleted` once the wait has seen the instance disappear, and `deleting` under `--no-wait`, which also hints at `surreal cloud instance watch <name> --until deleted`:

```json title="Output"
{
  "id": "67upif0m8sh1cn1p2c8t",
  "name": "staging",
  "slug": "staging-4jd1",
  "state": "deleted"
}
```

While waiting, a `404` is the success condition - there is nothing left to fetch.

## surreal cloud instance pause {#instance-pause}

Pause a running instance.

<Synopsis>
surreal cloud instance pause [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--wait",
            "description": "Wait for the instance to pause. On by default."
        },
        {
            "name": "--no-wait",
            "description": "Return as soon as the API accepts the request."
        },
        {
            "name": "--wait-timeout",
            "value": "<DURATION>",
            "default": "15m",
            "description": "How long to wait before giving up."
        }
    ]}
/>

```bash
surreal cloud instance pause staging
```

With the wait, the command emits a fresh detail view once the instance has paused. With `--no-wait` it emits the accepted response and hints at `surreal cloud instance watch <name> --until paused`.

Pausing something already paused prints a note and still emits a document, exit `0`. The shape of the answer does not change with remote state, so a script does not need to know which case it hit.

## surreal cloud instance resume {#instance-resume}

Resume a paused instance.

<Synopsis>
surreal cloud instance resume [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--wait",
            "description": "Wait for the instance to become ready. On by default."
        },
        {
            "name": "--no-wait",
            "description": "Return as soon as the API accepts the request."
        },
        {
            "name": "--wait-timeout",
            "value": "<DURATION>",
            "default": "15m",
            "description": "How long to wait before giving up."
        }
    ]}
/>

```bash
surreal cloud instance resume staging
```

Resuming something already ready behaves like pausing something already paused: a note, a document, exit `0`. Resuming is usually slower than pausing, so the wait matters more here. With `--no-wait` the command hints at `surreal cloud instance watch <name>`.

## surreal cloud instance watch {#instance-watch}

Watch an operation that was started elsewhere.

<Synopsis>
surreal cloud instance watch [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--until",
            "value": "<STATE>",
            "default": "ready",
            "description": "The state to wait for: `ready`, `paused` or `deleted`."
        },
        {
            "name": "--wait-timeout",
            "value": "<DURATION>",
            "default": "15m",
            "description": "How long to watch before giving up."
        }
    ]}
/>

There is no `--wait` or `--no-wait` here, because a watch is nothing but a wait. The exit codes are those of any wait: `10` on timeout, `6` when the instance settles in a different state, and `5` when it disappears while you wait for another state.

| `--until` value | Finishes when |
| --- | --- |
| `ready` | The instance is running and healthy |
| `paused` | The instance is deliberately stopped |
| `deleted` | The instance is gone - a `404` while polling is what finishes this one |

```bash title="Pick up a create started in an earlier CI job"
surreal cloud instance watch api
```

```bash title="Wait for a pause to complete"
surreal cloud instance watch staging --until paused
```

> [!NOTE]
> This is the one command where `--json` writes to stdout continuously. The transitions *are* the answer, so the newline-delimited stream goes to stdout and nothing else is emitted. In text mode the run ends with a fresh detail view, except under `--until deleted`, where there is nothing left to fetch. Elsewhere, `--json` puts the progress stream on stderr and one final document on stdout.

```bash title="Follow the transitions as they happen"
surreal cloud instance watch api --json | jq -r '.event + " " + (.state // "")'
```

## surreal cloud instance status {#instance-status}

Show an instance's deployment phase and restorable snapshots.

<Synopsis>
surreal cloud instance status [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags), which arrange the snapshot table. Column ids are `snapshot_id`, `started_at`, `tiers` and `on_demand`, with `valid_versions` under `--wide`.

```bash
surreal cloud instance status production
```

The answer is one composite document: a phase, then the snapshots the instance can be restored from. In plain mode the phase is written as `phase<tab><Phase>` before the table, so `grep ^phase` works the same way it does on [`instance get`](#instance-get). An instance with no restorable snapshots gets a note on stderr rather than an empty table with no explanation.

Snapshot ids from here are what [`instance create --restore-from`](#instance-create) expects.

## surreal cloud instance endpoint {#instance-endpoint}

Print the endpoint a client connects to.

<Synopsis>
surreal cloud instance endpoint [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

This command has no options of its own.

The output is the bare endpoint plus a newline, byte-identical in rich and plain output, so `$(…)` captures exactly the value with no label and no styling.

```bash
ENDPOINT=$(surreal cloud instance endpoint production)
surreal db sql --endpoint "$ENDPOINT" --namespace app --database main
```

```bash title="Under --json, the hosts come too"
surreal cloud instance endpoint production --json
```

```json title="Output"
{
  "endpoint": "wss://production-6xk2.aws-euw1.surreal.cloud",
  "host": "production-6xk2.aws-euw1.surreal.cloud",
  "private_host": null,
  "access_type": "public"
}
```

The endpoint has the `wss://` scheme. A private-only instance produces a warning on stderr rather than a refusal - the endpoint is still correct, it is simply not reachable from where you are. The endpoint carries no credential: to connect, add a token from [`instance token`](#instance-token).

## surreal cloud instance token {#instance-token}

Mint a database token for an instance.

<Synopsis>
surreal cloud instance token [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--reveal",
            "description": "Print the token even when stdout is a terminal."
        }
    ]}
/>

This is a *database* token - the credential a SurrealDB client authenticates with. It is not a [personal access token](/docs/reference/cli/cloud/commands/token.md), which authenticates against the control plane.

```bash title="Copy it to the clipboard"
surreal cloud instance token production | pbcopy
```

```bash title="Use it with the database CLI"
SURREAL_TOKEN=$(surreal cloud instance token production) \
    surreal db sql --endpoint "$(surreal cloud instance endpoint production)" --namespace app --database main
```

To open a session without handling the token yourself, use [`instance sql`](#instance-sql), which does the same thing.

**Refusals.** Printing a credential into a terminal's scrollback is refused, exit `2`, and the check happens *before* the token is minted - so a forgotten `--reveal` never costs a credential nobody can recover:

```text
Refusing to print a database token to a terminal, where it would stay in your scrollback.
Pipe it:  surreal cloud instance token production | pbcopy
Or pass --reveal if you meant to see it.
```

Under `--json` the answer is a document with three fields, `kind` (`instance_token`), `instance_id` and `token`, so `surreal cloud instance token production --json | jq -r .token` works. Without `--json`, the bare token is the whole of stdout.

## surreal cloud instance jwks {#instance-jwks}

Fetch the key set that verifies an instance's tokens.

<Synopsis>
surreal cloud instance jwks [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug, name, or org/name. Omit to choose interactively."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are the JWK member names - `kid`, `kty`, `alg` and `use`, with `crv` under `--wide` - so a member the key type does not carry renders as missing rather than empty.

```bash title="Inspect the keys"
surreal cloud instance jwks production
```

```bash title="Pipe the whole key set into a verifier"
surreal cloud instance jwks production --json > jwks.json
```

Under `--json` the whole key set is emitted, `keys` wrapper included, so it can be handed straight to a JWT library.

A key set is public, so this is the one instance route that does not need a Cloud session. Addressing the instance by id avoids any lookup; a name or slug still needs to list the organisation's instances first, which needs a credential.

## Handing off to the database

`sql`, `import` and `export` do not speak SurrealQL themselves. Each one resolves the instance, mints a short-lived database token, and then becomes the matching database command, run through the `surreal` launcher:

| Command | Runs | Endpoint scheme |
| --- | --- | --- |
| `surreal cloud instance sql` | `surreal db sql` | `wss://` |
| `surreal cloud instance import` | `surreal db import` | `https://` |
| `surreal cloud instance export` | `surreal db export` | `https://` |

What the handoff passes to the database:

- `--endpoint` is added as `<scheme>://<instance host>`, unless the arguments after `--` already contain `--endpoint` or `-e`, in which case yours is used and nothing is added.
- The token is passed in the environment as `SURREAL_TOKEN`, never on the command line, so it does not appear in `ps` output or shell history.
- `--namespace` and `--database` become `SURREAL_NAMESPACE` and `SURREAL_DATABASE` in the child's environment.
- Everything after `--` is forwarded unchanged, because the database's own flags are documented on its own pages and change independently. Pass them as you would to `surreal db sql`, `surreal db import` or `surreal db export`.

The database is reached only through the launcher, at the path the launcher records in `SURREAL_CLI_LAUNCHER`. The component does not search `PATH` for a `surreal` binary, install anything, or fetch the database itself. If the database is not installed, the launcher offers to install it when it runs the command. Unattended, in CI or with `--no-input`, the launcher exits `12` and names the install command instead. See [`surreal`](/docs/reference/cli/launcher.md).

Run `surreal-cloud` directly rather than as `surreal cloud` and these three commands refuse with exit `2`, because there is no launcher to hand off to. They check this first, before anything else, so no token is minted for nothing.

On Unix the handoff replaces the process, so the database command owns the terminal, signals and exit code. On Windows the command stays alive and forwards the database's exit code.

Two warnings, neither of which stops the handoff: the instance is private-only, so its host resolves only inside its own network, and the instance is paused, failed or deleted, so the connection will probably be refused. `Connecting to <name> at <host>.` goes to stderr as the last thing printed, so `export -- -` can be piped without the note getting into the data.

## surreal cloud instance sql {#instance-sql}

Open a SurrealQL session against an instance.

<Synopsis>
surreal cloud instance sql [OPTIONS] [INSTANCE] [-- <ARGS>...]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        },
        {
            "name": "[ARGS]...",
            "description": "Arguments for `surreal db sql`, after a `--` separator."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--namespace",
            "value": "<NS>",
            "description": "The namespace to open."
        },
        {
            "name": "--database",
            "value": "<DB>",
            "description": "The database to open."
        }
    ]}
/>

This runs [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) against the instance, as described in [Handing off to the database](#handing-off-to-the-database).

```bash title="Open a shell"
surreal cloud instance sql production --namespace app --database main
```

```bash title="Pass flags through to surreal db sql"
surreal cloud instance sql production -- --ns app --db main --pretty
```

```bash title="Run a query from a pipeline"
echo "SELECT count() FROM person GROUP ALL;" \
    | surreal cloud instance sql production --namespace app --database main
```

`--namespace` and `--database` are optional here, because `surreal db sql` accepts a session without them.

## surreal cloud instance import {#instance-import}

Import a file into an instance.

<Synopsis>
surreal cloud instance import [OPTIONS] [INSTANCE] [-- <ARGS>...]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        },
        {
            "name": "[ARGS]...",
            "description": "Arguments for `surreal db import`, after a `--` separator. The file to load goes here."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--namespace",
            "value": "<NS>",
            "required": true,
            "description": "The namespace to import into. Required by this command, though `--help` does not mark it so."
        },
        {
            "name": "--database",
            "value": "<DB>",
            "required": true,
            "description": "The database to import into. Required by this command, though `--help` does not mark it so."
        }
    ]}
/>

This runs [`surreal db import`](/docs/reference/cli/db/commands/import.md) against the instance, as described in [Handing off to the database](#handing-off-to-the-database). The file is the positional argument of the database command, so it goes after the `--`.

```bash
surreal cloud instance import production --namespace app --database main -- ./seed.surql
```

**Refusals.** `surreal db import` requires both a namespace and a database, so this command does too. The check fires before a token is minted, exit `2`:

```text
`surreal cloud instance import` needs both --namespace and --database; `surreal import` requires them.
```

See [`surreal db import`](/docs/reference/cli/db/commands/import.md) for the flags available after the `--`.

## surreal cloud instance export {#instance-export}

Export an instance to a file.

<Synopsis>
surreal cloud instance export [OPTIONS] [INSTANCE] [-- <ARGS>...]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        },
        {
            "name": "[ARGS]...",
            "description": "Arguments for `surreal db export`, after a `--` separator. The destination goes here, or `-` for stdout."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--namespace",
            "value": "<NS>",
            "required": true,
            "description": "The namespace to export from. Required by this command, though `--help` does not mark it so."
        },
        {
            "name": "--database",
            "value": "<DB>",
            "required": true,
            "description": "The database to export from. Required by this command, though `--help` does not mark it so."
        }
    ]}
/>

This runs [`surreal db export`](/docs/reference/cli/db/commands/export.md) against the instance, as described in [Handing off to the database](#handing-off-to-the-database).

```bash title="Export to a file"
surreal cloud instance export production --namespace app --database main -- ./backup.surql
```

```bash title="Export to stdout and compress"
surreal cloud instance export production --namespace app --database main -- - | gzip > backup.surql.gz
```

Like `import`, this refuses a missing namespace or database before minting anything. The destination and the export selection flags, such as `--tables` or `--only`, belong to [`surreal db export`](/docs/reference/cli/db/commands/export.md). With no destination the database writes to stdout. Everything this command itself prints goes to stderr, so the dump on stdout stays clean.

For a managed snapshot rather than a SurrealQL dump, use [`instance backup create`](#instance-backup-create).

## surreal cloud instance metrics {#instance-metrics}

Show an instance's resource metrics.

<Synopsis>
surreal cloud instance metrics [OPTIONS] --metric <METRIC> [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--metric",
            "value": "<METRIC>",
            "required": true,
            "description": "Which metric to read, such as `cpu`, `memory` or `disk`."
        },
        {
            "name": "--from",
            "value": "<TIME>",
            "description": "Start of the window, as an RFC 3339 timestamp."
        },
        {
            "name": "--to",
            "value": "<TIME>",
            "description": "End of the window, as an RFC 3339 timestamp."
        },
        {
            "name": "--samples",
            "description": "Show every sample instead of one summary per series."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Summary rows carry `series`, `last`, `min`, `mean`, `max` and `unit`, with `samples` and `gaps` under `--wide`; sample rows carry `timestamp`, `series` and `value`, with `unit` under `--wide`.

`--metric` is a free-form string rather than a fixed set, because the platform's metric vocabulary is not published anywhere the CLI can read it.

```bash title="A summary per series"
surreal cloud instance metrics production --metric cpu
```

```bash title="Every sample over a window"
surreal cloud instance metrics production \
    --metric memory \
    --from 2026-08-11T10:00:00Z \
    --to 2026-08-11T12:00:00Z \
    --samples
```

Timestamps are validated locally and forwarded byte-for-byte, never reformatted:

```text
Ensure `11-08-2026` is an RFC3339 timestamp, such as 2026-08-11T10:00:00Z
```

`--samples` chooses a table, not a payload: `--json` carries the whole document either way. A note on stderr states the metric, its unit and the window, which matters when neither bound is given because the API then chooses the window. An empty window prints a note suggesting a wider `--from` or a check of the metric name.

## surreal cloud instance logs {#instance-logs}

Show an instance's logs.

<Synopsis>
surreal cloud instance logs [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--from",
            "value": "<TIME>",
            "description": "Start of the window, as an RFC 3339 timestamp."
        },
        {
            "name": "--to",
            "value": "<TIME>",
            "description": "End of the window, as an RFC 3339 timestamp. Conflicts with `--follow`."
        },
        {
            "name": "--level",
            "value": "<LEVELS>",
            "description": "Show only these levels, comma-separated. Filtered locally; the API has no level parameter."
        },
        {
            "name": "--follow",
            "short": "-f",
            "description": "Keep printing new lines. There is no streaming endpoint, so this polls."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `timestamp`, `level` and `message`, with `pod` under `--wide`.

```bash title="Recent lines"
surreal cloud instance logs production
```

```bash title="Errors and warnings only"
surreal cloud instance logs production --level error,warn
```

```bash title="Follow, and hand each line to jq"
surreal cloud instance logs production --follow --json | jq -r '.timestamp + " " + .message'
```

`--level` is a display filter applied on this side, and a note reports how many lines were hidden by it. `--json` still carries every line the API sent. `--limit` takes from the start of the window, matching its meaning elsewhere, so to read the most recent lines, narrow the window with `--from`.

`--follow` polls with a moving start time at a fixed five-second interval - there is no streaming endpoint and no interval flag. It runs until interrupted, and lines on the boundary between two polls are not printed twice. Under `--follow --json`, each new line is written as newline-delimited JSON on stdout; in text mode a table is printed per batch, with the header only on the first.

## surreal cloud instance usage {#instance-usage}

Show what an instance has consumed.

<Synopsis>
surreal cloud instance usage [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        }
    ]}
/>

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags) and nothing else - this route takes no query parameters at all. Column ids are `instance_id`, `metric_type`, `compute_hours`, `disk_used_bytes`, `period_start` and `period_end`, with `instance_type` and `source` under `--wide`.

```bash
surreal cloud instance usage production
```

For every instance in the organisation at once, use [`org usage`](/docs/reference/cli/cloud/commands/org.md#org-usage).

## surreal cloud instance estimate {#instance-estimate}

Estimate what an instance would cost.

<Synopsis>
surreal cloud instance estimate [OPTIONS] --type <SLUG> --region <REGION>
</Synopsis>

This command takes no positional argument: it prices a hypothetical instance, so there is nothing to name.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--type",
            "value": "<SLUG>",
            "required": true,
            "description": "The instance type to price, by slug."
        },
        {
            "name": "--region",
            "value": "<REGION>",
            "required": true,
            "description": "The region to price it in."
        },
        {
            "name": "--units",
            "value": "<N>",
            "description": "How many compute units. Defaults to the type's own. Minimum 1."
        },
        {
            "name": "--storage",
            "value": "<GB>",
            "description": "How much storage, in gigabytes. Defaults to the type's own. Minimum 1."
        }
    ]}
/>

```bash title="Price a change before making it"
surreal cloud instance estimate --type production-4 --region aws-euw1 --units 8 --storage 250
```

Flags you omit are left out of the request rather than sent as null, so the platform's own defaults for that type apply. The estimate is for the organisation resolved from `--org` and the [precedence chain](/docs/reference/cli/cloud/global-flags.md#the-precedence-chain). Nothing is created or billed. The result shows the type, the region, any units or storage you gave, the cost with its currency symbol, and the currency.

**Refusals**, exit `2`:

```text
An instance has at least 1 compute unit; 0 was given.
An instance has at least 1GB of storage; 0 was given.
```

This is the one place the API does not use integer minor units - the cost is a number in a named currency - and `--json` keeps the raw value.

## surreal cloud instance capabilities get {#instance-capabilities-get}

Show an instance's capability configuration.

<Synopsis>
surreal cloud instance capabilities get [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        }
    ]}
/>

This command has no options of its own.

```bash
surreal cloud instance capabilities get production
```

The view lists `allow_scripting`, `allow_guests` and `allow_insecure_storable_closures`, then the allowed and denied list for each of `experimental`, `arbitrary_query`, `eval_query`, `rpc_methods`, `http_endpoints`, `networks` and `functions`, named as the API names them (`allowed_functions`, `denied_networks` and so on). A setting the API did not send is left out, which is different from an empty list: an explicitly empty list is shown as `(none)` and permits nothing in that category, while an absent one leaves SurrealDB's default in force. Capabilities this build does not know about are shown as they arrive.

Under `--json` the nested capabilities object is emitted, not the whole instance - so it can be diffed against another instance directly.

```bash title="Compare two instances"
diff <(surreal cloud instance capabilities get staging --json) \
     <(surreal cloud instance capabilities get production --json)
```

## surreal cloud instance capabilities set {#instance-capabilities-set}

Change an instance's capability configuration.

<Synopsis>
surreal cloud instance capabilities set [OPTIONS] [INSTANCE]
</Synopsis>

The capabilities route is a full replacement, so this command reads the current configuration, applies the flags given here, shows what would change, and asks before writing. A list flag **replaces** that list rather than adding to it, and anything not named is left exactly as it is - including capabilities this build does not know about.

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--allow-scripting",
            "description": "Allow embedded scripting functions. Conflicts with `--deny-scripting`."
        },
        {
            "name": "--deny-scripting",
            "description": "Refuse embedded scripting functions."
        },
        {
            "name": "--allow-guests",
            "description": "Allow unauthenticated guest access. Conflicts with `--deny-guests`."
        },
        {
            "name": "--deny-guests",
            "description": "Refuse unauthenticated guest access."
        },
        {
            "name": "--allow-experimental",
            "value": "<NAMES>",
            "description": "Experimental features to allow, comma-separated."
        },
        {
            "name": "--deny-experimental",
            "value": "<NAMES>",
            "description": "Experimental features to deny, comma-separated."
        },
        {
            "name": "--allow-arbitrary-query",
            "value": "<TARGETS>",
            "description": "Arbitrary-query targets to allow, comma-separated."
        },
        {
            "name": "--deny-arbitrary-query",
            "value": "<TARGETS>",
            "description": "Arbitrary-query targets to deny, comma-separated."
        },
        {
            "name": "--allow-eval-query",
            "value": "<TARGETS>",
            "description": "Eval-query targets to allow, comma-separated."
        },
        {
            "name": "--deny-eval-query",
            "value": "<TARGETS>",
            "description": "Eval-query targets to deny, comma-separated."
        },
        {
            "name": "--allow-rpc",
            "value": "<METHODS>",
            "description": "RPC methods to allow, comma-separated."
        },
        {
            "name": "--deny-rpc",
            "value": "<METHODS>",
            "description": "RPC methods to deny, comma-separated."
        },
        {
            "name": "--allow-http",
            "value": "<ENDPOINTS>",
            "description": "HTTP endpoints to allow, comma-separated."
        },
        {
            "name": "--deny-http",
            "value": "<ENDPOINTS>",
            "description": "HTTP endpoints to deny, comma-separated."
        },
        {
            "name": "--allow-net",
            "value": "<TARGETS>",
            "description": "Networks to allow, comma-separated."
        },
        {
            "name": "--deny-net",
            "value": "<TARGETS>",
            "description": "Networks to deny, comma-separated."
        },
        {
            "name": "--allow-funcs",
            "value": "<FUNCTIONS>",
            "description": "Functions to allow, comma-separated."
        },
        {
            "name": "--deny-funcs",
            "value": "<FUNCTIONS>",
            "description": "Functions to deny, comma-separated."
        },
        {
            "name": "--force",
            "description": "Replace the capabilities without confirming."
        }
    ]}
/>

```bash title="Turn scripting off"
surreal cloud instance capabilities set production --deny-scripting
```

```bash title="Replace the allowed function list"
surreal cloud instance capabilities set production --allow-funcs "array,string,time"
```

```bash title="Deny outbound HTTP from a script"
surreal cloud instance capabilities set production --deny-funcs "http::*" --force
```

The diff is printed on stderr in every output mode, `--json` included, then confirmed unless `--force` or the global `--yes` is given. `--quiet` suppresses the diff, so combine it with `--force` rather than with a prompt. Declining prints `Nothing was changed.` and exits `0`. In a non-interactive session without `--force` or `--yes`, the command exits `2` having written nothing.

Flags for the same capability in opposite directions, such as `--allow-scripting` with `--deny-scripting`, are a usage error. A list flag may be given together with its opposite, for example `--allow-funcs` and `--deny-funcs`, and each replaces its own list. Setting a list to the value it already has is not a change. There is no flag for `allow_insecure_storable_closures`: `set` leaves it as it is.

On success the command prints the configuration now in force.

**Refusals and short-circuits.** No flags at all is a usage error, exit `2`, before any request:

```text
Nothing to set. Pass at least one capability flag, such as --allow-scripting or --deny-funcs http::*.
Run `surreal cloud instance capabilities get` to see the current configuration.
```

Flags that would change nothing produce a note and exit `0` without a write:

```text
`production` already has those capabilities. Nothing was changed.
```

## surreal cloud instance backup list {#instance-backup-list}

List the snapshots an instance can be restored from.

<Synopsis>
surreal cloud instance backup list [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        }
    ]}
/>

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags) and nothing else. Column ids are `snapshot_id`, `started_at`, `tiers` and `on_demand`, with `valid_versions` under `--wide`.

```bash
surreal cloud instance backup list production
```

```bash title="The newest snapshot id"
surreal cloud instance backup list production --sort started_at --reverse --limit 1 \
    --columns snapshot_id --no-header
```

There is no backup collection route on the API, so this reads the snapshots off the instance's status route. `--json` carries the snapshot array rather than the whole status document, and an instance too new to have been backed up gives an empty array and a note. [`instance status`](#instance-status) shows the same snapshots alongside the deployment phase.

## surreal cloud instance backup create {#instance-backup-create}

Take a backup of an instance now.

<Synopsis>
surreal cloud instance backup create [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        }
    ]}
/>

This command has no options of its own, and carries no `--wait` or `--no-wait`.

```bash
surreal cloud instance backup create production
```

The route answers with no body, so the document is synthesised:

```json title="Output"
{
  "instance_id": "67upif0m8sh1cn1p2c8t",
  "status": "accepted"
}
```

A replayed request is a second snapshot, so this call is not retried after a timeout or a `502`. Requesting a backup while one is already queued answers with a `429` that is reported as a conflict, exit `6`, rather than as a rate limit - the condition is "one is already running", not "you are asking too often". Only one on-demand backup runs at a time.

Poll [`instance backup list`](#instance-backup-list) to see the snapshot appear.

## surreal cloud instance backup policy get {#instance-backup-policy-get}

Show the backup retention policy.

<Synopsis>
surreal cloud instance backup policy get [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        }
    ]}
/>

This command has no options of its own.

```bash
surreal cloud instance backup policy get production
```

The answer reports each retention tier, how often snapshots are taken, and which tiers this organisation's plan allows you to change - which is what [`backup policy set`](#instance-backup-policy-set) will accept.

## surreal cloud instance backup policy set {#instance-backup-policy-set}

Change the backup retention policy.

<Synopsis>
surreal cloud instance backup policy set [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--daily",
            "value": "<DAYS>",
            "description": "How many days of daily snapshots to keep, or the literal `default`."
        },
        {
            "name": "--weekly",
            "value": "<WEEKS>",
            "description": "How many weeks of weekly snapshots to keep, or the literal `default`."
        },
        {
            "name": "--monthly",
            "value": "<MONTHS>",
            "description": "How many months of monthly snapshots to keep, or the literal `default`."
        },
        {
            "name": "--frequency-hours",
            "value": "<HOURS>",
            "description": "How often to take a snapshot, in hours. Checked against the allow-list this organisation's plan publishes, not against a range."
        },
        {
            "name": "--force",
            "description": "Change the policy without confirming."
        }
    ]}
/>

The three retention flags also accept the literal `default`, which restores this organisation's own value for that tier. `--frequency-hours` has no `default` form, because the platform gives it no resettable value.

`--frequency-hours` is an **allow-list**, not a range: the plan publishes the intervals it offers, and a value sitting between two of them is refused rather than rounded. Run [`backup policy get`](#instance-backup-policy-get) to see which intervals this plan offers.

```bash title="Keep a fortnight of dailies"
surreal cloud instance backup policy set production --daily 14
```

```bash title="Snapshot every six hours"
surreal cloud instance backup policy set production --frequency-hours 6
```

```bash title="Reset the monthly tier to the organisation's own value"
surreal cloud instance backup policy set production --monthly default
```

**Confirmation.** Only a *reduction* asks. Lengthening a retention cannot lose a snapshot, so it is applied without a prompt. A reset to `default` counts as a reduction, because the organisation's own value is not known in advance. Changing only `--frequency-hours` never asks, because taking snapshots less often deletes none. The shortened tiers are listed on stderr and the question names the instance. Declining prints `The policy was not changed.` and exits `0`. In a non-interactive session without `--force` or `--yes`, a reduction exits `2` having changed nothing.

**Refusals**, exit `2`:

- No flags at all:

```text
Nothing to set. Pass --daily, --weekly, --monthly or --frequency-hours.
Each retention flag also takes the literal `default`, which restores this organization's own value for that tier.
```

- A tier the plan does not allow changing:

```text
This organization's plan does not allow changing the monthly retention on `production`.
Run `surreal cloud instance backup policy get` to see which tiers are editable.
```

- A value outside the plan's bounds, naming the bound it broke:

```text
Daily retention must be between 1 and 30 days on this plan; 90 was given.
```

- A `--frequency-hours` the plan does not offer, listing the intervals it does:

```text
A backup every 5 hours is not offered on this plan. Choose one of: 6, 12, 24.
```

## Related pages

- [Long-running operations](/docs/reference/cli/cloud/long-running-operations.md) - `--wait`, `--no-wait`, polling and exit code `10`
- [`org` commands](/docs/reference/cli/cloud/commands/org.md) - the organisation that owns these instances
- [`catalog` commands](/docs/reference/cli/cloud/commands/catalog.md) - the type, region and version slugs `create` and `update` expect
- [The `surreal` launcher](/docs/reference/cli/launcher.md) - how `sql`, `import` and `export` reach the database component
- [Overview](/docs/reference/cli/cloud.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/db.md) - the data plane: `surreal db sql`, `surreal db import`, `surreal db export`

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/invite

# surreal cloud invite

Reference for surreal cloud invite - list pending organisation invitations, send one with a role, and withdraw one by email address or code. Same command as team invite; email is checked locally and roles come from org roles.

`surreal cloud invite` manages organisation invitations: the ones that have been sent and not yet accepted. Once someone accepts, they become a member and move into [`team`](/docs/reference/cli/cloud/commands/team.md).

<Synopsis>
surreal cloud invite <COMMAND> [OPTIONS]
surreal cloud invites <COMMAND> [OPTIONS]
</Synopsis>

| Verb | Purpose | Alias |
| --- | --- | --- |
| [`list`](#invite-list) | List an organisation's pending invitations | `ls` |
| [`create`](#invite-create) | Invite someone to an organisation | |
| [`delete`](#invite-delete) | Withdraw an invitation | `rm` |

[`team invite`](/docs/reference/cli/cloud/commands/team.md#team-invite) and [`invite create`](#invite-create) are the same command reached two ways - the same arguments, the same validators and the same messages - because sending an invitation is both how a team gains a member and how an invitation comes to exist. Use whichever reads better in the script you are writing.

This group declares the scopes `read:cloud` and `write:cloud-organization`, the same pair as [`org`](/docs/reference/cli/cloud/commands/org.md) and [`team`](/docs/reference/cli/cloud/commands/team.md). A personal access token cannot use the mutating verbs: see [Authentication](/docs/reference/cli/cloud/authentication.md#what-a-personal-access-token-cannot-do). There is no `update`: to change an invitation, withdraw it and send another.

## surreal cloud invite list {#invite-list}

List an organisation's pending invitations.

<Synopsis>
surreal cloud invite list [OPTIONS]
</Synopsis>

This command takes no positional argument. The organisation comes from `--org` and the [precedence chain](/docs/reference/cli/cloud/global-flags.md#the-precedence-chain).

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags) and nothing else. Column ids are `email`, `role` and `status`, with `code` and `organization_id` under `--wide`.

```bash
surreal cloud invite list
```

```text title="Output"
EMAIL                ROLE    STATUS
dee@acme.example     member  pending
eli@acme.example     admin   pending
```

```bash title="Include the codes, which withdrawals also accept"
surreal cloud invite list --wide
```

The list is not filtered by status, so it shows every status the API reports. An organisation with no invitations is a success: exit `0`, and an empty array under `--json`.

## surreal cloud invite create {#invite-create}

Invite someone to an organisation.

<Synopsis>
surreal cloud invite create [OPTIONS] --role <ROLE> <EMAIL>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<EMAIL>",
            "required": true,
            "description": "The email address to invite."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--role",
            "value": "<ROLE>",
            "required": true,
            "description": "The role to grant. Run `surreal cloud org roles` for the ones this organisation defines."
        }
    ]}
/>

```bash
surreal cloud invite create dee@acme.example --role member
```

```bash title="Invite a whole list, reading roles from a file"
while IFS=, read -r email role; do
    surreal cloud invite create "$email" --role "$role"
done < new-joiners.csv
```

**Refusals**, exit `2`, before any request. The address validator asks only for an `@` with something either side and no internal whitespace; the API is the authority on deliverability:

```text
`dee.acme.example` is not an email address: it has no `@`.
```

Only an empty `--role` is refused locally, because the role vocabulary is per organisation:

```text
A role cannot be empty. Run `surreal cloud org roles` to see the ones this organization defines.
```

A role the API rejects as invalid is annotated with a pointer to [`org roles`](/docs/reference/cli/cloud/commands/org.md#org-roles).

The command is never retried after the request has been written, because a repeat would send a second email carrying a second working code. On success it prints the invitation, including its code.

## surreal cloud invite delete {#invite-delete}

Withdraw an invitation.

<Synopsis>
surreal cloud invite delete [OPTIONS] <INVITE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<INVITE>",
            "required": true,
            "description": "The invitation, by email address or code."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Withdraw without confirming."
        }
    ]}
/>

```bash title="By address"
surreal cloud invite delete dee@acme.example
```

```bash title="By code, without a prompt"
surreal cloud invite delete 7f3a91c4 --force
```

The invitation has no id of its own. The API identifies it by its code, which the recipient also redeems, so `<INVITE>` accepts either. An email address is resolved to its code by listing the invitations first, and the listing also happens when you pass a code, so the confirmation can name the address.

**Refusals.** The confirmation names the address the invitation was sent to. Declining prints `Nothing was withdrawn.` and exits `0`. In a non-interactive session without `--force` or `--yes`, it exits `2` having sent nothing.

Withdrawing an invitation someone has already accepted is not how you remove them - use [`team remove`](/docs/reference/cli/cloud/commands/team.md#team-remove) for that.

## Related pages

- [`team` commands](/docs/reference/cli/cloud/commands/team.md) - members who have already joined
- [`org roles`](/docs/reference/cli/cloud/commands/org.md#org-roles) - the roles an invitation can grant
- [Overview](/docs/reference/cli/cloud.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/db.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/misc

# surreal cloud other commands

Reference for the surreal cloud leaf commands - whoami, api, open, status, version and completion.

Six commands sit at the top level rather than inside a noun group. They cover identity, the raw API escape hatch, opening the dashboard, diagnostics, version information and shell completion.

| Command | Purpose | Alias |
| --- | --- | --- |
| [`whoami`](#whoami) | Show who the API thinks you are | |
| [`api`](#api) | Call the API directly | |
| [`open`](#open) | Open a dashboard page in a browser | |
| [`status`](#status) | Check that everything is configured and reachable | `doctor` |
| [`version`](#version) | Output version information | |
| [`completion`](#completion) | Generate shell completions | |

## surreal cloud whoami {#whoami}

Show who the API thinks you are.

<Synopsis>
surreal cloud whoami [OPTIONS]
</Synopsis>

This command takes no arguments and no options of its own.

```bash
surreal cloud whoami
```

```text title="Output"
Email           ana@acme.example
User Id         67upif0m8sh1cn1p2c8t
Name            Ana Silva
Default Org     acme
```

`whoami` spends a request on the API's own profile route, which makes it the counterpart to [`auth status`](/docs/reference/cli/cloud/commands/auth.md#auth-status): that one reports what is on this machine, without touching the network; this one reports what the API believes. When they disagree, the API is right.

In text modes the default organisation is shown by name, which costs a second request. That lookup is skipped under `--json`, where the payload is the profile exactly as the API sent it.

## surreal cloud api {#api}

Call the API directly.

<Synopsis>
surreal cloud api [OPTIONS] <METHOD> <PATH>
</Synopsis>

This is the escape hatch. It signs a request with whatever credential the profile holds, applies the same retries and the same error classification as every other command, and prints the body - so a route that has no dedicated command is still reachable without rebuilding authentication by hand.

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<METHOD>",
            "required": true,
            "description": "The HTTP method: `get`, `post`, `put`, `patch` or `delete`. Case-insensitive."
        },
        {
            "name": "<PATH>",
            "required": true,
            "description": "The path, such as `/api/cloud/v0/organizations`. A leading `/` is added when absent."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--data",
            "short": "-d",
            "value": "<BODY>",
            "description": "A JSON body, `@FILE` to read a file, or `-` to read standard input."
        },
        {
            "name": "--query",
            "value": "<KEY=VALUE>",
            "description": "A query parameter, as `key=value`. Repeatable."
        },
        {
            "name": "--header",
            "value": "<NAME:VALUE>",
            "description": "An extra request header, as `name:value`. Repeatable."
        },
        {
            "name": "--include",
            "description": "Print the response status and headers to stderr."
        },
        {
            "name": "--raw",
            "description": "Print the body exactly as it arrived, without pretty-printing."
        },
        {
            "name": "--force",
            "description": "Send a write without confirming."
        }
    ]}
/>

```bash title="A GET, pretty-printed"
surreal cloud api get /api/cloud/v0/organizations
```

```bash title="Query parameters and response headers"
surreal cloud api get /api/cloud/v0/organizations --query limit=5 --include
```

```bash title="A write, from a file"
surreal cloud api post /api/cloud/v0/organizations --data @org.json --force
```

```bash title="A write, from stdin"
echo '{"name":"acme"}' | surreal cloud api post /api/cloud/v0/organizations -d - --force
```

`-d` follows curl's spellings: `-` and `@-` mean standard input, `@path` means a file, and anything else is treated as an inline body.

**Confirmation.** Any write method - `POST`, `PUT`, `PATCH` or `DELETE` - confirms first unless `--force` or the global `--yes` is given. Declining prints `Nothing was sent.` and exits `0`. In a non-interactive session with neither, it exits `2` having sent nothing.

**Refusals**, all exit `2`:

- An unknown method:

```text
Ensure the method is one of get, post, put, patch or delete (got `HEAD`)
```

- A URL where a path was expected:

```text
Ensure the path is a path and not a URL; the host comes from --api
```

- A malformed `--query` or `--header`. Both split on their first separator only, so a value containing `=` or `:` survives intact.
- A reserved header. `Authorization`, `X-Cloud-Token` and `X-Request-Id` come from the credential in use:

```text
`authorization` is set from the credential in use and cannot be overridden here. Pass --token or --token-file to change what is sent.
```

- A body on a `GET`: `A GET takes no body. Use --query for parameters, or pick another method.`
- A body that is not valid JSON, naming where it came from.

**Notes.** A path on neither the Cloud nor the accounts surface is sent **without credentials**, and says so first. Retries follow the method: a `GET` is retried freely, a `POST` never, and `PUT`, `PATCH` and `DELETE` only when the connection failed before anything was sent.

`--raw` writes the bytes verbatim, control characters included, which is safe only because it is opt-in. `--json` is unaffected by `--raw`: it always emits one parseable document, turning a non-JSON body into a JSON string and an empty body into `null`. In text mode an empty body prints nothing, and a note on stderr reports the status that answered with none.

## surreal cloud open {#open}

Open a dashboard page in a browser.

<Synopsis>
surreal cloud open [OPTIONS] [SUBJECT] [REF]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[SUBJECT]",
            "default": "dashboard",
            "description": "What to open: `dashboard`, `org`, `instance`, `billing` or `terms`."
        },
        {
            "name": "[REF]",
            "description": "The organisation or instance, by id, slug or name."
        }
    ]}
/>

This command has no options of its own - only the [global flags](/docs/reference/cli/cloud/global-flags.md).

| `[SUBJECT]` | Opens |
| --- | --- |
| `dashboard` | The account's overview page |
| `org` | An organisation's overview |
| `instance` | One instance, by id, slug or name |
| `billing` | An organisation's billing and invoices |
| `terms` | The terms and privacy policy, which this CLI links to and never accepts |

```bash title="The account overview"
surreal cloud open
```

```bash title="One instance"
surreal cloud open instance production
```

```bash title="Billing for the resolved organisation"
surreal cloud open billing
```

**The URL is always printed; it is only sometimes opened.** A browser is attempted when a person is present - which includes a `--json` invocation, since a human may well want machine output and a browser window at the same time - and a failure to open is a warning rather than an error. So the command is still useful over SSH: you get the link, and you paste it yourself.

**Refusals**, exit `2`:

- A `[REF]` alongside `dashboard` or `terms`, which take none:

```text
That subject takes no reference. Try `surreal cloud open org <name>` or `surreal cloud open instance <name>`.
```

- `instance` with no reference:

```text
`open instance` needs to know which one.

Try:  surreal cloud open instance <name>
```

`open terms` links to the legal documents and lists the others as notes. This CLI never accepts terms on your behalf.

## surreal cloud status {#status}

Check that everything is configured and reachable. Also spelled `surreal cloud doctor`.

<Synopsis>
surreal cloud status [OPTIONS]
surreal cloud doctor [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `check`, `ok` and `detail`.

Six checks run in dependency order, one row each. No check can abort the command, so you always get the whole picture:

| Check | What it covers |
| --- | --- |
| `credential` | Which credential this profile holds, and when it expires. Local only. A failure here is fatal |
| `api` | A version request against the API. An authentication error skips this rather than failing it |
| `version` | The client version the API expects. Reported, never enforced |
| `cloud session` | The Cloud session token. A personal access token is reported as skipped, not passed or failed |
| `organization` | The full precedence chain, picker included. Never fatal |
| `system message` | A platform banner, when a session is available and the API has one. No verdict |

```bash
surreal cloud status
```

```bash title="Just the failures"
surreal cloud status --json | jq -r '.[] | select(.ok == false) | .check + ": " + .detail'
```

The `ok` column is a tri-state. A check either passed, failed, or never ran because something it depended on had already failed - the third case renders as a dash, and under `--json` the `ok` field is `null` rather than `false`.

> [!IMPORTANT]
> This is the one command that prints its table even when it exits non-zero, because the table *is* the diagnosis and a doctor command that goes silent when things break is useless in the one situation it exists for. The exit code comes from the first fatal check - `3` for a credential problem, `9` for an unreachable API - rather than a flat `1`.

`status` checks the control plane only. It does not look for the database, because that is the launcher's concern: `surreal version` lists every installed component. A `version` check that fails names `surreal upgrade cloud` as the remedy.

## surreal cloud version {#version}

Output version information.

<Synopsis>
surreal cloud version [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--local",
            "description": "Report this build without asking the API for its version."
        }
    ]}
/>

```bash
surreal cloud version
```

```text title="Output"
surreal cloud  0.1.0
Commit         9f3c1ab
Built          2026-08-11
Target         aarch64-apple-darwin
```

```json title="Under --json"
{
  "version": "0.1.0",
  "commit": "9f3c1ab",
  "build_date": "2026-08-11",
  "target": "aarch64-apple-darwin"
}
```

`version` reports the build in front of you. It needs no credential and no organisation, so it works before you have signed in and inside a container with no egress. The flag forms `surreal cloud -V` and `surreal cloud --version` print the version string alone, with the commit, build date and target in parentheses.

To see every component the launcher manages, with the version of each, use `surreal version`. For the API's own version and the client version floor it expects, use [`status`](#status).

## surreal cloud completion {#completion}

Generate shell completions.

<Synopsis>
surreal cloud completion [OPTIONS] <SHELL>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<SHELL>",
            "required": true,
            "description": "The shell to generate completions for: `bash`, `elvish`, `fish`, `powershell` or `zsh`."
        }
    ]}
/>

This command has no options of its own.

```bash title="zsh"
surreal cloud completion zsh > "${fpath[1]}/_surreal-cloud"
```

```bash title="bash"
surreal cloud completion bash > /etc/bash_completion.d/surreal-cloud
```

```bash title="fish"
surreal cloud completion fish > ~/.config/fish/completions/surreal-cloud.fish
```

```powershell title="PowerShell"
surreal cloud completion powershell | Out-String | Invoke-Expression
```

This is the one command whose payload is not a view: it emits a shell script, so `--json` does not wrap it. The script is buffered before it is written, so `surreal cloud completion bash | head -1` exits `0` rather than failing on the closed pipe.

The generated script registers completions for the component's own command name, `surreal-cloud`, which is why the file names above use it.

## Related pages

- [Output and exit codes](/docs/reference/cli/cloud/output-and-exit-codes.md) - the `--json` contract and the full exit-code table
- [Authentication](/docs/reference/cli/cloud/authentication.md) - what `whoami` and `status` are reporting on
- [Install](/docs/explore/cli/install.md) - installing the launcher and its components
- [surreal cloud reference](/docs/reference/cli/cloud.md) - the rest of the reference
- [Database commands](/docs/reference/cli/db.md) - for queries, imports, exports and running a server

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/org

# surreal cloud org

Reference for surreal cloud org - list, get, create, rename, archive and remember a default organisation, and read its roles, permissions, usage, spend and plans. Includes the --period format, archive confirmation and the scopes this group needs.

`surreal cloud org` manages organisations. An organisation owns instances, members and billing, and almost every other command needs to know which one you mean - so this group is usually the second one you use after [`auth`](/docs/reference/cli/cloud/commands/auth.md).

<Synopsis>
surreal cloud org <COMMAND> [OPTIONS]
surreal cloud orgs <COMMAND> [OPTIONS]
</Synopsis>

| Verb | Purpose | Alias |
| --- | --- | --- |
| [`list`](#org-list) | List the organisations you belong to | `ls` |
| [`get`](#org-get) | Show one organisation | |
| [`create`](#org-create) | Create an organisation | |
| [`update`](#org-update) | Rename an organisation | |
| [`archive`](#org-archive) | Archive an organisation | |
| [`use`](#org-use) | Remember an organisation as the default for this profile | |
| [`roles`](#org-roles) | List the roles an organisation can assign | |
| [`permissions`](#org-permissions) | Show what you are permitted to do in an organisation | |
| [`usage`](#org-usage) | Show resource usage across an organisation's instances | |
| [`spend`](#org-spend) | Show an organisation's billed spend | |
| [`plans`](#org-plans) | List the plans available to an organisation | |

This group declares the scopes `read:cloud` and `write:cloud-organization`, the same pair as [`team`](/docs/reference/cli/cloud/commands/team.md) and [`invite`](/docs/reference/cli/cloud/commands/invite.md). A personal access token is read-only here regardless: the gateway refuses every non-GET Cloud route for one, so the mutating verbs answer 403 and exit `4` even for a token carrying `write:cloud-organization`. See [Authentication](/docs/reference/cli/cloud/authentication.md#what-a-personal-access-token-cannot-do).

## surreal cloud org list {#org-list}

List the organisations you belong to.

<Synopsis>
surreal cloud org list [OPTIONS]
</Synopsis>

This command takes no positional arguments - it lists everything you can see.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--all",
            "description": "Include archived organisations."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `name`, `id`, `plan`, `state`, `role` and `members`, with `billing_provider`, `max_free`, `max_paid`, `privatelink`, `locked` and `archived_at` shown only under `--wide`.

```bash
surreal cloud org list
```

```text title="Output"
NAME      ID                    PLAN    STATE   YOUR ROLE  MEMBERS
acme      67upif0m8sh1cn1p2c8t  scale   active  owner            7
contoso   6a2k9lqzt4v8bn3m1x5c  free    active  member           2
```

```bash title="Just the names, for a loop"
surreal cloud org list --columns name --no-header
```

Archived organisations are left out of the table. When any were hidden, a note on stderr says how many and suggests `--all`. `--json` always carries every organisation, because the filter only affects the table. An empty list is a success: exit `0` and an empty array under `--json`.

## surreal cloud org get {#org-get}

Show one organisation.

<Synopsis>
surreal cloud org get [OPTIONS] [ORG]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[ORG]",
            "description": "The organisation, by id or name. Defaults to the resolved one."
        }
    ]}
/>

This command has no options of its own.

```bash
surreal cloud org get acme
```

```bash title="Whichever organisation is in force"
surreal cloud org get
```

The detail view reports the id, plan, state, your role, the member count, free and paid instance allowances, the billing provider and details, the payment method and PrivateLink availability. Two further fields appear only when they apply: `Resources Locked` when true, and `Archived` when the organisation has been archived. Backup retention bounds are shown when the plan defines them.

## surreal cloud org create {#org-create}

Create an organisation.

<Synopsis>
surreal cloud org create [OPTIONS] <NAME>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<NAME>",
            "required": true,
            "description": "A name for the organisation. Between 1 and 30 characters."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--use",
            "description": "Remember this organisation as the default for this profile, as if `org use` had been run afterwards."
        }
    ]}
/>

```bash
surreal cloud org create acme --use
```

**Refusals.** A name outside 1 to 30 characters, measured after trimming, is a usage error, exit `2`, raised before any request. The same limit applies to [`org update --name`](#org-update).

On success the command prints the new organisation's detail view. With `--use`, it then records the organisation for the current profile and says so.

## surreal cloud org update {#org-update}

Rename an organisation.

<Synopsis>
surreal cloud org update [OPTIONS] --name <NAME> [ORG]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[ORG]",
            "description": "The organisation, by id or name. Defaults to the resolved one."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--name",
            "value": "<NAME>",
            "required": true,
            "description": "The new name."
        }
    ]}
/>

```bash
surreal cloud org update acme --name "Acme Corporation"
```

The name has the same 1 to 30 character limit as [`org create`](#org-create), checked locally with exit `2` before any request. Renaming does not change the organisation's id, so scripts that address it by id are unaffected.

## surreal cloud org archive {#org-archive}

Archive an organisation.

<Synopsis>
surreal cloud org archive [OPTIONS] [ORG]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[ORG]",
            "description": "The organisation, by id or name. Defaults to the resolved one."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Archive without confirming."
        }
    ]}
/>

```bash
surreal cloud org archive contoso
```

**Refusals.** The command confirms first unless `--force` or the global `--yes` is given. The question names the organisation and how many instances it holds, and archiving cannot be undone from the CLI because there is no unarchive command. In a non-interactive session with neither flag, it exits `2` having sent nothing. Declining the prompt prints `Nothing was archived.` and exits `0`.

An organisation that is already archived is a no-op that exits `0`. It says so and stops before the archive request:

```text
`contoso` is already archived.
```

The document is still emitted, so `org archive <name> --json` answers with the organisation in both cases rather than changing shape with remote state the caller has not seen.

Archived organisations are hidden from [`org list`](#org-list) unless you pass `--all`.

## surreal cloud org use {#org-use}

Remember an organisation as the default for this profile.

<Synopsis>
surreal cloud org use [OPTIONS] [ORG]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[ORG]",
            "description": "The organisation, by id or name. Omit to choose interactively."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--clear",
            "description": "Forget the remembered organisation instead. Conflicts with `[ORG]`."
        }
    ]}
/>

```bash title="Remember one"
surreal cloud org use acme
```

```bash title="Pick from a list"
surreal cloud org use
```

```bash title="Forget it again"
surreal cloud org use --clear
```

The choice is written to the `[profile.<name>.context]` table of `config.toml`, which sits below a hand-written `org` key in the [precedence chain](/docs/reference/cli/cloud/global-flags.md#the-precedence-chain) - so setting `org` yourself is never overwritten by this command.

**Refusals.** Omitting `[ORG]` in a non-interactive session exits `2` and names the flags that would have settled it. The command warns when `SURREAL_CLOUD_ORG` is set, because the variable outranks what it just wrote.

`org use` remembers an organisation *within* a profile. [`context use`](/docs/reference/cli/cloud/commands/context.md#context-use) switches the whole profile.

## surreal cloud org roles {#org-roles}

List the roles an organisation can assign.

<Synopsis>
surreal cloud org roles [OPTIONS] [ORG]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[ORG]",
            "description": "The organisation, by id or name. Defaults to the resolved one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `name` and `permissions`.

```bash
surreal cloud org roles
```

The role vocabulary is per organisation, which is why [`team invite`](/docs/reference/cli/cloud/commands/team.md#team-invite) and [`team update`](/docs/reference/cli/cloud/commands/team.md#team-update) point here rather than validating a `--role` value locally.

## surreal cloud org permissions {#org-permissions}

Show what you are permitted to do in an organisation.

<Synopsis>
surreal cloud org permissions [OPTIONS] [ORG]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[ORG]",
            "description": "The organisation, by id or name. Defaults to the resolved one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `resource` and `action`.

```bash
surreal cloud org permissions --org acme
```

This is the authorisation model to consult when a command answers 403. [`auth scopes`](/docs/reference/cli/cloud/commands/auth.md#auth-scopes) reports the credential's own scopes, which are a different thing.

## surreal cloud org usage {#org-usage}

Show resource usage across an organisation's instances.

<Synopsis>
surreal cloud org usage [OPTIONS] [ORG]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[ORG]",
            "description": "The organisation, by id or name. Defaults to the resolved one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `instance_id`, `metric_type`, `compute_hours`, `disk_used_bytes`, `period_start` and `period_end`, with `instance_type` and `source` under `--wide`.

```bash title="Compute hours per instance, largest first"
surreal cloud org usage --sort compute_hours --reverse
```

For one instance rather than all of them, use [`instance usage`](/docs/reference/cli/cloud/commands/instance.md#instance-usage).

## surreal cloud org spend {#org-spend}

Show an organisation's billed spend.

<Synopsis>
surreal cloud org spend [OPTIONS] [ORG]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[ORG]",
            "description": "The organisation, by id or name. Defaults to the resolved one."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--period",
            "value": "<MM-YYYY>",
            "description": "The billing period, as MM-YYYY. Defaults to the current month."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `description`, `resource`, `amount`, `units` and `effective_at`, with `instance_id`, `instance_type` and `cloud_usage_units` under `--wide`.

```bash title="Last month's ledger"
surreal cloud org spend --period 07-2026
```

```bash title="The ten largest lines"
surreal cloud org spend --sort amount --reverse --limit 10
```

A total is printed on stderr in text modes, computed before `--limit` is applied, so the total is always the whole bill even when the table is truncated.

**Refusals.** The period is validated locally, exit `2`, and an ISO-ordered value gets a correction rather than a bare rejection:

```text
`2026-03` looks like YYYY-MM. This API wants the month first: use 03-2026.
```

Under `--json`, amounts stay as the API's integers, in millicents, in a field the API spells `amount_millcents`. Formatting them as currency is left to the table view.

## surreal cloud org plans {#org-plans}

List the plans available to an organisation.

<Synopsis>
surreal cloud org plans [OPTIONS] [ORG]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[ORG]",
            "description": "The organisation, by id or name. Defaults to the resolved one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags). Column ids are `name`, `id`, `regions` and `instance_types`, with `description` under `--wide`.

```bash
surreal cloud org plans
```

This is the organisation-scoped answer, and the one to trust before an [`instance create`](/docs/reference/cli/cloud/commands/instance.md#instance-create). The [`catalog`](/docs/reference/cli/cloud/commands/catalog.md) commands list what the platform offers globally, which is always the wider set.

## Related pages

- [`instance` commands](/docs/reference/cli/cloud/commands/instance.md) - the instances an organisation owns
- [`team` commands](/docs/reference/cli/cloud/commands/team.md) - its members
- [`catalog` commands](/docs/reference/cli/cloud/commands/catalog.md) - platform-wide regions, types and versions
- [Global flags](/docs/reference/cli/cloud/global-flags.md) - `--org` and the precedence chain
- [Overview](/docs/reference/cli/cloud.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/db.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/team

# surreal cloud team

Reference for surreal cloud team - list and inspect organisation members, invite someone, change a member's role, and remove a membership. Members are matched by username or user id; roles come from org roles.

`surreal cloud team` manages organisation members: the people who already belong. Invitations that have been sent but not accepted live in [`invite`](/docs/reference/cli/cloud/commands/invite.md), and `team list` deliberately does not fold them in - a pending invitation is not a member.

<Synopsis>
surreal cloud team <COMMAND> [OPTIONS]
surreal cloud teams <COMMAND> [OPTIONS]
</Synopsis>

| Verb | Purpose | Alias |
| --- | --- | --- |
| [`list`](#team-list) | List the members of an organisation | `ls` |
| [`get`](#team-get) | Show one member | |
| [`invite`](#team-invite) | Invite someone to an organisation | |
| [`update`](#team-update) | Change a member's role | |
| [`remove`](#team-remove) | End someone's membership of an organisation | `rm` |

Two verbs here break the house grammar on purpose. `remove` is not `delete`, because `delete` invites the reading that the *person* is deleted rather than their membership. `invite` exists because sending one is how a team gains a member and no house verb covers it - and it shares [`invite create`](/docs/reference/cli/cloud/commands/invite.md#invite-create)'s implementation rather than copying it.

The API has no route for a single member, so `get`, `update` and `remove` fetch the member list and match locally, by user id or username, with did-you-mean suggestions when nothing matches. The display name is never a key, because it is neither unique nor stable.

This group declares the scopes `read:cloud` and `write:cloud-organization`, the same pair as [`org`](/docs/reference/cli/cloud/commands/org.md) and [`invite`](/docs/reference/cli/cloud/commands/invite.md), because the API has no separate scope for membership. A personal access token cannot use the mutating verbs: see [Authentication](/docs/reference/cli/cloud/authentication.md#what-a-personal-access-token-cannot-do).

## surreal cloud team list {#team-list}

List the members of an organisation.

<Synopsis>
surreal cloud team list [OPTIONS]
</Synopsis>

This command takes no positional argument. The organisation comes from `--org` and the [precedence chain](/docs/reference/cli/cloud/global-flags.md#the-precedence-chain).

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags) and nothing else. Column ids are `username`, `name` and `role`, with `user_id` and `profile_picture` under `--wide`.

```bash
surreal cloud team list
```

```text title="Output"
USERNAME             NAME            ROLE
ana@acme.example     Ana Silva       owner
bo@acme.example      Bo Nakamura     admin
cai@acme.example     Cai Oduya       member
```

```bash title="Owners and admins only"
surreal cloud team list --json | jq -r '.[] | select(.role != "member") | .username'
```

## surreal cloud team get {#team-get}

Show one member.

<Synopsis>
surreal cloud team get [OPTIONS] <MEMBER>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<MEMBER>",
            "required": true,
            "description": "The member, by username or user id."
        }
    ]}
/>

This command has no options of its own.

```bash
surreal cloud team get bo@acme.example
```

A reference that does not match any member exits `5`, and lists the near matches it found. The detail view shows the username, role, user id and organisation id.

## surreal cloud team invite {#team-invite}

Invite someone to an organisation.

<Synopsis>
surreal cloud team invite [OPTIONS] --role <ROLE> <EMAIL>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<EMAIL>",
            "required": true,
            "description": "The email address to invite."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--role",
            "value": "<ROLE>",
            "required": true,
            "description": "The role to grant. Run `surreal cloud org roles` for the ones this organisation defines."
        }
    ]}
/>

```bash
surreal cloud team invite dee@acme.example --role member
```

The answer is the invitation, not a member - the person appears in [`team list`](#team-list) only once they accept. Track it in the meantime with [`invite list`](/docs/reference/cli/cloud/commands/invite.md#invite-list).

**Refusals**, exit `2`, before any request. The address validator is deliberately shallow - an `@` with something either side and no internal whitespace - because the API is the authority on deliverability:

```text
`dee.acme.example` is not an email address: it has no `@`.
```

The role vocabulary is per organisation, so only an empty value is refused locally:

```text
A role cannot be empty. Run `surreal cloud org roles` to see the ones this organization defines.
```

A role the API rejects as invalid is annotated with a pointer to [`org roles`](/docs/reference/cli/cloud/commands/org.md#org-roles) - but only for that class of error, never for a 403 or a rate limit, where the role is not the problem.

An invitation is not retried automatically, because a repeat would send a second email with a second working code.

## surreal cloud team update {#team-update}

Change a member's role.

<Synopsis>
surreal cloud team update [OPTIONS] --role <ROLE> <MEMBER>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<MEMBER>",
            "required": true,
            "description": "The member, by username or user id."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--role",
            "value": "<ROLE>",
            "required": true,
            "description": "The new role. Run `surreal cloud org roles` for the ones this organisation defines."
        }
    ]}
/>

```bash
surreal cloud team update cai@acme.example --role admin
```

The command prints the updated member. The role is compared exactly as written: if the member already holds it, the command prints a note that nothing changed, sends no request and exits `0`.

## surreal cloud team remove {#team-remove}

End someone's membership of an organisation.

<Synopsis>
surreal cloud team remove [OPTIONS] <MEMBER>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<MEMBER>",
            "required": true,
            "description": "The member, by username or user id."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Remove without confirming."
        }
    ]}
/>

```bash title="With a confirmation"
surreal cloud team remove cai@acme.example
```

```bash title="From an offboarding script"
surreal cloud team remove cai@acme.example --force --json
```

This ends a membership. It does not delete the person's SurrealDB account, and it does not touch anything they created.

**Refusals.** The confirmation names the person and the organisation, and says that the account is not deleted. Declining prints `Nothing was changed.` and exits `0`. Removing yourself prints a warning first, because only another member can invite you back. In a non-interactive session without `--force` or `--yes`, it exits `2` having sent nothing.

## Related pages

- [`invite` commands](/docs/reference/cli/cloud/commands/invite.md) - pending invitations
- [`org roles`](/docs/reference/cli/cloud/commands/org.md#org-roles) - the roles this organisation defines
- [`org permissions`](/docs/reference/cli/cloud/commands/org.md#org-permissions) - what a role lets you do
- [Overview](/docs/reference/cli/cloud.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/db.md) - database users, which are a separate concept from organisation members

---

Source: https://surrealdb.com/docs/reference/cli/cloud/commands/token

# surreal cloud token

Reference for surreal cloud token - listing, creating and revoking personal access tokens, and listing the scopes a token can be granted. Covers the login-session requirement, the secret guard, expiry, and the 10-minute revocation lag.

`surreal cloud token` manages personal access tokens: the `sdbp_…` credentials that authenticate CI jobs and automation against the control plane.

<Synopsis>
surreal cloud token <COMMAND> [OPTIONS]
surreal cloud tokens <COMMAND> [OPTIONS]
</Synopsis>

| Verb | Purpose | Alias |
| --- | --- | --- |
| [`list`](#token-list) | List your personal access tokens | `ls` |
| [`create`](#token-create) | Create a personal access token | |
| [`delete`](#token-delete) | Revoke a personal access token | `rm` |
| [`scopes`](#token-scopes) | List the scopes a token can be granted | |

> [!IMPORTANT]
> Every verb in this group needs an interactive login session. A personal access token is refused locally, before any request is sent, and exits `4`. This is deliberate: a leaked token must not be able to mint its own replacements, nor revoke the one an operator would use to clean up after it. The refusal is decided by the credential's kind rather than by its scopes, so no token can be minted carrying something that lifts it.

```text title="What a token gets instead"
Managing personal access tokens needs an interactive login session, so nothing was sent.

Sign in with:  surreal cloud auth login
```

Being signed out is a different answer with a different code: `3`, not `4`. Sign in with [`auth login`](/docs/reference/cli/cloud/commands/auth.md#auth-login).

These tokens are not database credentials. For a token a SurrealDB client can authenticate with, use [`instance token`](/docs/reference/cli/cloud/commands/instance.md#instance-token).

## surreal cloud token list {#token-list}

List your personal access tokens.

<Synopsis>
surreal cloud token list [OPTIONS]
</Synopsis>

This command takes no positional arguments.

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags) and nothing else. Column ids are `label`, `id`, `scopes`, `created_at` and `expires_at`, with `user_id` under `--wide`.

```bash
surreal cloud token list
```

```text title="Output"
LABEL              ID                    SCOPES                  CREATED       EXPIRES
build runner       67upif0m8sh1cn1p2c8t  read:cloud              2 months ago  in 4 days
nightly export     6a2k9lqzt4v8bn3m1x5c  read:cloud              3 days ago    never
```

```bash title="Label and expiry of every token"
surreal cloud token list --json | jq -r '.[] | .label + " " + .expires_at'
```

Expired tokens are listed as the API sends them, with a past expiry, because they are the ones to find when a job has stopped authenticating.

Only the label, the id, the scopes and the timestamps are stored - the secret itself exists once, at creation. A token with no expiry shows `never` in the table and carries the `9999-12-31T23:59:59Z` sentinel under `--json`.

## surreal cloud token create {#token-create}

Create a personal access token.

<Synopsis>
surreal cloud token create [OPTIONS] <LABEL>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<LABEL>",
            "required": true,
            "description": "What this token is for, shown in every listing. Between 1 and 120 characters."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--scope",
            "value": "<SCOPE>",
            "description": "A scope to grant. Repeat the flag to grant several, up to 32."
        },
        {
            "name": "--expires-in",
            "value": "<DAYS>",
                        "description": "Days until the token expires, between 1 and 365, or the word `never`. When omitted, the API applies its own 30 days."
        },
        {
            "name": "--reveal",
            "description": "Print the secret even when stdout is a terminal."
        }
    ]}
/>

The secret is returned exactly once. Capture it on creation or create another one.

```bash title="Capture the secret into a variable"
TOKEN=$(surreal cloud token create "build runner" --scope read:cloud | tail -1)
```

```bash title="Several scopes, and a shorter life"
surreal cloud token create "nightly export" \
    --scope read:cloud \
    --scope write:cloud-instances \
    --expires-in 90 \
    | tail -1 > /run/secrets/surreal-cloud-token
```

Stdout carries a detail view of the token with the secret removed, then **the bare secret as the last line** - so `| tail -1` is the whole capture recipe. The success line goes to stderr. The follow-up command in the view names the token's id rather than its label, because a label may contain spaces.

> [!WARNING]
> This is the one command in the whole CLI with no `--json` form. Passing `--json` is a usage error, exit `2`, raised before anything is minted.

```text title="Passing --json"
`token create` has no --json form. The secret is returned exactly once, and it can neither go inside
the document nor share stdout with it.
Run it without --json and take the last line:
  surreal cloud token create <label> | tail -1
```

**Refusals**, all exit `2` and all before the request, checked in this order:

- `--json`, as above.
- stdout is a terminal and `--reveal` was not passed:

```text
Refusing to print a secret to a terminal, where it would stay in your scrollback.
Pipe it:  surreal cloud token create <label> | tail -1
Or pass --reveal if you meant to see it.
```

- A label outside 1 to 120 characters, counted after trimming.
- More than 32 scopes, after repeated `--scope` values are collapsed into one: `A token carries at most 32 scopes; 40 were given.`
- An `--expires-in` that is neither a number of days from 1 to 365 nor the word `never`. `never` is matched without regard to case.

**Warnings** before the request, on stderr:

- `--expires-in never` warns, because the API's own schema calls a non-expiring token discouraged.
- No `--scope` at all notes that the token will be permitted nothing, and points at [`token scopes`](#token-scopes).

No command changes a token's scopes or expiry afterwards. To change either, create a new token and [revoke](#token-delete) the old one.

Scopes are not pre-checked against the catalogue. An ungrantable scope answers 403 naming the problem, which is more useful than the bare 400 the API gives for everything else.

## surreal cloud token delete {#token-delete}

Revoke a personal access token.

<Synopsis>
surreal cloud token delete [OPTIONS] <TOKEN>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<TOKEN>",
            "required": true,
            "description": "The token, by id or label."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Revoke without confirming."
        }
    ]}
/>

```bash title="By label"
surreal cloud token delete "build runner"
```

```bash title="By id, from a script"
surreal cloud token delete 67upif0m8sh1cn1p2c8t --force --json
```

**Refusals.** The confirmation names the token by label and id, and the lookup lists your tokens first. Declining prints `Nothing was revoked.` and exits `0`. Labels are not unique, so a label shared by two tokens is reported as ambiguous with both ids rather than guessed at. In a non-interactive session without `--force` or `--yes`, it exits `2` having sent nothing.

> [!NOTE]
> Revocation is not instantaneous everywhere. The gateway caches token exchanges for a few minutes, so a revoked token may keep working for up to about ten minutes, and the success message says so. Treat revocation as the start of the process when responding to a leak, and rotate whatever the token could reach.

## surreal cloud token scopes {#token-scopes}

List the scopes a token can be granted.

<Synopsis>
surreal cloud token scopes [OPTIONS]
</Synopsis>

This command takes no positional arguments.

Accepts the [list presentation flags](/docs/reference/cli/cloud/output-and-exit-codes.md#list-presentation-flags) and nothing else. Column ids are `id`, `category` and `label`, with `description` under `--wide`.

```bash
surreal cloud token scopes
```

```bash title="Just the ids, to paste into a create"
surreal cloud token scopes --columns id --no-header
```

This lists what a token *can* be granted. [`auth scopes`](/docs/reference/cli/cloud/commands/auth.md#auth-scopes) lists what the credential in hand actually carries.

## Related pages

- [Authentication](/docs/reference/cli/cloud/authentication.md#personal-access-tokens) - how a token is supplied, and what it cannot do
- [`auth` commands](/docs/reference/cli/cloud/commands/auth.md) - signing in with a login session
- [`instance token`](/docs/reference/cli/cloud/commands/instance.md#instance-token) - database tokens, which are a different credential
- [Output and exit codes](/docs/reference/cli/cloud/output-and-exit-codes.md#the-secret-guard) - the secret guard
- [Overview](/docs/reference/cli/cloud.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/db.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/cloud/global-flags

# surreal cloud global flags

The sixteen flags every surreal cloud command accepts, the environment variable behind each one, the precedence chain that resolves them, the duration syntax, and the deprecated SURREALCTL_ aliases.

Sixteen flags are accepted by every `surreal cloud` command. They may appear anywhere after `cloud` - before the group, between the group and the verb, or after the verb - so all three of these are the same invocation:

```bash
surreal cloud --json instance list
surreal cloud instance --json list
surreal cloud instance list --json
```

`--help` groups them under four headings, and this page keeps those groups.

## Context

<OptionsTable
    title="Context flags"
    options={[
        {
            "name": "--profile",
            "value": "<PROFILE>",
            "default": "default",
            "env": "SURREAL_CLOUD_PROFILE",
            "description": "The configuration profile to use. A profile bundles a credential, an API base URL and a set of configuration values. When the flag is absent, the file's `active_profile` applies, then `default`."
        },
        {
            "name": "--config",
            "value": "<PATH>",
            "env": "SURREAL_CLOUD_CONFIG",
            "description": "Path to the configuration file, or to the directory that holds it. The directory also becomes the credential directory."
        },
        {
            "name": "--org",
            "value": "<ORG>",
            "env": "SURREAL_CLOUD_ORG",
            "description": "The organisation to operate on, by id or name."
        }
    ]}
/>

## Authentication

<OptionsTable
    title="Authentication flags"
    options={[
        {
            "name": "--token",
            "value": "<TOKEN>",
            "env": "SURREAL_CLOUD_TOKEN",
            "description": "Personal access token to authenticate with. Conflicts with `--token-file`, and is never persisted."
        },
        {
            "name": "--token-file",
            "value": "<PATH>",
            "env": "SURREAL_CLOUD_TOKEN_FILE",
            "description": "Read the personal access token from a file, or `-` for standard input. Conflicts with `--token`."
        },
        {
            "name": "--api",
            "value": "<URL>",
            "default": "https://api.surrealdb.com",
            "env": "SURREAL_CLOUD_API",
            "description": "Base URL of the SurrealDB API. Must be absolute, `http` or `https`, name a host, and carry no path."
        }
    ]}
/>

`--token` has no short form on purpose: `-t` reads as `--type` on the instance commands, and a credential belongs in the environment or a file rather than in `argv`. See [Authentication](/docs/reference/cli/cloud/authentication.md#personal-access-tokens).

A base URL with a path of its own is refused as a usage error, because the client appends its own routes and the path would be silently prepended to every one of them:

```text
Ensure the API base has no path, such as https://api.surrealdb.com
```

## Output

<OptionsTable
    title="Output flags"
    options={[
        {
            "name": "--json",
            "short": "-j",
            "env": "SURREAL_CLOUD_JSON",
            "description": "Emit machine-readable JSON on stdout. See the output contract for what is guaranteed."
        },
        {
            "name": "--plain",
            "env": "SURREAL_CLOUD_PLAIN",
            "description": "Disable tables, spinners and relative times. Output becomes width-independent."
        },
        {
            "name": "--color",
            "value": "<WHEN>",
            "default": "auto",
            "description": "When to use colour in output: `auto`, `always` or `never`."
        },
        {
            "name": "--quiet",
            "short": "-q",
            "description": "Suppress progress and informational output. The data payload on stdout is unaffected."
        },
        {
            "name": "--yes",
            "short": "-y",
            "env": "SURREAL_CLOUD_YES",
            "description": "Assume yes for every confirmation in this invocation."
        },
        {
            "name": "--no-input",
            "env": "SURREAL_CLOUD_NO_INPUT",
            "description": "Never prompt for input; fail instead. Also spelled `--non-interactive`."
        }
    ]}
/>

`--color` is the one flag with no `SURREAL_CLOUD_*` variable. Colour also honours the `NO_COLOR`, `FORCE_COLOR` and `CLICOLOR_FORCE` conventions.

`--json` and `--plain` are independent axes: `--json` chooses the encoding, `--plain` the decoration. Passing both is accepted and `--plain` is ignored, because CI scripts pass both and erroring on that is hostile.

## Logging

`--help` files the request timeout and the retry budget under this heading alongside the log level.

<OptionsTable
    title="Logging flags"
    options={[
        {
            "name": "--log",
            "value": "<FILTER>",
            "default": "warn",
            "env": "SURREAL_CLOUD_LOG",
            "description": "The logging level, or a full set of filter directives."
        },
        {
            "name": "--debug",
            "env": "SURREAL_CLOUD_DEBUG",
            "description": "Log every API request and response to stderr, with the credential reduced to a digest. Also reports which precedence layer supplied each resolved value."
        },
        {
            "name": "--timeout",
            "value": "<DURATION>",
            "default": "30s",
            "env": "SURREAL_CLOUD_TIMEOUT",
            "description": "Maximum time to wait for a single API request."
        },
        {
            "name": "--retries",
            "value": "<N>",
            "default": "3",
            "env": "SURREAL_CLOUD_RETRIES",
            "description": "How many times to retry a failed request. The number of attempts is one more than this."
        }
    ]}
/>

The effective per-request timeout is never shorter than 21 seconds, because the API's own upstream timeout is 20 seconds and giving up sooner abandons requests that are still being worked on.

## The precedence chain

Every value that can come from more than one place is resolved once, before the command runs, in this order - highest first:

1. A command-line flag
2. An environment variable
3. The profile in `config.toml`
4. The persisted context, written by [`org use`](/docs/reference/cli/cloud/commands/org.md#org-use)
5. Your account's default organisation, as the API reports it
6. The only candidate, when exactly one exists
7. An interactive picker, when a person is present
8. An error naming the flag that would have settled it

Layers 5 to 7 apply to the organisation specifically. Exactly one candidate is not a choice, so it is used silently; several candidates in a non-interactive session is an error rather than a guess:

```text
Several organizations are available and none was chosen.
Pass --org, set SURREAL_CLOUD_ORG, or run `surreal cloud org use <name>`.

Available:
  acme
  contoso
```

`--debug` prints which layer won each value, and [`context show`](/docs/reference/cli/cloud/commands/context.md#context-show) reports it as part of its answer:

| Layer | Reported as |
| --- | --- |
| Command-line flag | `command-line flag` |
| Environment variable | `environment variable` |
| Profile in `config.toml` | `profile in config.toml` |
| Persisted context | `persisted context (org use)` |
| Account default | `your account's default organization` |
| Single candidate | `the only candidate` |
| Interactive picker | `interactive choice` |

An organisation reference that already looks like an id is used with no lookup at all. A name or slug costs one request to resolve, and an unrecognised one is answered with did-you-mean candidates.

> [!NOTE]
> A configured `api` value applies only when `--api` was not given. Because the flag has a default, the CLI cannot distinguish "absent" from "passed exactly the default", so passing `--api https://api.surrealdb.com` explicitly leaves the configured value unused.

## Duration syntax

`--timeout` and `--wait-timeout` accept the same syntax. A bare number means seconds.

| Suffix | Unit |
| --- | --- |
| *(none)*, `s`, `sec`, `secs`, `second`, `seconds` | Seconds |
| `ms` | Milliseconds |
| `m`, `min`, `mins`, `minute`, `minutes` | Minutes |
| `h`, `hr`, `hrs`, `hour`, `hours` | Hours |
| `d`, `day`, `days` | Days |

```bash
surreal cloud --timeout 90 instance list
surreal cloud instance create api --type shared-1 --region aws-euw1 --wait-timeout 5m
```

## The `SURREAL_CLOUD_` prefix

Every environment variable this component reads is prefixed `SURREAL_CLOUD_`, and never plain `SURREAL_`.

Reusing the database's prefix would be harmful. `SURREAL_TOKEN` in a shell means a *database* JWT for one instance; reading it as a control-plane credential would send database tokens to `api.surrealdb.com` on every command.

The only `SURREAL_*` variables involved are ones `surreal cloud` *sets* for the database process during the handoff in [`instance sql`, `import` and `export`](/docs/reference/cli/cloud/commands/instance.md#instance-sql): `SURREAL_TOKEN`, and `SURREAL_NAMESPACE` and `SURREAL_DATABASE` when given.

### Variables that mirror a flag

| Variable | Flag |
| --- | --- |
| `SURREAL_CLOUD_PROFILE` | `--profile` |
| `SURREAL_CLOUD_CONFIG` | `--config` |
| `SURREAL_CLOUD_ORG` | `--org` |
| `SURREAL_CLOUD_TOKEN` | `--token` |
| `SURREAL_CLOUD_TOKEN_FILE` | `--token-file` |
| `SURREAL_CLOUD_API` | `--api` |
| `SURREAL_CLOUD_JSON` | `--json` |
| `SURREAL_CLOUD_PLAIN` | `--plain` |
| `SURREAL_CLOUD_YES` | `--yes` |
| `SURREAL_CLOUD_NO_INPUT` | `--no-input` |
| `SURREAL_CLOUD_LOG` | `--log` |
| `SURREAL_CLOUD_DEBUG` | `--debug` |
| `SURREAL_CLOUD_TIMEOUT` | `--timeout` |
| `SURREAL_CLOUD_RETRIES` | `--retries` |

### Variables with no flag

| Variable | Effect |
| --- | --- |
| `SURREAL_CLOUD_CONFIG_DIR` | Overrides the configuration and credential directory |
| `SURREAL_CLOUD_AGENT` | `1` or `0`, forcing agent detection on or off |
| `SURREAL_CLOUD_SESSION_TOKEN` | Supplies an already-minted Cloud session token, for support reproduction |
| `SURREAL_CLOUD_AUTH_CLIENT_ID` | Overrides the OAuth client id, for a non-production tenant |
| `SURREAL_CLOUD_AUTH_ISSUER` | Overrides the OAuth issuer, for a non-production tenant |

### Launcher variables

`surreal cloud` runs under the launcher, which sets `SURREAL_CLI_LAUNCHER` to its own path so that the handoff commands can reach the database through it. `SURREAL_CLI_HOME` (default `~/.surrealdb`) moves the directory that holds both the launcher's files and the `cloud` configuration directory, and `SURREAL_CLI_OFFLINE=1` forbids all network use by the launcher. The [launcher reference](/docs/reference/cli/launcher.md) documents these.

### Conventions it honours

| Variable | Effect |
| --- | --- |
| `NO_COLOR` | Disables colour |
| `FORCE_COLOR`, `CLICOLOR_FORCE` | Enables colour, even when piped |
| `CI` | Selects plain, structural output. `CI=false`, `CI=0` and an empty value all mean *not* CI |
| `TERM=dumb` | Selects plain output with no colour |
| `SSH_CONNECTION`, `SSH_TTY` | Skips the browser login flow |
| `HOME`, `SURREAL_CLI_HOME` | Locate the configuration directory |
| `HTTPS_PROXY`, `NO_PROXY` | Proxy the API connection |
| `VISUAL`, `EDITOR` | Used by [`config edit`](/docs/reference/cli/cloud/commands/config.md#config-edit), in that order |

## Deprecated SURREALCTL_ aliases

Before it became `surreal cloud`, the tool was `surrealctl` and every variable above carried the prefix `SURREALCTL_` instead. The old names keep working, so a CI job that exports `SURREALCTL_TOKEN` does not break when it picks up the new release. The old name is consulted only when the new one is unset, and a run that uses any of them prints one warning on stderr listing each old name with its replacement:

```text
SURREALCTL_* variables are deprecated. Rename SURREALCTL_TOKEN to SURREAL_CLOUD_TOKEN.
```

Two variables are not a plain prefix swap. `SURREALCTL_CLOUD_TOKEN` is now `SURREAL_CLOUD_SESSION_TOKEN`, because `SURREAL_CLOUD_CLOUD_TOKEN` would read as a typo. `SURREALCTL_SURREAL_BINARY` and the `surreal_binary` configuration key no longer exist, because the database is reached only through the launcher. Variables under the old prefix that never had a meaning are not mentioned.

## Configuration file equivalents

Four values can be persisted per profile with [`config set`](/docs/reference/cli/cloud/commands/config.md#config-set). Each is outranked by its environment variable, and `config set` warns when that variable is exported.

| Key | Environment variable |
| --- | --- |
| `org` | `SURREAL_CLOUD_ORG` |
| `api` | `SURREAL_CLOUD_API` |
| `json` | `SURREAL_CLOUD_JSON` |
| `plain` | `SURREAL_CLOUD_PLAIN` |

## Related pages

- [Output and exit codes](/docs/reference/cli/cloud/output-and-exit-codes.md) - what `--json`, `--plain` and `--quiet` actually produce
- [`config` commands](/docs/reference/cli/cloud/commands/config.md) - reading and writing the configuration file
- [`context` commands](/docs/reference/cli/cloud/commands/context.md) - switching profiles
- [surreal cloud reference](/docs/reference/cli/cloud.md) - the rest of the reference
- [Database environment variables](/docs/reference/cli/db/environment-variables.md) - the `SURREAL_*` set this component never reads

---

Source: https://surrealdb.com/docs/reference/cli/cloud/long-running-operations

# surreal cloud long-running operations

How surreal cloud waits for an instance to settle - the wait flags, which commands wait by default, the polling schedule, rate-limit handling, and exit code 10.

Creating, resizing, pausing, resuming and deleting an instance are asynchronous. The API accepts the request, answers `202`, and the resource changes state over the following seconds or minutes.

`surreal cloud` waits for that to finish by default, so the next line of a script can connect to the instance it just created.

## The wait flags

Five commands carry all three flags: [`instance create`](/docs/reference/cli/cloud/commands/instance.md#instance-create), [`instance update`](/docs/reference/cli/cloud/commands/instance.md#instance-update), [`instance delete`](/docs/reference/cli/cloud/commands/instance.md#instance-delete), [`instance pause`](/docs/reference/cli/cloud/commands/instance.md#instance-pause) and [`instance resume`](/docs/reference/cli/cloud/commands/instance.md#instance-resume).

<OptionsTable
    title="Wait flags"
    options={[
        {
            "name": "--wait",
            "description": "Wait for the operation to finish. On by default, so this flag is only needed to override an earlier `--no-wait`."
        },
        {
            "name": "--no-wait",
            "description": "Return as soon as the API accepts the request."
        },
        {
            "name": "--wait-timeout",
            "value": "<DURATION>",
            "default": "15m",
            "description": "How long to wait before giving up. Accepts the standard duration syntax, such as `90s` or `5m`."
        }
    ]}
/>

`--wait` and `--no-wait` override each other, so the last one on the command line wins. That makes shell aliases and wrapper scripts composable: appending `--wait` undoes an alias that sets `--no-wait`, without having to know what the alias contained.

Waiting is on in **every** output mode - rich, plain, CI and `--json` alike. A terminal-dependent default would break the obvious script and would mean `CI` changed semantics rather than presentation.

The fifteen-minute default is chosen so that a normal operation never reaches it and a stuck one does not hold a CI job for an hour: a shared-tier instance is ready in about ninety seconds, and a version upgrade on a large instance is the slow case.

Two commands that might look asynchronous are not: [`instance backup create`](/docs/reference/cli/cloud/commands/instance.md#instance-backup-create) carries no wait flags, and [`instance watch`](/docs/reference/cli/cloud/commands/instance.md#instance-watch) carries `--wait-timeout` only, because a watch is nothing but a wait.

```bash title="Create and block until ready - the default"
surreal cloud instance create api --type shared-1 --region aws-euw1
```

```bash title="Fire and forget, then pick the wait up later"
surreal cloud instance create api --type shared-1 --region aws-euw1 --no-wait
surreal cloud instance watch api
```

```bash title="Give up sooner than fifteen minutes"
surreal cloud instance update production --type production-2 --wait-timeout 5m
```

## How the wait works

The API has no job resources, no `Location` header and no idempotency key. A long operation is a `202` plus the mutated resource, so the only way to know it finished is to fetch the resource again. `surreal cloud` therefore polls the instance until it reaches the state the command asked for.

Polling backs off:

| Elapsed | Interval |
| --- | --- |
| Under 30 seconds | 2 seconds |
| 30 seconds to 2 minutes | 5 seconds |
| Over 2 minutes | 10 seconds |

Each interval carries jitter between 0.8× and 1.2× so that concurrent waiters do not convoy, and the result is capped at 15 seconds.

In plain and CI output, one line is printed per state change plus a heartbeat every 60 seconds, so a log stays readable and a stalled wait is still visibly alive. Under `--json`, transitions are [newline-delimited JSON on stderr](/docs/reference/cli/cloud/output-and-exit-codes.md#ndjson-streams) and the final document goes to stdout.

The credential is re-checked before every poll, because a long wait can outlive a Cloud session.

### Rate limiting during a wait

A `429` honours the `Retry-After` header - falling back to five seconds when the header is absent - and extends the deadline by the time it slept, so being throttled cannot itself cause a timeout.

That credit is capped at a cumulative two minutes. Without a ceiling, a poll that answered `429` every time would extend the deadline by exactly the time it slept and loop for ever, and a hang looks like a slow API rather than a bug. Two minutes absorbs any real throttle while guaranteeing the loop reaches its deadline.

### Unrecognised states

A state this build has never seen keeps the wait going and is displayed verbatim. Aborting on one would turn an addition to Cloud's vocabulary into an outage here.

What each state means depends on what the command asked for:

| Waiting for | Ready | Paused | Failed | Gone |
| --- | --- | --- | --- | --- |
| Ready | Success | Settled elsewhere | Failed | Vanished |
| Paused | Settled elsewhere | Success | Failed | Vanished |
| Deleted | Keep waiting | Keep waiting | Failed | Success |

*Settled elsewhere* is its own outcome because the instance reached a stable state the caller did not ask for, and polling on would burn the whole timeout to reach the same conclusion.

For a delete, a `404` while polling is the success condition - there is nothing left to fetch.

## Outcomes and exit codes

| Outcome | Exit code | What happened |
| --- | --- | --- |
| Succeeded | `0` | The instance reached the state the command asked for |
| Timed out | `10` | The wait gave up, and the operation is still running |
| Failed | `6` | The instance entered a failed state |
| Settled elsewhere | `6` | The instance settled in a stable state the command did not ask for |
| Vanished | `5` | The instance was removed while the command was waiting |

Each outcome closes with one sentence naming how long it took:

```text
The instance is ready after 1m 31s.
The instance entered `failed` after 2m 14s.
The instance settled in `paused` after 45s.
The instance was removed while waiting, after 12s.
Gave up after 15m; the instance is still `provisioning` and still working.
```

Exit code `10` deserves its own handling. It means the wait gave up, **not** that the operation failed:

```text
Timed out after 15m waiting for the instance to become ready.
It is still `provisioning`, and the operation is still running server-side.
```

The right response is usually to poll again on the next run rather than to roll anything back:

```bash title="Treat a timeout as 'check again later'"
surreal cloud instance create api --type shared-1 --region aws-euw1 --wait-timeout 3m
case $? in
    0)  echo "ready" ;;
    10) echo "still provisioning; the next run will pick it up" ;;
    *)  exit 1 ;;
esac
```

## Interrupting a wait

Interrupting a wait does not stop the operation - the CLI is only polling, and the change carries on server-side. Pick the state back up with [`instance get`](/docs/reference/cli/cloud/commands/instance.md#instance-get) for a snapshot, or [`instance watch`](/docs/reference/cli/cloud/commands/instance.md#instance-watch) to resume waiting.

```bash
surreal cloud instance watch api --until ready
```

The same applies to a wait that was never started. `--no-wait` and a later `instance watch` are equivalent to waiting inline, which is what makes a two-stage CI pipeline possible: provision in one job, block in the next.

> [!WARNING]
> `instance create` has no idempotency key, because the API's create route accepts none. A retried create is a second instance and a second bill. If a create times out, run [`instance list`](/docs/reference/cli/cloud/commands/instance.md#instance-list) before running it again.

## Related pages

- [`instance` commands](/docs/reference/cli/cloud/commands/instance.md) - every command that waits
- [Output and exit codes](/docs/reference/cli/cloud/output-and-exit-codes.md) - the NDJSON progress stream and the full exit-code table
- [Global flags](/docs/reference/cli/cloud/global-flags.md) - `--timeout` and `--retries`, which govern a single request rather than a wait
- [surreal cloud reference](/docs/reference/cli/cloud.md) - the rest of the reference
- [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) - for connecting to the instance once it is ready

---

Source: https://surrealdb.com/docs/reference/cli/cloud/output-and-exit-codes

# surreal cloud output and exit codes

The surreal cloud --json contract, the stdout and stderr split, NDJSON streams, the shared list presentation flags, the secret and confirmation guards, and every exit code.

`surreal cloud` is driven as often by pipelines and agents as by people. This page is the contract those callers can rely on: which stream carries what, what `--json` guarantees, and what each exit code means.

The rule is that **the exit codes are a contract, and stderr text is not.**

## The stream split

- **stdout** carries the command's data payload, and nothing else, ever.
- **stderr** carries progress, prompts, warnings, hints, errors and debug output.

So `| jq` always works, `--json > out.json` can still prompt you, and a failed command leaves stdout empty. Exactly one document is written to stdout per invocation.

A broken downstream pipe exits `0` silently, so `surreal cloud instance list | head -3` behaves as you expect.

```bash title="Capture the data, keep the diagnostics"
surreal cloud instance list --json > instances.json
```

## `--json`

**On success**, stdout carries the bare wire payload, pretty-printed with a two-space indent and no envelope. An envelope would force `.data[]` on every consumer for ever, and the stream split plus the exit code already answer *did it work*.

```bash
surreal cloud instance get production --json
```

```json title="Output"
{
  "id": "67upif0m8sh1cn1p2c8t",
  "name": "production",
  "slug": "production-6xk2",
  "state": "ready",
  "type": "shared-1",
  "region": "aws-euw1",
  "version": "3.2.4",
  "host": "production-6xk2.aws-euw1.surreal.cloud"
}
```

Because the payload is the API's own body rather than a struct assembled here, a field the API adds this morning appears in `--json` this afternoon with no new release.

**On failure**, stdout gets zero bytes and an enveloped object goes to stderr:

```json title="stderr"
{
  "kind": "conflict",
  "message": "could not pause `api`: Instance is not in a valid state",
  "status": 409,
  "code": null,
  "request_id": "01J8XYZ2QK7M4N0P8R9S1T2V3W",
  "hint": "The instance is busy with another change. Wait for it to settle, then try again.",
  "command": "surreal cloud instance get api",
  "docs": null,
  "retry_after_secs": null,
  "exit_code": 6
}
```

Every key is always present, with an explicit `null` rather than being omitted, so a consumer can index without checking first.

| Key | Type | Meaning |
| --- | --- | --- |
| `kind` | string | The error class. Never `null` |
| `message` | string | The full message chain, including the layer naming which command failed |
| `status` | integer or `null` | The HTTP status, when there was one |
| `code` | string or `null` | The API's own error code, when it sent a non-empty one |
| `request_id` | string or `null` | The request identifier to quote when reporting a problem |
| `hint` | string or `null` | What to do about it |
| `command` | string or `null` | A command to run next |
| `docs` | string or `null` | A documentation link, for authentication and rate-limit failures only |
| `retry_after_secs` | integer or `null` | From the `Retry-After` header |
| `exit_code` | integer | Matches the process exit code exactly |

One further key appears conditionally: `candidates`, an array of strings, when a resource reference could not be resolved and there were near matches to suggest.

`docs` is populated only where a link explains something. Authentication and permission failures link to the authentication documentation; rate limits link to the rate-limit documentation; everything else is `null`, because a link to a generic index in place of an explanation is worse than no link.

### The `kind` vocabulary

Eleven values, and the set is closed:

| `kind` | Exit code | Meaning |
| --- | --- | --- |
| `auth` | `3` | Not authenticated, or the credential could not be renewed |
| `forbidden` | `4` | Authenticated but not allowed |
| `not_found` | `5` | The named resource does not exist |
| `conflict` | `6` | Not in a state that allows this, or a precondition failed |
| `invalid` | `7` | The API rejected the request as invalid |
| `rate_limited` | `8` | Rate limited after the retry budget was spent |
| `upstream` | `9` | The API's upstream failed |
| `network` | `9` | The API could not be reached |
| `not_available` | `9` | The feature is not enabled for this deployment |
| `wait_timeout` | `10` | A wait gave up; the operation is still running |
| `unknown` | `1` | Unclassified |

`not_available` is HTTP 501, and it means nothing is broken - the feature is simply not enabled on this deployment.

### What `--json` guarantees

- **Additions are non-breaking and expected.** Renames, removals and type changes are breaking.
- **Output is byte-identical** regardless of `CI`, `NO_COLOR`, `TERM`, terminal width, or `--color always`. `--json` is never coloured.
- **Money stays in integer minor units** - cents, or millicents where the API uses them. Formatting happens in the view layer only.
- **Timestamps stay verbatim RFC 3339**, including the `9999-12-31T23:59:59Z` sentinel that means "never expires".
- **Presentation flags never reach it.** `--columns`, `--wide`, `--sort`, `--reverse` and `--limit` arrange a table; `--json` always emits the complete payload.
- **Relative times never appear.** "3 days ago" is a rendering, not data.

Two commands are documented exceptions. [`token create`](/docs/reference/cli/cloud/commands/token.md#token-create) has no `--json` form at all, because the secret is returned exactly once and can neither go inside the document nor share stdout with it. [`completion`](/docs/reference/cli/cloud/commands/misc.md#completion) emits a shell script rather than a view, so `--json` does not wrap it.

## Output shapes

| Shape | Produced by | Under `--json` |
| --- | --- | --- |
| **Table** | Every `list` verb, every `catalog` command, `org roles`/`permissions`/`usage`/`spend`/`plans`, `instance jwks`/`metrics`/`logs`/`usage`, `instance backup list`, `token scopes`, `auth scopes`, `config list`, `context list`, `status` | A JSON array |
| **Detail** | Every `get`, `create`, `update`, `delete`, `archive` and `use`, plus `whoami`, `version`, `context show`, `open`, `instance estimate`, `instance capabilities get`/`set`, `instance backup create`, `instance backup policy get`/`set` | The wire body |
| **Scalar** | `instance endpoint`, `config get`, `config path` | The single value |
| **Secret** | `instance token`, `token create` | A one-field document, except `token create`, which refuses `--json` |
| **Composite** | `instance status` - a phase plus a snapshot table, as one document | The whole status body |
| **NDJSON on stdout** | `instance watch`, `instance logs --follow` | One compact object per line |
| **Raw bytes** | `completion`, `api --raw` | Written straight through |

A scalar is the value alone plus a newline, byte-identical in every text mode, with no label and no styling - so `$(…)` captures exactly the value:

```bash
ENDPOINT=$(surreal cloud instance endpoint production)
```

`config get` on an unset key writes zero bytes, so `[ -z "$(surreal cloud config get org)" ]` and `wc -l` agree with each other.

## NDJSON streams

A command that waits reports its progress as newline-delimited JSON on **stderr**, then writes one final document to stdout. The split is what keeps "exactly one document on stdout" true for a command that reports progress.

```json title="stderr, during instance create --json"
{"event":"started","resource":"instance","goal":"ready","state":"pending"}
{"event":"transition","resource":"instance","from":"pending","to":"provisioning","elapsed_secs":4}
{"event":"heartbeat","resource":"instance","state":"provisioning","elapsed_secs":64}
{"event":"finished","resource":"instance","outcome":"succeeded","state":"ready","elapsed_secs":91,"succeeded":true}
```

Two more event shapes appear when the API pushes back: `poll_failed`, carrying a `detail`, and `throttled`, carrying `retry_after_secs`.

[`instance watch`](/docs/reference/cli/cloud/commands/instance.md#instance-watch) inverts the split and writes the same stream to **stdout**, because a watch is a stream by nature and there is no single final object to be the answer. [`instance logs --follow`](/docs/reference/cli/cloud/commands/instance.md#instance-logs) does the same with log lines. Both say so in their own `--help`.

## List presentation flags {#list-presentation-flags}

Every list-shaped command accepts the same six flags.

<OptionsTable
    title="Presentation flags"
    options={[
        {
            "name": "--columns",
            "value": "<IDS>",
            "description": "Show only these columns, comma-separated. Conflicts with `--wide`."
        },
        {
            "name": "--wide",
            "description": "Show every column, including the ones hidden by default."
        },
        {
            "name": "--no-header",
            "description": "Omit the header row."
        },
        {
            "name": "--sort",
            "value": "<ID>",
            "description": "Sort by this column. An unrecognised id is a usage error listing the valid ones."
        },
        {
            "name": "--reverse",
            "description": "Reverse the sort order."
        },
        {
            "name": "--limit",
            "value": "<N>",
            "description": "Show at most this many rows."
        }
    ]}
/>

Sorting, reversing and limiting are always applied on this side, because the API has no pagination, no sort parameter and no search. They are applied in one order - sort, then reverse, then limit - and sorting compares the *rendered* value of each cell, so the display order matches the sort order.

`--wide` reveals the columns a table hides by default. `--columns` takes the column ids, which are what `--sort` also accepts. Run a command with `--wide --no-header` to see everything a row carries.

```bash title="Just the names and states, longest-lived first"
surreal cloud instance list --columns name,state,version --sort name
```

```bash title="Every column, for a spreadsheet"
surreal cloud instance list --wide --plain > instances.tsv
```

An unknown column id is a usage error, exit `2`. It is caught as the rows are arranged for display rather than before the request, so the list has already been fetched by the time it is reported. The message names every column the row carries, including the ones only `--wide` shows:

```text
Unknown sort column `naem`. Available columns: name, state, type, version, region, compute_units, storage, id, slug, host, access_type, organization_id
```

None of these flags reach `--json`, which always emits the complete payload. A `--limit 1` must not silently truncate a pipeline.

> [!NOTE]
> [`auth scopes`](/docs/reference/cli/cloud/commands/auth.md#auth-scopes) is the one list-shaped command that carries only `--no-header`. It has no `--columns`, `--wide`, `--sort`, `--reverse` or `--limit`.

## Output modes

`--json` chooses the encoding; `--plain` chooses the decoration. The encoding is resolved per stream, so `surreal cloud instance list | less` gives plain data on stdout while a terminal on stderr keeps its spinner.

Encoding is decided by the first match:

1. `--json` or `SURREAL_CLOUD_JSON`
2. `--plain` or `SURREAL_CLOUD_PLAIN`
3. An agent environment is detected
4. `CI` is set to something other than `false`, `0` or empty
5. `TERM=dumb`
6. The stream is not a terminal
7. Otherwise, rich output

Colour is decided independently, and deliberately does not depend on `--plain` or `CI` - GitHub Actions sets `CI`, is not a terminal, and renders ANSI perfectly. `--color always` colours piped output; `--json` is never coloured, even then.

Plain output is space-padded to its content width, with uppercase headers, no borders and no truncation, and is byte-identical at any terminal width. `CI=1` is byte-for-byte equivalent to `--plain`.

What `CI` changes is structure, interactivity and progress granularity. What it must never change: colour, `--json` bytes, exit codes, which requests are made, timeouts, poll intervals, `--wait` defaults, or confirmation semantics.

## The secret guard

Every command that mints a credential refuses to write it to a terminal, where it would stay in your scrollback. The check happens **before** the request, so a forgotten `--reveal` never costs a credential nobody can recover.

```text
Refusing to print a secret to a terminal, where it would stay in your scrollback.
Pipe the output, or pass --reveal if you meant to see it.
```

That is exit `2`. Either pipe the output, or pass `--reveal` to say you meant it.

```bash title="Pipe it"
surreal cloud instance token production | pbcopy
```

```bash title="Or ask for it on screen"
surreal cloud instance token production --reveal
```

## The confirmation guard

A destructive command has exactly three outcomes:

- `--yes` (or the command's own `--force`) is set - it proceeds without asking.
- A person is present - it prompts on stderr, defaulting to **no**.
- Neither - it is an error, exit `2`, with nothing sent:

```text
Refusing to delete `production` without confirmation in a non-interactive session.
Pass --yes to confirm, or run this from a terminal.
```

Declining a prompt is not a failure. The command says what it did not do - `Nothing was deleted.` - and exits `0`.

## Exit codes

Codes are never reused, and none exceeds 125 except for the signal convention.

| Code | Meaning |
| --- | --- |
| `0` | The command did what was asked. Includes an empty list |
| `1` | A failure that does not fit any category below |
| `2` | Bad invocation: a usage error, client-side validation, a required value missing in a non-interactive session, or a destructive command that could not confirm |
| `3` | Not authenticated, or the credential expired and could not be renewed |
| `4` | Authenticated but not allowed: insufficient scopes, insufficient role, or a credential kind this operation refuses |
| `5` | The named resource does not exist |
| `6` | The resource is not in a state that allows this, or a precondition failed |
| `7` | The API rejected the request as invalid |
| `8` | Rate limited, after the retry budget was spent |
| `9` | The API or its upstream is unreachable, the feature is not available, or this client is too old |
| `10` | A wait gave up. **The operation is still running** |
| `11` | The credential store could not be read or written |
| `12` | A component the command needs is not installed and was not fetched: offline, nobody present to agree, or the offer declined. The message names the command that installs it |
| `30` | An interactive login flow did not complete |
| `130` | Interrupted |

`0`, `1` and `2` keep their conventional meanings, so `if ! surreal cloud …` behaves exactly as it does with the [database commands](/docs/reference/cli/db.md), and usage errors exit `2` as every other command-line tool does. Everything above `2` is additive.

`12` comes from the launcher rather than from the API. It is what `surreal cloud` itself returns when it is not installed and the session is unattended, and what `instance sql`, `instance import` and `instance export` return when the database is missing. In both cases the message names the install command, and nothing is downloaded silently. `SURREAL_CLI_OFFLINE=1` forbids all network use, so it always produces `12` for a missing component.

`10` has its own code because *still running* is a different answer from *failed*, and a pipeline may reasonably poll again rather than roll back. See [Long-running operations](/docs/reference/cli/cloud/long-running-operations.md).

```bash title="Branch on the outcome"
if surreal cloud instance create api --type shared-1 --region aws-euw1 --json > instance.json; then
    echo "ready: $(jq -r .name instance.json)"
else
    case $? in
        6)  echo "an instance called api already exists" ;;
        8)  echo "rate limited; requeue this job" ;;
        10) echo "still provisioning; poll again next run" ;;
        *)  echo "failed" ; exit 1 ;;
    esac
fi
```

> [!IMPORTANT]
> [`status`](/docs/reference/cli/cloud/commands/misc.md#status) is the single deliberate exception to "stdout is empty on failure". Its table *is* the diagnosis, so it prints even when the command exits non-zero. The exit code comes from the first fatal check - `3` for a credential problem, `9` for an unreachable API.

## Related pages

- [Global flags](/docs/reference/cli/cloud/global-flags.md) - `--json`, `--plain`, `--quiet`, `--color`, `--yes`
- [Long-running operations](/docs/reference/cli/cloud/long-running-operations.md) - waiting, polling and exit code `10`
- [surreal cloud reference](/docs/reference/cli/cloud.md) - the rest of the reference

---

Source: https://surrealdb.com/docs/reference/cli/db

# surreal db

The database component of the surreal CLI, run as surreal db. It can be used to export a dataset as SurrealQL from a local or remote SurrealDB database, import SurrealQL data into a local or remote database, and start a single SurrealDB instance or distributed cluster.

`surreal db` is the database component of the `surreal` CLI. It starts a server, opens an interactive SurrealQL shell, moves data in and out of a database, and reports on the version and readiness of an instance.

<Synopsis>
surreal db [OPTIONS] <COMMAND>
</Synopsis>

> [!IMPORTANT]
> Install the `surreal` CLI first. The installer also installs the database component by default, as described in [Install the CLI](/docs/explore/cli/install.md). To run SurrealDB in other ways, see the [installation](/docs/running/installation.md) pages. To experiment with SurrealDB before installing, see the [SurrealDB Studio sandbox](https://studio.surrealdb.com/) online. To persist your Sandbox data while still experimenting, click on **Deploy to Cloud** in SurrealDB Studio to create a free SurrealDB Cloud instance.

## surreal db and the launcher

The `surreal` command is a launcher that installs, versions and runs components, and `db` is the component that holds the database. The launcher installs and upgrades the database (`surreal install db`, `surreal upgrade db`), and everything after `db` is forwarded to the database unchanged, so the flags on these pages are the database's own flags. See the [launcher reference](/docs/reference/cli/launcher.md) and [Components](/docs/explore/cli/components.md) for how components are installed and selected.

The older top-level forms, such as `surreal start` and `surreal sql`, still run but print a deprecation warning that names the `surreal db` form to use instead. Scripts that call them keep working, and can be moved to `surreal db` at any time.

## Getting started

This documentation provides detailed information on each command, including usage examples and options. For a concise map of every subcommand, see [CLI commands](/docs/reference/cli/db/commands.md).

For a quickstart, [`surreal db start`](/docs/reference/cli/db/commands/start.md) and [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) will be enough to get you started.

```bash
surreal db start --user root --pass secret
```

Unless you specify otherwise, the CLI will start a database [in memory](/docs/running/in-memory.md) that serves at `127.0.0.1:8000` (or `http://localhost:8000`). This database has a single root user named `root` and a password `secret`.

In another window, you can then open up an interactive shell to make queries using the `surreal db sql` command. As of SurrealDB 3.0, the `sql` command will connect to the default 'main' name for both namespace and database.

```bash
# Connect to namespace 'main' and database 'main'
surreal db sql --username root --password secret --pretty

# Connect to a different namespace and database
surreal db sql --namespace ns --database db --username root --password secret --pretty
```

> [!WARNING]
> Using generic usernames and passwords is not recommended for production use. Please replace the authentication credentials with your own.

This will start an interactive shell to make queries. The output below shows what you will see when logged in as the root user inside a namespace called `main` and a database called `main`, with pretty (easily readable) output per query.

You can then try out a few queries and see the output.

```bash
main/main> CREATE person SET age = 20;
main/main> CREATE person SET age = 30;
main/main> SELECT * FROM person WHERE age > 25;
```

```surql title="Output"
[
	{
		age: 20,
		id: person:6jodx8xv39jsxdgykt0t
	}
]

[
	{
		age: 30,
		id: person:10bcq2owseyqqoinjgxl
	}
]

[
	{
		age: 30,
		id: person:10bcq2owseyqqoinjgxl
	}
]
```

## Next steps

- [CLI commands](/docs/reference/cli/db/commands.md) - every subcommand, with its arguments and options.
- [Environment variables](/docs/reference/cli/db/environment-variables.md) - the `SURREAL_*` variables that mirror the flags on this page.
- [Launcher](/docs/reference/cli/launcher.md) - installing, upgrading and selecting versions of the database and other components.
- [surreal cloud](/docs/reference/cli/cloud.md) - managing organisations and Cloud instances from the command line.

---

Source: https://surrealdb.com/docs/reference/cli/db/commands

# surreal db commands

Every surreal db subcommand with its purpose, and how each relates to the launcher commands, with links to each command’s flags and examples.

`surreal db` has **subcommands** for running the server, executing SurrealQL, importing and exporting data, and maintenance tasks. Each page documents that subcommand’s arguments, related environment variables, and typical usage.

<Synopsis>
surreal db [OPTIONS] <COMMAND>
</Synopsis>

| Subcommand | Purpose |
| --- | --- |
| [`surreal db start`](/docs/reference/cli/db/commands/start.md) | Run a SurrealDB server (in memory, on disk, or clustered). |
| [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) | Open an interactive SurrealQL shell or run queries from scripts. |
| [`surreal db export`](/docs/reference/cli/db/commands/export.md) | Dump a database to SurrealQL. |
| [`surreal db fix`](/docs/reference/cli/db/commands/fix.md) | Apply data or schema fixes offline. |
| [`surreal db help`](/docs/reference/cli/db/commands/help.md) | Show help for `surreal db` or a subcommand. |
| [`surreal db import`](/docs/reference/cli/db/commands/import.md) | Load SurrealQL from a file into a database. |
| [`surreal db is-ready`](/docs/reference/cli/db/commands/isready.md) | Health check for readiness probes. |
| [`surreal db mcp`](/docs/reference/cli/db/commands/mcp.md) _(since v3.1.0)_ | Start the Model Context Protocol server on stdio for IDE integrations. |
| [`surreal db ml`](/docs/reference/cli/db/commands/ml.md) | Work with machine-learning features from the CLI. |
| [`surreal db module`](/docs/reference/cli/db/commands/module.md) | Build and manage WASM modules (including Surrealism). |
| [`surreal upgrade db`](/docs/reference/cli/db/commands/upgrade.md) | Upgrade the database component. Run by the launcher, not by `surreal db`. |
| [`surreal db validate`](/docs/reference/cli/db/commands/validate.md) | Validate SurrealQL or configuration. |
| [`surreal db version`](/docs/reference/cli/db/commands/version.md) | Print the database client version, or the version of a server. |

**Sidebar order:** [`start`](/docs/reference/cli/db/commands/start.md) and [`sql`](/docs/reference/cli/db/commands/sql.md) first, then remaining subcommands alphabetically.

## Shared options

Every `surreal db` subcommand accepts `-h` / `--help`, which prints its arguments and options and exits, and the logging option group (`-l` / `--log`, `--log-format`, `--log-file-*`, and the socket and OpenTelemetry overrides). Each page lists `--log` alongside the command's own flags and shows the full group in its command help output.

Most flags have an equivalent environment variable, listed in the tables on each page and in full on the [environment variables](/docs/reference/cli/db/environment-variables.md#command-environment-variables) page.

## What is not here

These subcommands act on a database that already exists or that you start yourself. Provisioning, pausing, scaling, and deleting SurrealDB Cloud instances is handled by [`surreal cloud instance`](/docs/reference/cli/cloud.md), which calls out to `surreal db` for SQL, imports, and exports, so the pages here still describe what happens once a Cloud instance has been resolved.

Installing, upgrading and selecting versions of the database is done by the launcher, with `surreal install db`, `surreal upgrade db`, `surreal use db@<VERSION>` and `surreal version`, described in the [launcher reference](/docs/reference/cli/launcher.md). The `surreal db upgrade` form is refused, with a message that names `surreal upgrade db`.

For installation and a minimal end-to-end example, see the [surreal db overview](/docs/reference/cli/db.md).

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/export

# export

A command to export data from a SurrealDB database server into a SurrealQL file format.

Export an existing database as a SurrealQL script, either to a file or to stdout, using the [installed](/docs/explore/cli/install.md) `surreal` CLI.

<Synopsis>
surreal db export [OPTIONS] --namespace <NAMESPACE> --database <DATABASE> [FILE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[FILE]",
            "default": "-",
            "description": "Path to the SurrealQL file to write. Use a dash (`-`) to write into stdout, which is the default and can be redirected using `>`."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--endpoint",
            "short": "-e",
            "value": "<ENDPOINT>",
            "default": "http://localhost:8000",
            "description": "Database endpoint to export from."
        },
        {
            "name": "--username",
            "short": "-u",
            "value": "<USERNAME>",
            "env": "SURREAL_USER",
            "description": "Database authentication username to use when connecting. Alias: `--user`."
        },
        {
            "name": "--password",
            "short": "-p",
            "value": "<PASSWORD>",
            "env": "SURREAL_PASS",
            "description": "Database authentication password to use when connecting. Alias: `--pass`."
        },
        {
            "name": "--token",
            "short": "-t",
            "value": "<TOKEN>",
            "env": "SURREAL_TOKEN",
            "description": "Authentication token in JWT format, used instead of a username and password."
        },
        {
            "name": "--auth-level",
            "value": "<AUTH_LEVEL>",
            "default": "root",
            "env": "SURREAL_AUTH_LEVEL",
            "description": "Level on which the authenticating user is defined. Possible values: `root`, `namespace` (`ns`), `database` (`db`)."
        },
        {
            "name": "--namespace",
            "value": "<NAMESPACE>",
            "env": "SURREAL_NAMESPACE",
            "required": true,
            "description": "The namespace to export from. Alias: `--ns`."
        },
        {
            "name": "--database",
            "value": "<DATABASE>",
            "env": "SURREAL_DATABASE",
            "required": true,
            "description": "The database to export from. Alias: `--db`."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

Authenticate with either `--username` and `--password`, or with `--token`. Neither is needed against a server started with `--unauthenticated`.

## Export options

By default an export contains everything in the database. These flags narrow it down.

<OptionsTable
    title="Export options"
    options={[
        {
            "name": "--only",
            "description": "Whether only specific resources should be exported. When provided, only the resources named by the flags below are exported."
        },
        {
            "name": "--users",
            "value": "[<USERS>]",
            "description": "Whether system users should be exported. Possible values: `true`, `false`."
        },
        {
            "name": "--accesses",
            "value": "[<ACCESSES>]",
            "description": "Whether access methods (record or JWT) should be exported. Possible values: `true`, `false`. A signing key is never exported, so an access that has one has to be defined again after a restore. See [What a dump does not restore](/docs/manage/self-hosted/backups-and-recovery.md#what-a-dump-does-not-restore)."
        },
        {
            "name": "--params",
            "value": "[<PARAMS>]",
            "description": "Whether database parameters should be exported. Possible values: `true`, `false`."
        },
        {
            "name": "--functions",
            "value": "[<FUNCTIONS>]",
            "description": "Whether functions should be exported. Possible values: `true`, `false`."
        },
        {
            "name": "--analyzers",
            "value": "[<ANALYZERS>]",
            "description": "Whether analyzers should be exported. Possible values: `true`, `false`."
        },
        {
            "name": "--tables",
            "value": "[<TABLES>]",
            "description": "Whether tables should be exported, optionally providing a list of tables."
        },
        {
            "name": "--versions",
            "value": "[<VERSIONS>]",
            "description": "Refused from SurrealDB 3.3.1, because an export holds only the current state of each record. SurrealDB 3.0.0 to 3.3.0 accepted the flag and ignored it."
        },
        {
            "name": "--records",
            "value": "[<RECORDS>]",
            "description": "Whether records should be exported. Possible values: `true`, `false`."
        }
    ]}
/>

## Example usage

To perform a SurrealQL database export into a local file, in a terminal run the `surreal db export` command with the required arguments.

```bash
surreal db export --endpoint http://localhost:8000 --user root --pass secret --ns main --db main export.surql
```

Using token-based authentication:

```bash
surreal db export --endpoint http://localhost:8000 --token <token> --ns main --db main export.surql
```

To write the export to standard output instead of a file, for example to pipe it into another command, use `-` as the file name and add `--log none`. Without it, the command writes its log line `INFO surrealdb::method::export: Exporting bytes` to standard output as well, ahead of the export, and importing the result fails with a parse error on that line.

```bash
surreal db export --endpoint http://localhost:8000 --user root --pass secret --ns main --db main --log none - > export.surql
```

## Choosing what to export

`--only` starts from an empty export, and each resource flag after it adds that resource back. The following command exports the table definitions and their records, and leaves out users, access methods, functions and every other definition:

```bash
surreal db export --endpoint http://localhost:8000 --user root --pass secret --ns main --db main --only --tables --records -- export.surql
```

Without `--records`, the same command exports only the table definitions, which gives the schema without the data. `--tables` also takes a comma-separated list, such as `--tables person,order`, to export only those tables.

The resource flags accept an optional value, so a flag directly before the file name reads the file name as its value and the command fails with `invalid value`. Put `--` before the file name, as in the example above, or put the file name before the flags.

### Choosing in SurrealDB Studio

[SurrealDB Studio](/docs/explore/studio.md) builds the same command for you. Open **Export database** and choose **Choose what to include**, then select the tables and the definitions you want. **Record data** corresponds to `--records`, and each other checkbox to the flag of the same name.

<img src="~/assets/img/image/studio/export-database-choose.png" alt="The Export database dialog in SurrealDB Studio with Choose what to include selected, showing a checkbox for each table and for each kind of definition: record data, functions, parameters, users, access methods, analyzers, APIs, buckets, modules, configs and sequences." />

The **Use the CLI instead** section below the checkboxes shows the `surreal db export` command for that selection, with your connection's endpoint, namespace and database filled in. Running it from a terminal is faster for a large database, and it does not depend on the Studio window staying open.

<img src="~/assets/img/image/studio/export-database-cli.png" alt="The same dialog with Use the CLI instead expanded, showing a surreal db export command with the endpoint, namespace, database and credentials, followed by --only and one flag for each selected resource, and the file name after a double hyphen." />

If you select **Sequences**, add `--sequences` to the command yourself: Studio currently leaves that flag out of the command it shows.

## Using environment variables

> [!IMPORTANT]
> Most of the flags above have a corresponding [environment variable](/docs/reference/cli/db/environment-variables.md#command-environment-variables).
> For example, the `--username` flag can be configured with the `SURREAL_USER` environment variable instead.

When using the `surreal db export` command, you can also use environment variables to set the values for the command-line flags.

For more on the environment variables available for CLI commands or SurrealDB instances in general, see the [environment variables](/docs/reference/cli/db/environment-variables.md#command-environment-variables) page.

## Managing a Cloud instance

To export from a SurrealDB Cloud instance by name, [`surreal cloud`](/docs/reference/cli/cloud.md) resolves the endpoint and credentials for you and then runs this command. The flags and file-format notes on this page still apply.

## OPTION IMPORT keyword

The output of a database export includes a line that contains the keywords `OPTION IMPORT`. This command is used internally to ensure that side effects do not run when the data is imported, such as [events](/docs/reference/query-language/statements/define/event.md) and [table views](/docs/reference/query-language/statements/define/table.md#pre-computed-table-views).

As of SurrealDB 3.0.4, imports via the [`surreal db import`](/docs/reference/cli/db/commands/import.md) and [`/import`](/docs/reference/rest-api/http-protocol.md#import) HTTP endpoint require the automatically generated `OPTION IMPORT` line to be present in order to disable events, live queries, field processing, and result output for optimal import performance. If side effects are desired when importing data, remove the line and use the [`/sql`](/docs/reference/rest-api/http-protocol.md#sql) endpoint instead.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db export --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the export command.

```bash
surreal db export --help
```

The output of the above command:

```text
Export an existing database as a SurrealQL script

Usage: surreal export [OPTIONS] --namespace <NAMESPACE> --database <DATABASE> [FILE]

Arguments:
  [FILE]  Path to the SurrealQL file to export. Use dash - to write into stdout. [default: -]

Options:
  -e, --endpoint <ENDPOINT>      Database endpoint to export from [default: http://localhost:8000]
  -u, --username <USERNAME>      Database authentication username to use when connecting [env: SURREAL_USER=] [aliases: --user]
  -p, --password <PASSWORD>      Database authentication password to use when connecting [env: SURREAL_PASS=] [aliases: --pass]
  -t, --token <TOKEN>            Authentication token in JWT format to use when connecting [env: SURREAL_TOKEN=]
      --auth-level <AUTH_LEVEL>  Level on which the authenticating user is defined [env: SURREAL_AUTH_LEVEL=] [default: root] [possible
                                 values: root, namespace, ns, database, db]
      --namespace <NAMESPACE>    The namespace selected for the operation [env: SURREAL_NAMESPACE=] [aliases: --ns]
      --database <DATABASE>      The database selected for the operation [env: SURREAL_DATABASE=] [aliases: --db]
      --only                     Whether only specific resources should be exported
      --users [<USERS>]          Whether users should be exported [possible values: true, false]
      --accesses [<ACCESSES>]    Whether access methods should be exported [possible values: true, false]
      --params [<PARAMS>]        Whether params should be exported [possible values: true, false]
      --functions [<FUNCTIONS>]  Whether functions should be exported [possible values: true, false]
      --analyzers [<ANALYZERS>]  Whether analyzers should be exported [possible values: true, false]
      --tables [<TABLES>]        Whether tables should be exported, optionally providing a list of tables
      --versions [<VERSIONS>]    Whether versions should be exported [possible values: true, false]
      --records [<RECORDS>]      Whether records should be exported [possible values: true, false]
  -h, --help                     Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values:
                                               none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible
                                               values: daily, hourly, never]
```

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/fix

# fix

A command to convert SurrealDB version 1.x data into a usable format for versions 2.0 and above.

Convert data written by SurrealDB 1.x into the storage format used by 2.0 and above, using a 2.x version of the database component, which you can run for one command with `surreal +2.6.5 db fix` after `surreal install db@2.6.5`.

> [!IMPORTANT]
> The conversion is only implemented in 2.x binaries. From SurrealDB 3.0.0 the command is still listed, but it exits with `Fix is not implemented` and changes nothing. Run `surreal db fix` with a 2.x binary to move 1.x data to 2.x, then follow [Migrating from 2.x to 3.x](/docs/build/migrating/from-old-surrealdb-versions/2x-to-3x.md) to reach 3.x.

<Synopsis>
surreal db fix [OPTIONS] [PATH]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[PATH]",
            "default": "memory",
            "env": "SURREAL_PATH",
            "description": "Path to the existing data to convert to the 2.x storage format, for example `surrealkv://mydatabase.db`."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

## Example usage

To perform a fix from SurrealDB 1.x to 2.x storage, run the `surreal db fix` command in a terminal with the path to the stored data.

```bash
surreal db fix surrealkv://mydatabase.db

surreal db fix rocksdb:somedatabase
```

## Using environment variables

> [!IMPORTANT]
> Most of the flags above have a corresponding [environment variable](/docs/reference/cli/db/environment-variables.md#command-environment-variables).
> For example, the `--log` flag can be configured with the `SURREAL_LOG` environment variable instead.

When using the `surreal db fix` command, you can also use environment variables to set the values for the command-line flags.

For more on the environment variables available for CLI commands or SurrealDB instances in general, see the [environment variables](/docs/reference/cli/db/environment-variables.md#command-environment-variables) page.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db fix --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `fix` command.

```bash
surreal db fix --help
```

The output of the above command:

```text
Fix database storage issues

Usage: surreal fix [OPTIONS] [PATH]

Arguments:
  [PATH]  Database path used for storing data [env: SURREAL_PATH=] [default: memory]

Options:
  -h, --help  Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible values: daily, hourly, never]
```

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/help

# help

surreal db help and surreal db <COMMAND> --help print the database help. surreal help and surreal --help print the launcher help, which lists components and version commands instead.

Print the help information for `surreal db`, or for one of its subcommands.

`surreal db help` and `surreal help` print different things. `surreal db help` is the database's own help, described on this page, because everything after `db` is forwarded to the database. `surreal help` and `surreal --help` are the launcher's help, which lists the components and the commands that manage what is installed. See [Launcher help](#launcher-help).

<Synopsis>
surreal db help [COMMAND]
surreal db <COMMAND> --help
</Synopsis>

<OptionsTable
    title="Global options"
    options={[
        {
            "name": "--online-version-check",
            "env": "SURREAL_ONLINE_VERSION_CHECK",
            "description": "Whether to allow a web check for client version upgrades at start."
        },
        {
            "name": "--help",
            "short": "-h",
            "description": "Print help. Accepted by the binary and by every subcommand."
        },
        {
            "name": "--version",
            "short": "-V",
            "description": "Print the version of the database component."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

## Show the command-line help information

To see the general help information for the database, run `surreal db help` without any further arguments. This command lists the subcommands of `surreal db`.

```bash
surreal db help
```

The output of the above command:

```text
 .d8888b.                                             888 8888888b.  888888b.
d88P  Y88b                                            888 888  'Y88b 888  '88b
Y88b.                                                 888 888    888 888  .88P
 'Y888b.   888  888 888d888 888d888  .d88b.   8888b.  888 888    888 8888888K.
    'Y88b. 888  888 888P'   888P'   d8P  Y8b     '88b 888 888    888 888  'Y88b
      '888 888  888 888     888     88888888 .d888888 888 888    888 888    888
Y88b  d88P Y88b 888 888     888     Y8b.     888  888 888 888  .d88P 888   d88P
 'Y8888P'   'Y88888 888     888      'Y8888  'Y888888 888 8888888P'  8888888P'


To get started using SurrealDB, and for guides on connecting to and building applications
on top of SurrealDB, check out the SurrealDB documentation (https://surrealdb.com/docs).

If you have questions or ideas, join the SurrealDB community (https://surrealdb.com/community).

If you find a bug, submit an issue on GitHub (https://github.com/surrealdb/surrealdb/issues).

We would love it if you could star the repository (https://github.com/surrealdb/surrealdb).

----------

Usage: surreal [OPTIONS] <COMMAND>

Commands:
  start     Start the database server
  import    Import a SurrealQL script into an existing database
  export    Export an existing database as a SurrealQL script
  version   Output the command-line tool and remote server version information
  upgrade   Upgrade to the latest stable version
  sql       Start an SQL REPL in your terminal with pipe support
  mcp       Start the MCP server over stdio for AI assistant integration
  ml        Manage SurrealML models within an existing database
  module    Manage and execute WASM modules
  is-ready  Check if the SurrealDB server is ready to accept connections [aliases: isready]
  validate  Validate SurrealQL query files
  fix       Fix database storage issues
  v2        Run commands in version 2 of the database for backwards compatibility
  help      Print this message or the help of the given subcommand(s)

Options:
      --online-version-check  Whether to allow web check for client version upgrades at start [env: SURREAL_ONLINE_VERSION_CHECK=]
  -h, --help                  Print help
  -V, --version               Print version

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values:
                                               none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible
                                               values: daily, hourly, never]
```

The `upgrade` entry appears in the database's own list, but `surreal db upgrade` is refused. Use [`surreal upgrade db`](/docs/reference/cli/db/commands/upgrade.md).

## Launcher help {#launcher-help}

`surreal help` and `surreal --help` print the launcher's help. It lists the components (`db`, `cloud`), the commands that manage what is installed (`version`, `install`, `upgrade`, `use`, `uninstall`), and the `+VERSION` option that runs another version for one command. Run `surreal <COMPONENT> help`, such as `surreal cloud help`, for the help of a component. The `--no-banner` option, or `SURREAL_CLI_NO_BANNER`, prints the launcher help without the banner. See the [launcher reference](/docs/reference/cli/launcher.md) for each of these commands.

## Getting help on individual commands

For individual commands, such as `surreal db start` and `surreal db sql`, a help prompt can be displayed by adding the `--help` flag. This flag overrides all other flags, and thus can be added to the end of any command regardless of length.

```bash
surreal db start --help
surreal db start --user root --pass secret --log debug --help
```

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/import

# import

A command that imports a file in SurrealQL format into a local or remote SurrealDB database server.

Import a SurrealQL script into an existing database, on a local or remote server, using the [installed](/docs/explore/cli/install.md) `surreal` CLI.

<Synopsis>
surreal db import [OPTIONS] --namespace <NAMESPACE> --database <DATABASE> <FILE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<FILE>",
            "required": true,
            "description": "Path to the SurrealQL file to import."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--endpoint",
            "short": "-e",
            "value": "<ENDPOINT>",
            "default": "http://localhost:8000",
            "description": "Database endpoint to import to."
        },
        {
            "name": "--username",
            "short": "-u",
            "value": "<USERNAME>",
            "env": "SURREAL_USER",
            "description": "Database authentication username to use when connecting. Alias: `--user`."
        },
        {
            "name": "--password",
            "short": "-p",
            "value": "<PASSWORD>",
            "env": "SURREAL_PASS",
            "description": "Database authentication password to use when connecting. Alias: `--pass`."
        },
        {
            "name": "--token",
            "short": "-t",
            "value": "<TOKEN>",
            "env": "SURREAL_TOKEN",
            "description": "Authentication token in JWT format, used instead of a username and password."
        },
        {
            "name": "--auth-level",
            "value": "<AUTH_LEVEL>",
            "default": "root",
            "env": "SURREAL_AUTH_LEVEL",
            "description": "Level on which the authenticating user is defined. Possible values: `root`, `namespace` (`ns`), `database` (`db`)."
        },
        {
            "name": "--namespace",
            "value": "<NAMESPACE>",
            "env": "SURREAL_NAMESPACE",
            "required": true,
            "description": "The namespace to import into. Alias: `--ns`."
        },
        {
            "name": "--database",
            "value": "<DATABASE>",
            "env": "SURREAL_DATABASE",
            "required": true,
            "description": "The database to import into. Alias: `--db`."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

Authenticate with either `--username` and `--password`, or with `--token`. Neither is needed against a server started with `--unauthenticated`.

## Example usage

To perform a SurrealQL database import from a local file, in a terminal run the `surreal db import` command with the required arguments.

```bash
surreal db import --endpoint http://localhost:8000 --user root --pass secret \
  --ns main --db main downloads/surreal_deal_v1.surql
```

Using token-based authentication:

```bash
surreal db import --endpoint http://localhost:8000 --token <token> --ns main \
  --db main downloads/surreal_deal_v1.surql
```

> [!NOTE]
> If you are using SurrealDB Studio, you can import files into your database by using the `Import database` button in the Explorer view. See the [SurrealDB Studio documentation](/docs/explore/studio.md) for more information.

## Size limits and partial imports

An import against a remote endpoint goes through the [`/import`](/docs/reference/rest-api/http-protocol.md#import) endpoint, which accepts up to 4 GiB per request by default. The limit is cumulative for the whole request rather than per chunk, so a single file larger than the cap fails no matter how it is transferred - split the dataset across several files instead.

The import is applied as it is parsed, statement by statement, and each statement commits as it goes. An import that exceeds the limit or is interrupted therefore leaves everything applied up to that point in the database, with nothing rolled back.

A statement that fails does not stop the import, and the statements after it are still applied. When the import finishes, the command logs the failure and exits with code `1`, so a script can tell a partial import from a complete one.

Before importing a large file, decide how a failed run would be retried. Either write the file so that running it twice is safe, or import into a fresh namespace or database and switch over once the import has completed.

See [request size limits](/docs/reference/rest-api/http-protocol.md#request-size-limits) for the caps on every endpoint, and [environment variables](/docs/reference/cli/db/environment-variables.md#http-server-config) for changing them on a self-hosted server.

## Validating files before import

A good practice before importing for the first time is to use the [`surreal db validate`](/docs/reference/cli/db/commands/validate.md) command to ensure that the statements therein are valid SurrealQL. This allows you to save time by failing quickly on the command line if there is invalid SurrealQL instead of starting a full database instance that will eventually fail in the middle of the import process.

## Importing data from other databases and sources

To import data from other sources besides `.surql` files (such as PostgreSQL, MongoDB, CSV data, Kafka, etc.), see the [migrations](/docs/build/migrating.md) section of the documentation.

## Using environment variables

When using the `surreal db import` command, you can also use environment variables to set the values for the command-line flags.

> [!IMPORTANT]
> Most of the flags above have a corresponding [environment variable](/docs/reference/cli/db/environment-variables.md#command-environment-variables).
> For example, the `--username` flag can be configured with the `SURREAL_USER` environment variable instead.

For more on the environment variables available for CLI commands or SurrealDB instances in general, see the [environment variables](/docs/reference/cli/db/environment-variables.md#command-environment-variables) page.

## Managing a Cloud instance

To import into a SurrealDB Cloud instance by name, [`surreal cloud`](/docs/reference/cli/cloud.md) resolves the endpoint and credentials for you and then runs this command. The size limits and partial-import behaviour described above apply there too.

## OPTION IMPORT

The output of a database export includes a line that contains the keywords `OPTION IMPORT`. This command is used internally to ensure that side effects do not run when the data is imported, such as [events](/docs/reference/query-language/statements/define/event.md) and [table views](/docs/reference/query-language/statements/define/table.md#pre-computed-table-views).

As of SurrealDB 3.0.4, this line must be present in order to use the `/import` endpoint. If side effects when importing a `.surql` file are desired, remove the `OPTION IMPORT` line and use the [`/sql`](/docs/reference/rest-api/http-protocol.md#sql) endpoint instead.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db import --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `import` command.

```bash
surreal db import --help
```

The output of the above command:

```text
Import a SurrealQL script into an existing database

Usage: surreal import [OPTIONS] --namespace <NAMESPACE> --database <DATABASE> <FILE>

Arguments:
  <FILE>  Path to the SurrealQL file to import

Options:
  -e, --endpoint <ENDPOINT>      Database endpoint to import to [default: http://localhost:8000]
  -u, --username <USERNAME>      Database authentication username to use when connecting [env: SURREAL_USER=] [aliases: --user]
  -p, --password <PASSWORD>      Database authentication password to use when connecting [env: SURREAL_PASS=] [aliases: --pass]
  -t, --token <TOKEN>            Authentication token in JWT format to use when connecting [env: SURREAL_TOKEN=]
      --auth-level <AUTH_LEVEL>  Level on which the authenticating user is defined [env: SURREAL_AUTH_LEVEL=] [default: root] [possible
                                 values: root, namespace, ns, database, db]
      --namespace <NAMESPACE>    The namespace selected for the operation [env: SURREAL_NAMESPACE=] [aliases: --ns]
      --database <DATABASE>      The database selected for the operation [env: SURREAL_DATABASE=] [aliases: --db]
  -h, --help                     Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values:
                                               none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible
                                               values: daily, hourly, never]
```

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/isready

# isready

A command that determines whether a SurrealDB server has started and is able to accept connections.

Check whether a SurrealDB server has started and is able to accept connections, using the [installed](/docs/explore/cli/install.md) `surreal` CLI. The command is named `is-ready` and also accepts the alias `isready`.

<Synopsis>
surreal db is-ready [OPTIONS]
</Synopsis>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--endpoint",
            "short": "-e",
            "value": "<ENDPOINT>",
            "default": "ws://localhost:8000",
            "description": "Remote database server URL to connect to."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

## Example usage

To check whether a server has started and is accepting connections, in a terminal run the `surreal db isready` command against its endpoint.

```bash
surreal db isready --endpoint http://localhost:8000
```

The output of the command will either be a simple `OK` if ready, or some sort of error depending on the situation.

```text
# Server is running and ready
OK

# Output if server not started yet
2026-02-10T04:12:10.274572Z ERROR surrealdb_server::cli: There was an error processing a WebSocket request: IO error: Connection refused (os error 61)
```

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db isready --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `isready` command.

```bash
surreal db isready --help
```

The output of the above command:

```text
Check if the SurrealDB server is ready to accept connections

Usage: surreal is-ready [OPTIONS]

Options:
  -e, --endpoint <ENDPOINT>  Remote database server url to connect to [default: ws://localhost:8000]
  -h, --help                 Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values:
                                               none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible
                                               values: daily, hourly, never]
```

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/mcp

# mcp

Start SurrealDB's Model Context Protocol server over stdio for local IDE and agent integrations.

_(since v3.1.0)_

The `surreal db mcp` subcommand starts the built-in [Model Context Protocol](https://modelcontextprotocol.io) server on **stdio**, suitable for Cursor, VS Code, Claude Desktop, and other MCP clients. It opens an embedded datastore at the path you pass (default `memory`) and runs every tool call with owner-level access.

For HTTP-based clients against a running server, use [`surreal db start`](/docs/reference/cli/db/commands/start.md) and connect to the **`/mcp`** endpoint instead. See [When to use `surreal db mcp` vs `surreal db start`](/docs/build/ai-agents/mcp/embedded.md#when-to-use-surreal-db-mcp-vs-surreal-db-start) for the comparison, and [Embedded MCP](/docs/build/ai-agents/mcp/embedded.md) for transports, editor setup, and security.

> [!WARNING]
> Stdio MCP is intended for a trusted operator on the same machine. Do not expose this process to untrusted users - there is no per-call HTTP authentication surface to re-bind credentials.

<Synopsis>
surreal db mcp [OPTIONS] [PATH]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[PATH]",
            "default": "memory",
            "env": "SURREAL_PATH",
            "description": "Database path for the embedded datastore."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--ns",
            "value": "<NAMESPACE>",
            "env": "SURREAL_MCP_NS",
            "description": "Initial namespace."
        },
        {
            "name": "--db",
            "value": "<DATABASE>",
            "env": "SURREAL_MCP_DB",
            "description": "Initial database."
        },
        {
            "name": "--username",
            "short": "-u",
            "value": "<USERNAME>",
            "env": "SURREAL_USER",
            "description": "Root username. Requires `--password`, and applies only if no root user exists yet."
        },
        {
            "name": "--password",
            "short": "-p",
            "value": "<PASSWORD>",
            "env": "SURREAL_PASS",
            "description": "Root password."
        }
    ]}
/>

Database tuning flags from `surreal db start` are also available via the shared `dbs` option group (see `surreal db mcp --help`).

## Usage

```bash
surreal db mcp --ns main --db main memory
```

Optional root credentials apply only when no root user exists yet (same semantics as `surreal db start`):

```bash
surreal db mcp -u root -p secret --ns main --db main memory
```

## Limits

Process-wide MCP limits use the `SURREAL_MCP_*` variables documented in [Environment variables](/docs/reference/cli/db/environment-variables.md) and on [Embedded MCP](/docs/build/ai-agents/mcp/embedded.md#configuration). For HTTP `/mcp` on a non-loopback hostname, set `SURREAL_MCP_ALLOWED_HOSTS` (or `SURREAL_MCP_ALLOW_ALL_HOSTS` behind a trusted proxy). See those pages for details.

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/ml

# ml

The ML command can be used to import and export machine learning models.

Manage SurrealML models within an existing database. The command has two subcommands: one imports a trained model into a database, the other exports a model that is already stored there.

> [!NOTE]
> **Before you begin** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal db ml [OPTIONS] <COMMAND>
</Synopsis>

| Subcommand | Purpose |
| --- | --- |
| [`surreal db ml import`](#ml-import) | Import a SurrealML model into an existing database. |
| [`surreal db ml export`](#ml-export) | Export a SurrealML model from an existing database. |
| `surreal db ml help` | Print this message or the help of the given subcommand. |

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db ml --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `ml` command.

```bash
surreal db ml --help
```

The output of the above command:

```text
Manage SurrealML models within an existing database

Usage: surreal ml [OPTIONS] <COMMAND>

Commands:
  import  Import a SurrealML model into an existing database
  export  Export a SurrealML model from an existing database
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help  Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible values: daily, hourly, never]
```

## surreal db ml export {#ml-export}

The ML export command is used to export an existing machine learning model from SurrealDB.

<Synopsis>
surreal db ml export [OPTIONS] --name <NAME> --version <VERSION> --namespace <NAMESPACE> --database <DATABASE> [FILE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[FILE]",
            "default": "-",
            "description": "Path to the SurrealML file to write. Use a dash (`-`) to write into stdout."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--name",
            "value": "<NAME>",
            "env": "SURREAL_NAME",
            "required": true,
            "description": "The name of the model to export."
        },
        {
            "name": "--version",
            "value": "<VERSION>",
            "env": "SURREAL_VERSION",
            "required": true,
            "description": "The version of the model to export."
        },
        {
            "name": "--endpoint",
            "short": "-e",
            "value": "<ENDPOINT>",
            "default": "ws://localhost:8000",
            "description": "Remote database server URL to connect to."
        },
        {
            "name": "--username",
            "short": "-u",
            "value": "<USERNAME>",
            "env": "SURREAL_USER",
            "description": "Database authentication username to use when connecting. Alias: `--user`."
        },
        {
            "name": "--password",
            "short": "-p",
            "value": "<PASSWORD>",
            "env": "SURREAL_PASS",
            "description": "Database authentication password to use when connecting. Alias: `--pass`."
        },
        {
            "name": "--token",
            "short": "-t",
            "value": "<TOKEN>",
            "env": "SURREAL_TOKEN",
            "description": "Authentication token in JWT format, used instead of a username and password."
        },
        {
            "name": "--auth-level",
            "value": "<AUTH_LEVEL>",
            "default": "root",
            "env": "SURREAL_AUTH_LEVEL",
            "description": "Level on which the authenticating user is defined. Possible values: `root`, `namespace` (`ns`), `database` (`db`)."
        },
        {
            "name": "--namespace",
            "value": "<NAMESPACE>",
            "env": "SURREAL_NAMESPACE",
            "required": true,
            "description": "The namespace holding the model. Alias: `--ns`."
        },
        {
            "name": "--database",
            "value": "<DATABASE>",
            "env": "SURREAL_DATABASE",
            "required": true,
            "description": "The database holding the model. Alias: `--db`."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

### Example usage

To export a stored model to a local file, in a terminal run the `surreal db ml export` command with the required arguments.

```bash
surreal db ml export --endpoint http://localhost:8000 --user root --pass secret --ns main --db main --name my-surrealml-model --version 1.0.0 my-surrealml-model.surml
```

Using token-based authentication:

```bash
surreal db ml export --endpoint http://localhost:8000 --token <token> --ns main --db main --name my-surrealml-model --version 1.0.0 my-surrealml-model.surml
```

### Command help

To see the help information and usage instructions, in a terminal run the `surreal db ml export --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `ml export` command.

```bash
surreal db ml export --help
```

The output of the above command:

```text
Export a SurrealML model from an existing database

Usage: surreal ml export [OPTIONS] --name <NAME> --version <VERSION> --namespace <NAMESPACE> --database <DATABASE> [FILE]

Arguments:
  [FILE]  Path to the SurrealML file to export. Use dash - to write into stdout. [default: -]

Options:
      --name <NAME>              The name of the model [env: SURREAL_NAME=]
      --version <VERSION>        The version of the model [env: SURREAL_VERSION=]
  -e, --endpoint <ENDPOINT>      Remote database server url to connect to [default: ws://localhost:8000] [aliases: --conn]
  -u, --username <USERNAME>      Database authentication username to use when connecting [env: SURREAL_USER=] [aliases: --user]
  -p, --password <PASSWORD>      Database authentication password to use when connecting [env: SURREAL_PASS=] [aliases: --pass]
  -t, --token <TOKEN>            Authentication token in JWT format to use when connecting [env: SURREAL_TOKEN=]
      --auth-level <AUTH_LEVEL>  Level on which the authenticating user is defined [env: SURREAL_AUTH_LEVEL=] [default: root] [possible values: root, namespace, ns, database, db]
      --namespace <NAMESPACE>    The namespace selected for the operation [env: SURREAL_NAMESPACE=] [aliases: --ns]
      --database <DATABASE>      The database selected for the operation [env: SURREAL_DATABASE=] [aliases: --db]
  -h, --help                     Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible values: daily, hourly, never]
```

## surreal db ml import {#ml-import}

The ML import command is used to import a new machine learning model into SurrealDB.

<Synopsis>
surreal db ml import [OPTIONS] --namespace <NAMESPACE> --database <DATABASE> <FILE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<FILE>",
            "required": true,
            "description": "Path to the SurrealML file to import."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--endpoint",
            "short": "-e",
            "value": "<ENDPOINT>",
            "default": "ws://localhost:8000",
            "description": "Remote database server URL to connect to."
        },
        {
            "name": "--username",
            "short": "-u",
            "value": "<USERNAME>",
            "env": "SURREAL_USER",
            "description": "Database authentication username to use when connecting. Alias: `--user`."
        },
        {
            "name": "--password",
            "short": "-p",
            "value": "<PASSWORD>",
            "env": "SURREAL_PASS",
            "description": "Database authentication password to use when connecting. Alias: `--pass`."
        },
        {
            "name": "--token",
            "short": "-t",
            "value": "<TOKEN>",
            "env": "SURREAL_TOKEN",
            "description": "Authentication token in JWT format, used instead of a username and password."
        },
        {
            "name": "--auth-level",
            "value": "<AUTH_LEVEL>",
            "default": "root",
            "env": "SURREAL_AUTH_LEVEL",
            "description": "Level on which the authenticating user is defined. Possible values: `root`, `namespace` (`ns`), `database` (`db`)."
        },
        {
            "name": "--namespace",
            "value": "<NAMESPACE>",
            "env": "SURREAL_NAMESPACE",
            "required": true,
            "description": "The namespace to import the model into. Alias: `--ns`."
        },
        {
            "name": "--database",
            "value": "<DATABASE>",
            "env": "SURREAL_DATABASE",
            "required": true,
            "description": "The database to import the model into. Alias: `--db`."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

### Example usage

To import a model from a local file, in a terminal run the `surreal db ml import` command with the required arguments.

```bash
surreal db ml import --endpoint http://localhost:8000 --user root --pass secret \
  --ns main --db main my-surrealml-model.surml
```

Using token-based authentication:

```bash
surreal db ml import --endpoint http://localhost:8000 --token <token> --ns main --db main my-surrealml-model.surml
```

### Command help

To see the help information and usage instructions, in a terminal run the `surreal db ml import --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `ml import` command.

```bash
surreal db ml import --help
```

The output of the above command:

```text
Import a SurrealML model into an existing database

Usage: surreal ml import [OPTIONS] --namespace <NAMESPACE> --database <DATABASE> <FILE>

Arguments:
  <FILE>  Path to the SurrealML file to import

Options:
  -e, --endpoint <ENDPOINT>      Remote database server url to connect to [default: ws://localhost:8000] [aliases: --conn]
  -u, --username <USERNAME>      Database authentication username to use when connecting [env: SURREAL_USER=] [aliases: --user]
  -p, --password <PASSWORD>      Database authentication password to use when connecting [env: SURREAL_PASS=] [aliases: --pass]
  -t, --token <TOKEN>            Authentication token in JWT format to use when connecting [env: SURREAL_TOKEN=]
      --auth-level <AUTH_LEVEL>  Level on which the authenticating user is defined [env: SURREAL_AUTH_LEVEL=] [default: root] [possible values: root, namespace, ns, database, db]
      --namespace <NAMESPACE>    The namespace selected for the operation [env: SURREAL_NAMESPACE=] [aliases: --ns]
      --database <DATABASE>      The database selected for the operation [env: SURREAL_DATABASE=] [aliases: --db]
  -h, --help                     Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible values: daily, hourly, never]
```

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/module

# module

A command used to compile, manage and execute Surrealism plugin modules.

_(since v3.0.0)_

Manage and execute WASM modules, including [Surrealism](/docs/learn/extensions/plugins/overview.md) plugin modules: scaffold a project, build it, inspect the result, and run its functions from the command line, using the [installed](/docs/explore/cli/install.md) `surreal` CLI.

<Synopsis>
surreal db module [OPTIONS] <COMMAND>
</Synopsis>

> [!NOTE]
> A dedicated `mod` component of the launcher will supersede `surreal db module`. Until it is released, `surreal db module` is the way to work with modules from the command line. The top-level form, `surreal module`, is deprecated and prints a warning that names `surreal db module`.

| Subcommand | Purpose |
| --- | --- |
| [`surreal db module init`](#module-init) | Initialise a new Surrealism module project. |
| [`surreal db module build`](#module-build) | Build a WASM module. |
| [`surreal db module run`](#module-run) | Run a function with arguments. |
| [`surreal db module sig`](#module-sig) | Show the function signature. |
| [`surreal db module info`](#module-info) | Show the module information. |
| `surreal db module help` | Print this message or the help of the given subcommand. |

## surreal db module init {#module-init}

Scaffold a new Surrealism module project (Rust crate, `surrealism.toml`, `.cargo/Config.toml`, and starter `src/lib.rs`).

<Synopsis>
surreal db module init [OPTIONS] [PATH]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[PATH]",
            "description": "Directory to scaffold the crate in. The current directory is used when omitted."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--headless",
            "description": "Scaffold without prompting, for use in scripts and CI."
        },
        {
            "name": "--org",
            "value": "<ORG>",
            "description": "Organisation the module belongs to."
        },
        {
            "name": "--name",
            "value": "<NAME>",
            "description": "Name of the module."
        }
    ]}
/>

```bash
surreal db module init
```

`surreal db module init` writes the WASI build flags Surrealism needs into `.cargo/config.toml`. `surreal db module build` applies the same flags when it invokes Cargo, so existing projects pick them up even if the file was not scaffolded yet.

```toml title=".cargo/Config.toml flags"
[build]
rustflags = ["--cfg", "tokio_unstable"]
```

For scripts or CI, use non-interactive mode:

```bash
surreal db module init --headless --org surrealdb --name my_module ./path/to/crate
```

## surreal db module build {#module-build}

Build a WASM binary from the Rust source code. A binary must have the `.surli` file extension.

<Synopsis>
surreal db module build [OPTIONS] [PATH]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[PATH]",
            "description": "Path to the crate to build. The current directory is used when omitted."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "-o",
            "value": "<FILE>",
            "description": "Path of the `.surli` binary to write."
        },
        {
            "name": "--debug",
            "description": "Skip the usual optimisation pass for a faster build time in exchange for lower performance."
        }
    ]}
/>

```bash
surreal db module build -o demo.surli ../demo
```

Using `--debug` skips the usual optimisation pass for a faster build time in exchange for lower performance. Recommended when iterating and testing before producing a final build.

```bash
surreal db module build --debug -o demo-debug.surli .
```

## surreal db module run {#module-run}

Run a single function from a compiled module, including functions that require arguments to be passed in.

<Synopsis>
surreal db module run [OPTIONS] --fnc <FUNCTION> <FILE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<FILE>",
            "required": true,
            "description": "Path to the compiled `.surli` module."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--fnc",
            "value": "<FUNCTION>",
            "required": true,
            "description": "Name of the function to run."
        },
        {
            "name": "--arg",
            "value": "<ARG>",
            "description": "Value passed to the function. Functions that take no arguments need no `--arg`."
        }
    ]}
/>

```bash
surreal db module run --fnc can_drive --arg 17    demo.surli
surreal db module run --fnc can_drive --arg 18    demo.surli
surreal db module run --fnc result    --arg false demo.surli
surreal db module run --fnc result    --arg true  demo.surli
surreal db module run --fnc test_kv               demo.surli
surreal db module run --fnc test_io               demo.surli
surreal db module run --fnc test_none_value       demo.surli
```

## surreal db module sig {#module-sig}

Show the signature for a single function in a compiled module.

<Synopsis>
surreal db module sig [OPTIONS] --fnc <FUNCTION> <FILE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<FILE>",
            "required": true,
            "description": "Path to the compiled `.surli` module."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--fnc",
            "value": "<FUNCTION>",
            "required": true,
            "description": "Name of the function whose signature is shown."
        }
    ]}
/>

```bash
surreal db module sig --fnc can_drive demo.surli
```

## surreal db module info {#module-info}

Show the information held by a compiled WASM binary.

<Synopsis>
surreal db module info [OPTIONS] <FILE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<FILE>",
            "required": true,
            "description": "Path to the compiled `.surli` module."
        }
    ]}
/>

```bash
surreal db module info demo.surli
```

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db module --help` command without any further arguments. This output lists subcommands, global options, and shared logging flags for the `module` command.

```bash
surreal db module --help
```

The output of the above command:

```text
Manage and execute WASM modules

Usage: surreal module [OPTIONS] <COMMAND>

Commands:
  init   Initialize a new Surrealism module project
  run    Run a function with arguments
  sig    Show the function signature
  info   Show the module information
  build  Build a WASM module
  help   Print this message or the help of the given subcommand(s)

Options:
  -h, --help  Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible values: daily, hourly, never]
```

## Further reading

- [Surrealism extensions overview](/docs/learn/extensions/plugins/overview.md) - end-to-end workflow
- [Creating custom modules](/docs/learn/extensions/guides/creating-custom-modules.md) - attributes, `surrealism.toml`, and attached filesystems

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/sql

# sql

Open a SurrealQL REPL, or pipe queries in from a script.

Start a SurrealQL REPL in your terminal with the [installed](/docs/explore/cli/install.md) `surreal` CLI, with pipe support for one-shot queries.

<Synopsis>
surreal db sql [OPTIONS]
</Synopsis>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--endpoint",
            "short": "-e",
            "value": "<ENDPOINT>",
            "default": "ws://localhost:8000",
            "description": "Remote database server URL to connect to, or an embedded storage path such as `memory` or `rocksdb://mydb`."
        },
        {
            "name": "--username",
            "short": "-u",
            "value": "<USERNAME>",
            "env": "SURREAL_USER",
            "description": "Database authentication username to use when connecting. Alias: `--user`."
        },
        {
            "name": "--password",
            "short": "-p",
            "value": "<PASSWORD>",
            "env": "SURREAL_PASS",
            "description": "Database authentication password to use when connecting. Alias: `--pass`."
        },
        {
            "name": "--token",
            "short": "-t",
            "value": "<TOKEN>",
            "env": "SURREAL_TOKEN",
            "description": "Authentication token in JWT format, used instead of a username and password. Cannot be combined with `--username`, `--password` or `--auth-level`."
        },
        {
            "name": "--auth-level",
            "value": "<AUTH_LEVEL>",
            "default": "root",
            "env": "SURREAL_AUTH_LEVEL",
            "description": "Level on which the authenticating user is defined. Possible values: `root`, `namespace` (`ns`), `database` (`db`)."
        },
        {
            "name": "--namespace",
            "value": "<NAMESPACE>",
            "env": "SURREAL_NAMESPACE",
            "description": "The namespace to select. Alias: `--ns`. Defaults to `main` from SurrealDB 3.0."
        },
        {
            "name": "--database",
            "value": "<DATABASE>",
            "env": "SURREAL_DATABASE",
            "description": "The database to select. Alias: `--db`. Defaults to `main` from SurrealDB 3.0."
        },
        {
            "name": "--pretty",
            "description": "Whether database responses should be pretty printed."
        },
        {
            "name": "--json",
            "description": "Whether to emit results in JSON."
        },
        {
            "name": "--multi",
            "description": "Whether omitting a semicolon causes a newline instead of submitting the statement."
        },
        {
            "name": "--hide-welcome",
            "env": "SURREAL_HIDE_WELCOME",
            "description": "Whether to hide the welcome message, which is useful when piping statements in."
        },
        {
            "name": "--allow-experimental",
            "value": "<TARGETS>",
            "env": "SURREAL_CAPS_ALLOW_EXPERIMENTAL",
            "description": "Experimental capabilities to enable, as a comma-separated list. Possible values: `files`, `surrealism`."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

`surreal db sql` also accepts the capability flags shown under `Capabilities` in the [command help](#command-help) - `--allow-all`, `--allow-funcs`, `--deny-net`, and the rest. Where they take effect depends on how you connect; see [Capabilities and remote connections](#capabilities-and-remote-connections).

## Example usage

To start an interactive REPL, run the `surreal db sql` command with the required arguments. Once you see the `>` character you can type SurrealQL, followed by the `enter` key. The command has support for `↑` and `↓` arrows for selecting previous SQL statements, and stores the statement history in a `history.txt` file. To exit the REPL, use the `ctrl + c` or `ctrl + d` key combinations.

```bash
surreal db sql --endpoint http://localhost:8000 --namespace main --database main --auth-level root --username username --password password
```

For a one-shot query without staying in the REPL, pipe SurrealQL into `surreal db sql`. This is only for a small number of statements; for larger scripts use the [import command](/docs/reference/cli/db/commands/import.md).

**Bash**

```bash
echo 'INFO FOR DB;' | surreal db sql --endpoint http://localhost:8000 --username root --password secret --namespace main --database main --pretty --hide-welcome
```

**PowerShell**

```powershell
'INFO FOR DB;' | surreal db sql --endpoint http://localhost:8000 --username root --password secret --namespace main --database main --pretty --hide-welcome
```

You can also pipe a file:

```bash
cat myfile.surql | surreal db sql --endpoint http://localhost:8000 --username root --password secret --namespace main --database main
```

### Default namespace and database

_(since v3.0.0)_

As the `surreal db start` command defaults to defining a namespace and database by the name `main` upon starting up, the `surreal db sql` command also connects to these two by default. As such, a command like `surreal db sql --user root --pass secret` or even just `surreal db sql` (for an instance with authentication disabled) is all that is needed to connect and begin using an instance via the CLI.

A [`USE`](/docs/reference/query-language/statements/use.md) statement changes the namespace and database for every statement after it in the same session, in the REPL and in piped input, so it does not need to be repeated on each line. The REPL prompt shows the current namespace and database, and the `USE` statement itself [returns them](/docs/reference/query-language/statements/use.md#value-returned-by-use-statement):

```text
main/main> USE NS shop DB sales;
[{ database: 'sales', namespace: 'shop' }]

shop/sales> CREATE person:one;
[[{ id: person:one }]]
```

## Using environment variables

> [!IMPORTANT]
> Most of the flags above have a corresponding [environment variable](/docs/reference/cli/db/environment-variables.md#command-environment-variables).
> For example, the `--database` flag can be configured with the `SURREAL_DATABASE` environment variable instead.

When using the `surreal db sql` command, you can also use environment variables to set the values for the command-line flags.

For more on the environment variables available for CLI commands or SurrealDB instances in general, see the [environment variables](/docs/reference/cli/db/environment-variables.md#command-environment-variables) page.

## `--auth-level` option

The `--auth-level` option sets the authentication level to use when connecting to the database. The option has three possible values: `root`, `namespace`, and `database`. The `root` value is the highest level of authentication, while the `namespace` and `database` values are used for authenticating as users defined on a specific namespace or database.

There are a few things to keep in mind when using the `--auth-level` option:

- The `root` value is used to access the database server as a root user, and if not specified is the default value.

```bash
surreal db sql --endpoint http://localhost:8000 --namespace main --database main --auth-level root --username username --password password
```

- The `namespace` value is used for accessing a specific namespace and all databases within that namespace. When this level is specified, a namespace must be provided via `--namespace`.

```bash
surreal db sql --endpoint http://localhost:8000 --namespace main --database main --auth-level namespace --username username --password password
```

- The `database` value is used for accessing a specific database within a namespace. When this level is specified, a namespace and a database must be provided via `--namespace` and `--database`.

```bash
surreal db sql --endpoint http://localhost:8000 --namespace main --database main --auth-level database --username username --password password
```

## `--token` option

The `--token` option sets the authentication token to use when connecting to the server. This option allows you to connect to SurrealDB using a JWT instead of user credentials. The token is used to authenticate the user and provide access to the database server which means it cannot be provided at the same time as `--username`, `--password` or `--auth-level`.

```bash
surreal db sql --endpoint http://localhost:8000 --namespace main --database main --token <token>
```

## Capabilities and remote connections

Both `surreal db sql` and [`surreal db start`](/docs/reference/cli/db/commands/start.md) accept the same capability flags (`--allow-funcs`, `--allow-eval-query`, `--allow-experimental`, and so on). **Where those flags take effect depends on how you connect.**

| Connection | Examples | Where capabilities are enforced |
| --- | --- | --- |
| **Remote server** | `ws://localhost:8000`, `http://…`, `grpc://…` with sign-in | On the **server** process started with `surreal db start` |
| **Embedded engine** | `memory`, `rocksdb://…`, `surrealkv://…` (no separate server) | On the **`surreal db sql` process** itself |

> [!NOTE]
> `grpc://` and `grpcs://` endpoints are accepted from SurrealDB 3.3.0. The server always mounts the gRPC transport, and the `surreal` binary now ships the client, so `surreal db sql --endpoint grpc://127.0.0.1:8000` connects without a custom build. On earlier versions the endpoint was rejected as an invalid connection string or an invalid URI.

When you use `surreal db sql` against a **running instance**, flags on the REPL command do **not** change what the server allows at execution time. Configure [`surreal db start`](/docs/reference/cli/db/commands/start.md) (or the server's environment variables) instead - including for [`eval::*`](/docs/reference/query-language/functions/database-functions/eval.md), arbitrary-query gates, and experimental features such as Surrealism.

Capability flags on `surreal db sql` still matter in two cases:

- **Embedded storage** - there is no separate server; pass the flags on `surreal db sql` directly.
- **REPL syntax validation** - the client uses its capability set when checking whether a line parses before you submit it. That can affect experimental syntax in the prompt; it is **not** the security boundary for remote execution.

## Connecting to a Cloud instance

`surreal cloud instance sql <name>` wraps this command: it resolves the endpoint and credentials of a SurrealDB Cloud instance and then hands off to `surreal db sql`, passing through any flags given after `--`. Everything on this page applies to that session. See the [surreal cloud reference](/docs/reference/cli/cloud.md) for the control-plane side.

## Experimental capabilities

> [!NOTE]
> The experimental capability is completely hidden in the CLI help command, and `--allow-all` will not enable the experimental capabilities by default.

To use experimental capabilities, set the `SURREAL_CAPS_ALLOW_EXPERIMENTAL` [environment variable](/docs/reference/cli/db/environment-variables.md) to the experimental capability you want to allow.

For **embedded** `surreal db sql` (for example `surreal db sql memory`), set the variable or flag on the REPL command. For a **remote** server, set it on [`surreal db start`](/docs/reference/cli/db/commands/start.md#experimental-capabilities) instead - see [Capabilities and remote connections](#capabilities-and-remote-connections).

For example, to use [Surrealism](/docs/learn/extensions/plugins/overview.md) extensions in an embedded session:

**Bash**

```bash
SURREAL_CAPS_ALLOW_EXPERIMENTAL=surrealism surreal db sql ...
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "surrealism"
surreal db sql ...
```

or, using the `--allow-experimental` flag:

```bash
surreal db sql -e [CONNECTION_STRING] --allow-experimental surrealism
```

Multiple experimental capabilities can be enabled by separating them with a comma.

**Bash**

```bash
SURREAL_CAPS_ALLOW_EXPERIMENTAL=surrealism,files surreal db sql ...

-- OR

surreal db sql -e [CONNECTION_STRING] --allow-experimental surrealism,files
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "surrealism,files"
surreal db sql ...

-- OR

surreal db sql -e [CONNECTION_STRING] --allow-experimental surrealism,files
```

The current experimental targets are `files` and `surrealism`. The legacy tag `gql` is still accepted for compatibility but has no effect from 3.3.0 - [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md) is enabled by default.

| Example feature/statement | Tag |
| --- | --- |
| [DEFINE BUCKET](/docs/reference/query-language/statements/define/bucket.md) | `files` |
| [DEFINE MODULE](/docs/reference/query-language/statements/define/module.md) | `surrealism` |

> [!NOTE]
> When connecting to a **remote** server (`ws://`, `http://`, …), experimental features are enforced on the server. Configure them with [`surreal db start`](/docs/reference/cli/db/commands/start.md#experimental-capabilities) (or the server's `SURREAL_CAPS_ALLOW_EXPERIMENTAL`). Flags on `surreal db sql` only affect [embedded connections](#capabilities-and-remote-connections) or REPL parse validation. From 3.3.0, [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md) does not need an experimental flag; `eval::gql` still needs [`--allow-eval-query`](/docs/learn/security/authorization/capabilities.md#eval-queries) on the server.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db sql --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `sql` command.

```bash
surreal db sql --help
```

The output of the above command:

```text
Start an SQL REPL in your terminal with pipe support

Usage: surreal sql [OPTIONS]

Options:
  -e, --endpoint <ENDPOINT>
          Remote database server url to connect to

          [default: ws://localhost:8000]

  -u, --username <USERNAME>
          Database authentication username to use when connecting

          [env: SURREAL_USER=]
          [aliases: --user]

  -p, --password <PASSWORD>
          Database authentication password to use when connecting

          [env: SURREAL_PASS=]
          [aliases: --pass]

  -t, --token <TOKEN>
          Authentication token in JWT format to use when connecting

          [env: SURREAL_TOKEN=]

      --auth-level <AUTH_LEVEL>
          Level on which the authenticating user is defined

          [env: SURREAL_AUTH_LEVEL=]
          [default: root]
          [possible values: root, namespace, ns, database, db]

      --namespace <NAMESPACE>
          The selected namespace

          [env: SURREAL_NAMESPACE=]
          [aliases: --ns]

      --database <DATABASE>
          The selected database

          [env: SURREAL_DATABASE=]
          [aliases: --db]

      --pretty
          Whether database responses should be pretty printed

      --json
          Whether to emit results in JSON

      --multi
          Whether omitting semicolon causes a newline

      --hide-welcome
          Whether to show welcome message

          [env: SURREAL_HIDE_WELCOME=]

  -h, --help
          Print help (see a summary with '-h')

Capabilities:
  -A, --allow-all
          Allow all capabilities except for those more specifically denied

          [env: SURREAL_CAPS_ALLOW_ALL=]

      --allow-scripting
          Allow execution of embedded scripting functions

          [env: SURREAL_CAPS_ALLOW_SCRIPT=]

      --allow-guests
          Allow guest users to execute queries

          [env: SURREAL_CAPS_ALLOW_GUESTS=]

      --allow-funcs [<ALLOW_FUNCS>...]
          Allow execution of all functions except for functions that are specifically denied. Alternatively, you can provide a
          comma-separated list of function names to allow
          Specifically denied functions and function families prevail over any other allowed function execution.
          Function names must be in the form <family>[::<name>]. For example:
           - 'http' or 'http::*' -> Include all functions in the 'http' family
           - 'http::get' -> Include only the 'get' function in the 'http' family

          [env: SURREAL_CAPS_ALLOW_FUNC=]

      --allow-arbitrary-query [<ALLOW_ARBITRARY_QUERY>...]
          Allow execution of arbitrary queries by certain user groups except when specifically denied. Alternatively, you can provide a
          comma-separated list of user groups to allow
          Specifically denied user groups prevail over any other allowed user group.
          User groups must be one of "guest", "record" or "system".

          [env: SURREAL_CAPS_ALLOW_ARBITRARY_QUERY=]

      --allow-net [<ALLOW_NET>...]
          Allow all outbound network connections except for network targets that are specifically denied. Alternatively, you can provide a
          comma-separated list of network targets to allow
          Specifically denied network targets prevail over any other allowed outbound network connections.
          Targets must be in the form of <host>[:<port>], <ipv4|ipv6>[/<mask>]. For example:
           - 'surrealdb.com', '127.0.0.1' or 'fd00::1' -> Match outbound connections to these hosts on any port
           - 'surrealdb.com:80', '127.0.0.1:80' or 'fd00::1:80' -> Match outbound connections to these hosts on port 80
           - '10.0.0.0/8' or 'fd00::/8' -> Match outbound connections to any host in these networks

          [env: SURREAL_CAPS_ALLOW_NET=]

      --allow-rpc [<ALLOW_RPC>...]
          Allow all RPC methods to be called except for routes that are specifically denied. Alternatively, you can provide a comma-separated
          list of RPC methods to allow.

          [env: SURREAL_CAPS_ALLOW_RPC=]
          [default: ]

      --allow-http [<ALLOW_HTTP>...]
          Allow all HTTP routes to be requested except for routes that are specifically denied. Alternatively, you can provide a
          comma-separated list of HTTP routes to allow.

          [env: SURREAL_CAPS_ALLOW_HTTP=]
          [default: ]

  -D, --deny-all
          Deny all capabilities except for those more specifically allowed

          [env: SURREAL_CAPS_DENY_ALL=]

      --deny-scripting
          Deny execution of embedded scripting functions

          [env: SURREAL_CAPS_DENY_SCRIPT=]

      --deny-guests
          Deny guest users to execute queries

          [env: SURREAL_CAPS_DENY_GUESTS=]

      --deny-funcs [<DENY_FUNCS>...]
          Deny execution of all functions except for functions that are specifically allowed. Alternatively, you can provide a
          comma-separated list of function names to deny.
          Specifically allowed functions and function families prevail over a general denial of function execution.
          Function names must be in the form <family>[::<name>]. For example:
           - 'http' or 'http::*' -> Include all functions in the 'http' family
           - 'http::get' -> Include only the 'get' function in the 'http' family

          [env: SURREAL_CAPS_DENY_FUNC=]

      --deny-arbitrary-query [<DENY_ARBITRARY_QUERY>...]
          Deny execution of arbitrary queries by certain user groups except when specifically allowed. Alternatively, you can provide a
          comma-separated list of user groups to deny
          Specifically allowed user groups prevail over a general denial of user group.
          User groups must be one of "guest", "record" or "system".

          [env: SURREAL_CAPS_DENY_ARBITRARY_QUERY=]

      --deny-net [<DENY_NET>...]
          Deny all outbound network connections except for network targets that are specifically allowed. Alternatively, you can provide a
          comma-separated list of network targets to deny.
          Specifically allowed network targets prevail over a general denial of outbound network connections.
          Targets must be in the form of <host>[:<port>], <ipv4|ipv6>[/<mask>]. For example:
           - 'surrealdb.com', '127.0.0.1' or 'fd00::1' -> Match outbound connections to these hosts on any port
           - 'surrealdb.com:80', '127.0.0.1:80' or 'fd00::1:80' -> Match outbound connections to these hosts on port 80
           - '10.0.0.0/8' or 'fd00::/8' -> Match outbound connections to any host in these networks

          [env: SURREAL_CAPS_DENY_NET=]

      --deny-rpc [<DENY_RPC>...]
          Deny all RPC methods from being called except for methods that are specifically allowed. Alternatively, you can provide a
          comma-separated list of RPC methods to deny.

          [env: SURREAL_CAPS_DENY_RPC=]

      --deny-http [<DENY_HTTP>...]
          Deny all HTTP routes from being requested except for routes that are specifically allowed. Alternatively, you can provide a
          comma-separated list of HTTP routes to deny.

          [env: SURREAL_CAPS_DENY_HTTP=]

Logging:
  -l, --log <LOG>
          The logging level for the command-line tool

          [env: SURREAL_LOG=]
          [default: info]
          [possible values: none, full, error, warn, info, debug, trace]

      --log-format <LOG_FORMAT>
          The format for terminal log output

          [env: SURREAL_LOG_FORMAT=]
          [default: text]
          [possible values: text, json]

      --log-socket <LOG_SOCKET>
          Send logs to the specified host:port

          [env: SURREAL_LOG_SOCKET=]

      --log-file-level <LOG_FILE_LEVEL>
          Override the logging level for file output

          [env: SURREAL_LOG_FILE_LEVEL=]
          [possible values: none, full, error, warn, info, debug, trace]

      --log-otel-level <LOG_OTEL_LEVEL>
          Override the logging level for OpenTelemetry output

          [env: SURREAL_LOG_OTEL_LEVEL=]
          [possible values: none, full, error, warn, info, debug, trace]

      --log-socket-level <LOG_SOCKET_LEVEL>
          Override the logging level for unix socket output

          [env: SURREAL_LOG_SOCKET_LEVEL=]
          [possible values: none, full, error, warn, info, debug, trace]

      --log-socket-format <LOG_SOCKET_FORMAT>
          The format for socket output

          [env: SURREAL_LOG_SOCKET_FORMAT=]
          [default: text]
          [possible values: text, json]

      --log-file-enabled
          Whether to enable log file output

          [env: SURREAL_LOG_FILE_ENABLED=]

      --log-file-path <LOG_FILE_PATH>
          The directory where log files will be stored

          [env: SURREAL_LOG_FILE_PATH=]
          [default: logs]

      --log-file-name <LOG_FILE_NAME>
          The name of the log file

          [env: SURREAL_LOG_FILE_NAME=]
          [default: surrealdb.log]

      --log-file-format <LOG_FILE_FORMAT>
          The format for log file output

          [env: SURREAL_LOG_FILE_FORMAT=]
          [default: text]
          [possible values: text, json]

      --log-file-rotation <LOG_FILE_ROTATION>
          The log file rotation interval

          [env: SURREAL_LOG_FILE_ROTATION=]
          [default: daily]
          [possible values: daily, hourly, never]
```

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/start

# start

Run a server in memory, on disk, or as a node in a cluster.

The start command starts a SurrealDB server in memory, on disk, or in a distributed setup, using the [installed](/docs/explore/cli/install.md) `surreal` CLI.

This page is divided into two views: the details of the `surreal db start` command itself, and a builder that creates a command based on the options you choose.

**surreal db start details**

<Synopsis>
surreal db start [OPTIONS] [PATH]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[PATH]",
            "default": "memory",
            "env": "SURREAL_PATH",
            "description": "Where the server stores data. Combine a backend name with `:` or `://` and an address or filename, for example `surrealkv://mydb` or `rocksdb:database`. See the Positional argument section below."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--bind",
            "short": "-b",
            "value": "<LISTEN_ADDRESSES>",
            "default": "127.0.0.1:8000",
            "env": "SURREAL_BIND",
            "description": "The hostname or IP address to listen for connections on."
        },
        {
            "name": "--postgres-bind",
            "description": "The hostname or IP address to listen for Postgres wire protocol connections on, for example `127.0.0.1:5432`. The listener is disabled when omitted."
        },
        {
            "name": "--client-ip",
            "value": "<CLIENT_IP>",
            "default": "socket",
            "env": "SURREAL_CLIENT_IP",
            "description": "The method of detecting the client's IP address. Possible values: `none`, `socket`, `CF-Connecting-IP`, `Fly-Client-IP`, `True-Client-IP`, `X-Real-IP`, `X-Forwarded-For`, `Forwarded`."
        },
        {
            "name": "--import-file",
            "value": "<IMPORT_FILE>",
            "env": "SURREAL_IMPORT_FILE",
            "description": "Path to a SurrealQL (`.surql`) file that is imported when starting the server."
        },
        {
            "name": "--username",
            "short": "-u",
            "value": "<USERNAME>",
            "env": "SURREAL_USER",
            "description": "The username for the initial database root user, applied only if no other root user exists. Alias: `--user`."
        },
        {
            "name": "--password",
            "short": "-p",
            "value": "<PASSWORD>",
            "env": "SURREAL_PASS",
            "description": "The password for the initial database root user, applied only if no other root user exists. Alias: `--pass`."
        },
        {
            "name": "--default-namespace",
            "value": "<DEFAULT_NAMESPACE>",
            "env": "SURREAL_DEFAULT_NAMESPACE",
            "default": "main",
            "description": "The namespace to create when the datastore is new."
        },
        {
            "name": "--default-database",
            "value": "<DEFAULT_DATABASE>",
            "env": "SURREAL_DEFAULT_DATABASE",
            "default": "main",
            "description": "The database to create in that namespace when the datastore is new."
        },
        {
            "name": "--no-defaults",
            "env": "SURREAL_NO_DEFAULTS",
            "description": "Create no namespace or database when the datastore is new."
        },
        {
            "name": "--unauthenticated",
            "env": "SURREAL_UNAUTHENTICATED",
            "description": "Whether to allow unauthenticated access. See Unauthenticated mode below."
        },
        {
            "name": "--no-identification-headers",
            "env": "SURREAL_NO_IDENTIFICATION_HEADERS",
            "description": "Whether to suppress the server name and version headers."
        },
        {
            "name": "--temporary-directory",
            "value": "<TEMPORARY_DIRECTORY>",
            "env": "SURREAL_TEMPORARY_DIRECTORY",
            "description": "The directory for storing temporary database files."
        },
        {
            "name": "--allow-experimental",
            "value": "<TARGETS>",
            "env": "SURREAL_CAPS_ALLOW_EXPERIMENTAL",
            "description": "Experimental capabilities to enable, as a comma-separated list. Possible values: `files`, `surrealism`. Takes several values, so use `--allow-experimental=files,surrealism` when a storage path follows it."
        },
        {
            "name": "--durable-sessions",
            "env": "SURREAL_DURABLE_SESSIONS",
            "description": "Whether to persist client-attached HTTP RPC sessions in the datastore. Off by default."
        },
        {
            "name": "--durable-session-ttl",
            "value": "<DURABLE_SESSION_TTL>",
            "default": "24h",
            "env": "SURREAL_DURABLE_SESSION_TTL",
            "description": "Idle lifetime of a persisted HTTP RPC session before it expires. Each use refreshes the expiry."
        },
        {
            "name": "--durable-session-gc-interval",
            "value": "<DURABLE_SESSION_GC_INTERVAL>",
            "default": "60s",
            "env": "SURREAL_DURABLE_SESSION_GC_INTERVAL",
            "description": "Interval for purging expired durable HTTP RPC sessions. Set `0` to disable the sweep."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the database server. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

The root user from `--username` and `--password`, and the default namespace and database, are created only when the datastore is new, and are stored in it. Starting the same datastore again with other values changes nothing: the existing root user keeps its password, and no new namespace or database is created. To change the password of an existing root user, use [`ALTER USER`](/docs/reference/query-language/statements/alter/user.md) or [`DEFINE USER OVERWRITE`](/docs/reference/query-language/statements/define/user.md).

`surreal db start` has more options than any other subcommand, and the table above covers the ones described on this page. The remaining groups - database tuning, datastore TLS, HTTP server, capabilities, and logging - are listed in full in the [command help](#command-help) below, and mapped to their variables on the [environment variables](/docs/reference/cli/db/environment-variables.md) page.

Two of the flags above are documented in more detail elsewhere: `--postgres-bind` exposes the [Postgres wire protocol](/docs/reference/rest-api/postgres-protocol.md), and the `--durable-*` flags are explained under [Durable HTTP RPC sessions](#durable-http-rpc-sessions). From SurrealDB 3.1, the `Forwarded` value for `--client-ip` reads the RFC 7239 `Forwarded` header.

The `--query-timeout` flag in the [command help](#command-help) sets the maximum duration that a batch of statements can run for, and is off by default. From 3.3.0 the same value is also a hard wall-clock limit on each HTTP `/sql`, `/gql` and `/graphql` request and on each RPC method call. An HTTP `/sql` or `/gql` request that exceeds it is answered with `504 Gateway Timeout`, and a `/graphql` request with a GraphQL error. The `begin`, `commit` and `cancel` RPC methods are exempt, but the calls made inside the transaction they control are still limited individually. See [`SURREAL_QUERY_TIMEOUT`](/docs/reference/cli/db/environment-variables.md#command-environment-variables).

## Getting started

This example will show how to host a SurrealDB server with the `surreal db start` command, and then access the Surreal DB server using the [`surreal db sql` command](/docs/reference/cli/db/commands/sql.md).

To start a SurrealDB server, run the `surreal db start` command, using the options below. This example stores the database in memory, with a username and password, hosted at `127.0.0.1:8000` (the default location).

```bash
surreal db start memory --user my_username --pass my_password
```

The server is actively running, and can be left alone until you want to stop hosting the SurrealDB server.

> [!NOTE]
> The message "Started web server on 127.0.0.1:8000", indicates where the server is being hosted and can be accessed by clients. The location `127.0.0.1:8000` is the default, and can be manually changed by specifying the `--bind` option of the `surreal db start` command.

To access the SurrealDB server that you have started hosting, open a new terminal which will act as the client, while the previous terminal is still running the `surreal db start` command described above. This is done using a separate [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) command. Ensure that the hosting location indicated by the output of the `surreal db start` command is passed to the `--endpoint` argument, and that you specify the same `--username` and `--password` as in the `surreal db start` command.

A particular namespace and database can be specified at this point, as seen below.

```bash
surreal db sql --endpoint http://127.0.0.1:8000 --namespace my_namespace \
  --database my_database --username my_username --password my_password
```

## Positional argument

> [!WARNING]
> FoundationDB support was removed in SurrealDB 3.0.0, so `fdb://` paths are only accepted by 2.x. Migrate a FoundationDB deployment to one of the backends below before upgrading.

In the `surreal db start` command, the path argument is used to specify the location of the database. If no argument is given, the default of `memory` for storage [in memory](/docs/running/in-memory.md) is assumed.

Arguments for persistent backends are a combination of the backend name, a `:` or `://`, and an address or filename - for example `surrealkv://mydb` or `rocksdb:database`. The available backends are:

- `memory` (or no argument) for in-memory storage
- `rocksdb` for RocksDB
- `surrealkv` for SurrealKV
- `indxdb` for IndexedDB
- `tikv` for TiKV

### Absolute vs. relative paths

The datastorage flavour (`rocksdb`, `surrealkv`, etc.) followed by `:` or `://` will be recognised as a relative path, which is resolved from the directory you run `surreal db start` in. Running `surreal db start rocksdb:mydb` from `/home/ann/projects` stores the data in `/home/ann/projects/mydb`, and running the same command from another directory creates a new, empty database there. Use an absolute path for a server that is started from different places, such as by a service manager. Any other number of slashes such as `rocksdb:/path` or `surrealkv:///path` will be interpreted as an absolute path. As a short absolute path of this nature will often require elevated permissions, the output for this command may end in this sort of error.

```text
Failed to create RocksDB directory: `Os { code: 30, kind: ReadOnlyFilesystem, message: "Read-only file system" }`.
```

If you see this error without having intended to start the server on an absolute path, it is likely that the path passed in unintentionally contains either one slash or more than two slashes.

> [!NOTE]
> Be sure not to use multiple storage backends in the same location, such as `rocksdb://path/to/database` followed by `surrealkv://path/to/database`. As storage is entirely delegated to the backend, the CLI is not aware of the structure of the data itself. While each backend uses its own file names and directory structure to store data, it is possible that data overwrite or other issues may occur.

> [!IMPORTANT]
> **TiKV** (`tikv://…`) is supported for local multi-node experimentation with the Community edition. Production multi-node HA uses distributed storage on [SurrealDB Cloud Scale](https://surrealdb.com/pricing/scale) or [SurrealDB Enterprise](https://surrealdb.com/enterprise). See [Run a multi-node cluster](/docs/running/multi-node.md) and [Deployment](/docs/manage/self-hosted/deployment-models.md). To provision and scale those instances from a script rather than a browser, see [`surreal cloud`](/docs/reference/cli/cloud.md).

## Datastore configuration

**3.x**

Configuring a datastore is done by passing in a string that begins with the datastore name, a path if persistent storage is needed, and optional parameters. Some examples:

```bash
surreal db start --user root --pass secret "surrealkv://path/to/db?versioned=true&sync=every&retention=30d"
surreal db start --user root --pass secret "rocksdb://path/to/rocksdb?versioned=true&retention=30d"
surreal db start --user root --pass secret "mem://tmp/data?aol=sync&snapshot=60s&sync=5s"
```

Each database store supports different parameters.

### Supported parameters for Memory (SurrealMX)

The memory backend does not support versioned queries. From 3.3.0 a server started on it with `versioned=true` or a non-zero `retention`, whether through the parameters or the matching `SURREAL_DATASTORE_VERSIONED` and `SURREAL_DATASTORE_RETENTION` environment variables, fails to start. `versioned=false` and a zero retention are accepted. To use the [`VERSION` clause](/docs/reference/query-language/statements/select.md#the-version-clause), start the server on SurrealKV or RocksDB instead.

- `aol` (`never`, `sync`, or `async`), for writing changes to an append-only log file
- `snapshot` (a duration string, e.g. `60s`), for periodically writing a snapshot of the database to the file system
- `sync` (`never`, `every`, or a duration string like `5s`), for specifying when to flush the append-only log file to the file system
  - `never` - (default) leave flushing to the OS (least durable)
  - `every` - sync on every commit (most durable)
  - `interval` - periodic background flushing at the given interval

Persistence for the memory backend is used for the following:

- Append-Only Log (AOL) - Synchronous/asynchronous modes for durability
- Snapshots - Periodic full database state capture
- Data Recovery - Automatic recovery from snapshots + AOL on startup
- AOL Truncation - Automatic cleanup after snapshots

### Supported parameters for SurrealKV
- `versioned` (`true` or `false`)
- `retention` (a duration string, e.g. `30d`, `24h`, `30m`)
- `sync` (`never`, `every`, or a duration string like `5s`), for specifying when to flush the database to the file system
  - `every` - (default) sync before completing and confirming each transaction (most durable)
  - `never` - leave flushing to the OS (least durable)
  - `interval` - periodic background flushing at the given interval

### Supported parameters for RocksDB
- `versioned` (`true` or `false`)
- `retention` (a duration string, e.g. `30d`, `24h`, `30m`)
- `sync` (`never`, `every`, or a duration string like `5s`), for specifying when to flush the database to the file system
  - `every` - (default) sync before completing and confirming each transaction (most durable)
  - `never` - leave flushing to the OS (least durable)
  - `interval` - periodic background flushing at the given interval

**2.x**

To start a SurrealDB instance with RocksDB as the storage engine, include the `rocksdb://` prefix in the path argument.

```bash
surreal db start -u root -p secret rocksdb://mydb
```

To start a SurrealDB instance with SurrealKV as the storage engine, include the `surrealkv://` prefix in the path argument.

```bash
surreal db start -u root -p secret surrealkv://mydb
```

While SurrealKV supports historical/temporal querying using the `VERSION` clause when [selecting](/docs/reference/query-language/statements/select.md#the-version-clause) data, you must explicitly opt in to this using the `surrealkv+versioned://` prefix in the path argument.

```bash
surreal db start -u root -p secret surrealkv+versioned://mydb
```

## Authentication

When starting a SurrealDB instance, authentication is enabled by default, and your user credentials will be required to connect. If you are starting a new instance, the user credentials you use to run the `start` command will [define a new root user](/docs/reference/query-language/statements/define/user.md#roles) with the [`OWNER`](/docs/reference/query-language/statements/define/user.md#roles) role.

```bash
surreal db start --user root --password secret
```

## Unauthenticated mode

> [!NOTE]
> We recommend enabling authentication when running SurrealDB in production or in publicly exposed ways. Failure to do so may result in unauthorised access.

Using the `--unauthenticated` flag, you can also start a SurrealDB instance in unauthenticated mode. By doing so, authentication will be disabled. In this mode, any guest user is considered to have the same permissions as a root user with the [`OWNER`](/docs/reference/query-language/statements/define/user.md#roles) role.

To start a SurrealDB instance in unauthenticated mode, run the following command:

```bash
surreal db start --unauthenticated
```

## Using environment variables

> [!IMPORTANT]
> Most of the flags above have a corresponding [environment variable](/docs/reference/cli/db/environment-variables.md#command-environment-variables).
> For example, the `--temporary-directory` flag can be configured with the `SURREAL_TEMPORARY_DIRECTORY` environment variable instead.

When using the `surreal db start` command, you can also use environment variables to set the values for the command-line flags. This is useful when you want to set the values for the command-line flags without having to pass them directly on the command line.

For more on the environment variables available for CLI commands or SurrealDB instances in general, see the [environment variables](/docs/reference/cli/db/environment-variables.md#command-environment-variables) page.

## Stopping the server

To stop the server, press `Ctrl+C` in its terminal, or send it `SIGTERM` (as `docker stop`, `kill` and `systemctl stop` do). Either signal starts a graceful shutdown: the server stops accepting connections, the storage engine writes out what it holds in memory, and the process exits with status `0`.

```text
WARN surrealdb::net: SIGINT received. Waiting for a graceful shutdown. A second signal will force an immediate shutdown.
INFO surrealdb::net: Web server stopped. Bye!
```

A second signal during the shutdown stops the process at once, without waiting. On a persistent backend such as SurrealKV or RocksDB, data from committed transactions is kept either way, subject to the backend's [`sync` setting](#datastore-configuration). On the in-memory backend, all data is lost when the process stops unless [persistence](#supported-parameters-for-memory-surrealmx) is configured.

## Enabling capabilities

> [!NOTE]
> If using SurrealDB Cloud, capabilities can be set from [SurrealDB Studio](/docs/manage/instances/configure.md#capabilities) or with [`surreal cloud`](/docs/reference/cli/cloud.md).

Capabilities arguments such as `allow-scripting` or `deny-net` can also be passed into the `surreal db start` command. These arguments, the order in which they are evaluated, and other notes on security are presented in detail in a [separate page on capabilities](/docs/learn/security/authorization/capabilities.md).

A production-oriented example of the `surreal db start` command that begins with the `--deny-all` flag and only thereafter sets which capabilities will be allowed:

```bash
surreal db start --deny-all --allow-funcs "array, string, crypto::argon2, http::get" --allow-net api.example.com:443
```

## Strict mode

Versions before SurrealDB 3.0 contained a flag to start the entire server in strict mode. When running in strict mode, no `NAMESPACE`, `DATABASE`, or `TABLE` definitions were enacted automatically when data is inserted. Any queries would return an error if the selected namespace, database, or table were not specifically defined in advance.

```bash
surreal db start --strict --log debug memory
```

Since SurrealDB 3.0, strict mode is now enacted on the database level via a [`DEFINE DATABASE`](/docs/reference/query-language/statements/define/database.md) statement.

```surql
DEFINE DATABASE my_anything_goes_db;
DEFINE DATABASE my_strict_db STRICT;
```

## Experimental capabilities

> [!NOTE]
> The experimental capability is completely hidden in the CLI help command, and `--allow-all` will not enable the experimental capabilities by default.

To use experimental capabilities, set the `SURREAL_CAPS_ALLOW_EXPERIMENTAL` [environment variable](/docs/reference/cli/db/environment-variables.md) to the experimental capability you want to allow.

For example, to use [Surrealism](/docs/learn/extensions/plugins/overview.md) extensions, set the `SURREAL_CAPS_ALLOW_EXPERIMENTAL` environment variable to `surrealism` - or pass it in via the `--allow-experimental` flag.

**Bash**

```bash
# Allow experimental via an env var
SURREAL_CAPS_ALLOW_EXPERIMENTAL=surrealism surreal db start

# Allow experimental via a flag
surreal db start --allow-experimental surrealism
```

**PowerShell**

```powershell
# Allow experimental via an env var
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "surrealism"
surreal db start

# Allow experimental via a flag
surreal db start --allow-experimental surrealism
```

Multiple experimental capabilities can be enabled by separating them with a comma.

**Bash**

```bash
SURREAL_CAPS_ALLOW_EXPERIMENTAL=surrealism,files surreal db start
surreal db start --allow-experimental surrealism,files
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "surrealism,files"
surreal db start
surreal db start --allow-experimental surrealism,files
```

`--allow-experimental` accepts several values, so a storage path written after it is read as one more capability name, and the server refuses to start with `invalid experimental target name`. Join the value to the flag with `=`, or put the path before the flag:

```bash
surreal db start --allow-experimental=surrealism,files rocksdb:mydatabase.db
surreal db start rocksdb:mydatabase.db --allow-experimental surrealism,files
```

> [!NOTE]
> Experimental capabilities are enforced on the **server** for remote clients. If you use [`surreal db sql`](/docs/reference/cli/db/commands/sql.md) against `ws://` or `http://`, configure flags here - not only on the REPL. See [Capabilities and remote connections](/docs/reference/cli/db/commands/sql.md#capabilities-and-remote-connections).

> [!NOTE]
> From **3.3.0**, [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md) is enabled by default and no longer uses an experimental capability. The legacy tag `gql` is still accepted for compatibility but has no effect. On **3.2.x**, use `--allow-experimental gql`.

| Example feature/statement | Tag |
| --- | --- |
| [DEFINE BUCKET](/docs/reference/query-language/statements/define/bucket.md) | `files` |
| [DEFINE MODULE](/docs/reference/query-language/statements/define/module.md) | `surrealism` |

## Identification headers

By default, SurrealDB includes headers in the HTTP response that identify the server name and version. You can suppress these headers by using the `--no-identification-headers` flag.

```bash
surreal db start --no-identification-headers
```

## Durable HTTP RPC sessions

_(since v3.2.2)_

Client-attached sessions on the HTTP [`/rpc`](/docs/reference/rest-api/rpc-protocol.md) endpoint normally live only in the process memory of the node that created them. After a restart, or when a later request is routed to a different node, the client receives a session-not-found error.

The `--durable-sessions` flag can be used to store those sessions in the datastore so they survive restarts and can be resumed on any node that shares the same storage. Each use refreshes a sliding idle TTL (`--durable-session-ttl`, default `24h`). A background task removes expired entries (`--durable-session-gc-interval`, default `60s`; set `0` to disable the sweep). Expired sessions are still dropped when loaded.

```bash
surreal db start --durable-sessions --durable-session-ttl 12h rocksdb://mydb
```

> [!WARNING]
> The durable copy includes the session's authentication state and is stored **unencrypted** in the datastore. Sticky routing is recommended so a given session is used on one node at a time. Concurrent use from multiple nodes is best-effort.

WebSocket connections keep in-memory sessions only. This mode does not change WebSocket behaviour.

## Further examples

As `surreal db start` is the command with by far the largest number of options, a few more examples will help give an idea of what sort of configurations are available.

An instance with a single root user, able to connect to the internet but unable to use three functions:

```bash
surreal db start --user root --pass secret --allow-net --deny-funcs "crypto::md5, http::post, http::delete"
```

An instance with more verbose logging that uses RocksDB as its storage engine:

```bash
surreal db start --log debug rocksdb:mydatabase.db
```

An instance with all capabilities denied except a few functions and a single endpoint:

```bash
surreal db start --deny-all --allow-funcs "array, string, crypto::argon2, http::get" --allow-net api.example.com:443
```

An instance with a different default address, less verbose logging level, and ability to use JavaScript functions:

```bash
surreal db start --bind 0.0.0.0:2218 --log warn --allow-scripting
```

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db start --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `start` command.

```bash
surreal db start --help
```

The output of the above command:

```text
Start the database server

Usage: surreal start [OPTIONS] [PATH]

Arguments:
  [PATH]
          Database path used for storing data

          [env: SURREAL_PATH=]
          [default: memory]

Options:
      --no-banner
          Whether to hide the startup banner

          [env: SURREAL_NO_BANNER=]

      --index-compaction-interval <INDEX_COMPACTION_INTERVAL>
          [env: SURREAL_INDEX_COMPACTION_INTERVAL=]
          [default: 5s]

      --async-event-interval <EVENT_PROCESSING_INTERVAL>
          [env: SURREAL_ASYNC_EVENT_PROCESSING_INTERVAL=]
          [default: 5s]

  -h, --help
          Print help (see a summary with '-h')

Database:
      --node-membership-refresh-interval <NODE_MEMBERSHIP_REFRESH_INTERVAL>
          The interval at which to refresh node registration information

          [env: SURREAL_NODE_MEMBERSHIP_REFRESH_INTERVAL=]
          [default: 3s]

      --node-membership-check-interval <NODE_MEMBERSHIP_CHECK_INTERVAL>
          The interval at which to process and archive inactive nodes

          [env: SURREAL_NODE_MEMBERSHIP_CHECK_INTERVAL=]
          [default: 15s]

      --node-membership-cleanup-interval <NODE_MEMBERSHIP_CLEANUP_INTERVAL>
          The interval at which to process and cleanup archived nodes

          [env: SURREAL_NODE_MEMBERSHIP_CLEANUP_INTERVAL=]
          [default: 300s]

      --changefeed-gc-interval <CHANGEFEED_GC_INTERVAL>
          The interval at which to perform changefeed garbage collection

          [env: SURREAL_CHANGEFEED_GC_INTERVAL=]
          [default: 30s]

      --graph-fold-interval <GRAPH_FOLD_INTERVAL>
          The interval at which to fold graph adjacency keys into packed blocks

          [env: SURREAL_GRAPH_FOLD_INTERVAL=]
          [default: 5s]

      --query-timeout <QUERY_TIMEOUT>
          The maximum duration that a batch of statements can run for

          [env: SURREAL_QUERY_TIMEOUT=]

      --transaction-timeout <TRANSACTION_TIMEOUT>
          The maximum duration that any single transaction can run for

          [env: SURREAL_TRANSACTION_TIMEOUT=]

      --durable-sessions
          Whether to persist client-attached HTTP RPC sessions in the datastore so they survive server restarts and can be resumed on any cluster node. Intended for deployments that route a given session to one node at a time (sticky routing / one runtime per session); a session used concurrently from multiple nodes is best-effort. The durable copy contains the session's authentication state, stored unencrypted in the datastore

          [env: SURREAL_DURABLE_SESSIONS=]

      --durable-session-ttl <DURABLE_SESSION_TTL>
          How long a persisted RPC session survives without being used; each use refreshes the expiry

          [env: SURREAL_DURABLE_SESSION_TTL=]
          [default: 24h]

      --durable-session-gc-interval <DURABLE_SESSION_GC_INTERVAL>
          The interval at which expired persisted RPC sessions are purged (0 to disable)

          [env: SURREAL_DURABLE_SESSION_GC_INTERVAL=]
          [default: 60s]

Authentication:
  -u, --username <USERNAME>
          The username for the initial database root user. Only if no other root user exists

          [env: SURREAL_USER=]
          [aliases: --user]

  -p, --password <PASSWORD>
          The password for the initial database root user. Only if no other root user exists

          [env: SURREAL_PASS=]
          [aliases: --pass]

      --unauthenticated
          Whether to allow unauthenticated access

          [env: SURREAL_UNAUTHENTICATED=]

Datastore connection:
      --kvs-ca <KVS_CA>
          Path to the CA file used when connecting to the remote KV store

          [env: SURREAL_KVS_CA=]

      --kvs-crt <KVS_CRT>
          Path to the certificate file used when connecting to the remote KV store

          [env: SURREAL_KVS_CRT=]

      --kvs-key <KVS_KEY>
          Path to the private key file used when connecting to the remote KV store

          [env: SURREAL_KVS_KEY=]

HTTP server:
      --web-crt <WEB_CRT>
          Path to the certificate file for encrypted client connections

          [env: SURREAL_WEB_CRT=]

      --web-key <WEB_KEY>
          Path to the private key file for encrypted client connections

          [env: SURREAL_WEB_KEY=]

      --client-ip <CLIENT_IP>
          The method of detecting the client's IP address

          Possible values:
          - none:             Don't use client IP
          - socket:           Raw socket IP
          - CF-Connecting-IP: Cloudflare connecting IP
          - Fly-Client-IP:    Fly.io client IP
          - True-Client-IP:   Akamai, Cloudflare true client IP
          - X-Real-IP:        Nginx real IP
          - X-Forwarded-For:  Industry standard header used by many proxies
          - Forwarded:        RFC 7239 Forwarded header (for=)

          [env: SURREAL_CLIENT_IP=]
          [default: socket]

  -b, --bind <LISTEN_ADDRESSES>
          The hostname or IP address to listen for connections on

          [env: SURREAL_BIND=]
          [default: 127.0.0.1:8000]

      --no-identification-headers
          Whether to suppress the server name and version headers

          [env: SURREAL_NO_IDENTIFICATION_HEADERS=]

Capabilities:
  -A, --allow-all
          Allow all capabilities except for those more specifically denied

          [env: SURREAL_CAPS_ALLOW_ALL=]

      --allow-scripting
          Allow execution of embedded scripting functions

          [env: SURREAL_CAPS_ALLOW_SCRIPT=]

      --allow-guests
          Allow guest users to execute queries

          [env: SURREAL_CAPS_ALLOW_GUESTS=]

      --allow-funcs [<ALLOW_FUNCS>...]
          Allow execution of all functions except for functions that are specifically denied. Alternatively, you can provide a
          comma-separated list of function names to allow
          Specifically denied functions and function families prevail over any other allowed function execution.
          Function names must be in the form <family>[::<name>]. For example:
           - 'http' or 'http::*' -> Include all functions in the 'http' family
           - 'http::get' -> Include only the 'get' function in the 'http' family

          [env: SURREAL_CAPS_ALLOW_FUNC=]

      --allow-arbitrary-query [<ALLOW_ARBITRARY_QUERY>...]
          Allow execution of arbitrary queries by certain user groups except when specifically denied. Alternatively, you can provide a
          comma-separated list of user groups to allow
          Specifically denied user groups prevail over any other allowed user group.
          User groups must be one of "guest", "record" or "system".

          [env: SURREAL_CAPS_ALLOW_ARBITRARY_QUERY=]

      --allow-net [<ALLOW_NET>...]
          Allow all outbound network connections except for network targets that are specifically denied. Alternatively, you can provide a
          comma-separated list of network targets to allow
          Specifically denied network targets prevail over any other allowed outbound network connections.
          Targets must be in the form of <host>[:<port>], <ipv4|ipv6>[/<mask>]. For example:
           - 'surrealdb.com', '127.0.0.1' or 'fd00::1' -> Match outbound connections to these hosts on any port
           - 'surrealdb.com:80', '127.0.0.1:80' or 'fd00::1:80' -> Match outbound connections to these hosts on port 80
           - '10.0.0.0/8' or 'fd00::/8' -> Match outbound connections to any host in these networks

          [env: SURREAL_CAPS_ALLOW_NET=]

      --allow-rpc [<ALLOW_RPC>...]
          Allow all RPC methods to be called except for routes that are specifically denied. Alternatively, you can provide a comma-separated
          list of RPC methods to allow.

          [env: SURREAL_CAPS_ALLOW_RPC=]
          [default: ]

      --allow-http [<ALLOW_HTTP>...]
          Allow all HTTP routes to be requested except for routes that are specifically denied. Alternatively, you can provide a
          comma-separated list of HTTP routes to allow.

          [env: SURREAL_CAPS_ALLOW_HTTP=]
          [default: ]

  -D, --deny-all
          Deny all capabilities except for those more specifically allowed

          [env: SURREAL_CAPS_DENY_ALL=]

      --deny-scripting
          Deny execution of embedded scripting functions

          [env: SURREAL_CAPS_DENY_SCRIPT=]

      --deny-guests
          Deny guest users to execute queries

          [env: SURREAL_CAPS_DENY_GUESTS=]

      --deny-funcs [<DENY_FUNCS>...]
          Deny execution of all functions except for functions that are specifically allowed. Alternatively, you can provide a
          comma-separated list of function names to deny.
          Specifically allowed functions and function families prevail over a general denial of function execution.
          Function names must be in the form <family>[::<name>]. For example:
           - 'http' or 'http::*' -> Include all functions in the 'http' family
           - 'http::get' -> Include only the 'get' function in the 'http' family

          [env: SURREAL_CAPS_DENY_FUNC=]

      --deny-arbitrary-query [<DENY_ARBITRARY_QUERY>...]
          Deny execution of arbitrary queries by certain user groups except when specifically allowed. Alternatively, you can provide a
          comma-separated list of user groups to deny
          Specifically allowed user groups prevail over a general denial of user group.
          User groups must be one of "guest", "record" or "system".

          [env: SURREAL_CAPS_DENY_ARBITRARY_QUERY=]

      --deny-net [<DENY_NET>...]
          Deny all outbound network connections except for network targets that are specifically allowed. Alternatively, you can provide a
          comma-separated list of network targets to deny.
          Specifically allowed network targets prevail over a general denial of outbound network connections.
          Targets must be in the form of <host>[:<port>], <ipv4|ipv6>[/<mask>]. For example:
           - 'surrealdb.com', '127.0.0.1' or 'fd00::1' -> Match outbound connections to these hosts on any port
           - 'surrealdb.com:80', '127.0.0.1:80' or 'fd00::1:80' -> Match outbound connections to these hosts on port 80
           - '10.0.0.0/8' or 'fd00::/8' -> Match outbound connections to any host in these networks

          [env: SURREAL_CAPS_DENY_NET=]

      --deny-rpc [<DENY_RPC>...]
          Deny all RPC methods from being called except for methods that are specifically allowed. Alternatively, you can provide a
          comma-separated list of RPC methods to deny.

          [env: SURREAL_CAPS_DENY_RPC=]

      --deny-http [<DENY_HTTP>...]
          Deny all HTTP routes from being requested except for routes that are specifically allowed. Alternatively, you can provide a
          comma-separated list of HTTP routes to deny.

          [env: SURREAL_CAPS_DENY_HTTP=]

      --temporary-directory <TEMPORARY_DIRECTORY>
          Sets the directory for storing temporary database files

          [env: SURREAL_TEMPORARY_DIRECTORY=]

      --import-file <IMPORT_FILE>
          Path to a SurrealQL file that will be imported when starting the server

          [env: SURREAL_IMPORT_FILE=]

      --slow-log-threshold <SLOW_LOG_THRESHOLD>
          The minimum execution time in milliseconds to trigger slow query logging

          [env: SURREAL_SLOW_QUERY_LOG_THRESHOLD=]

      --slow-log-param-allow <SLOW_LOG_PARAM_ALLOW>...
          A comma-separated list of parameter names to include in slow query logs

          [env: SURREAL_SLOW_QUERY_LOG_PARAM_ALLOW=]

      --slow-log-param-deny <SLOW_LOG_PARAM_DENY>...
          A comma-separated list of parameter names to omit from slow query logs

          [env: SURREAL_SLOW_QUERY_LOG_PARAM_DENY=]

      --default-namespace <DEFAULT_NAMESPACE>
          The default namespace for a new instance

          [env: SURREAL_DEFAULT_NAMESPACE=]

      --default-database <DEFAULT_DATABASE>
          The default database for a new instance

          [env: SURREAL_DEFAULT_DATABASE=]

      --no-defaults
          Whether to disable default namespace and database creation

          [env: SURREAL_NO_DEFAULTS=]

Logging:
  -l, --log <LOG>
          The logging level for the command-line tool

          [env: SURREAL_LOG=]
          [default: info]
          [possible values: none, full, error, warn, info, debug, trace]

      --log-format <LOG_FORMAT>
          The format for terminal log output

          [env: SURREAL_LOG_FORMAT=]
          [default: text]
          [possible values: text, json]

      --log-socket <LOG_SOCKET>
          Send logs to the specified host:port

          [env: SURREAL_LOG_SOCKET=]

      --log-file-level <LOG_FILE_LEVEL>
          Override the logging level for file output

          [env: SURREAL_LOG_FILE_LEVEL=]
          [possible values: none, full, error, warn, info, debug, trace]

      --log-otel-level <LOG_OTEL_LEVEL>
          Override the logging level for OpenTelemetry output

          [env: SURREAL_LOG_OTEL_LEVEL=]
          [possible values: none, full, error, warn, info, debug, trace]

      --log-socket-level <LOG_SOCKET_LEVEL>
          Override the logging level for unix socket output

          [env: SURREAL_LOG_SOCKET_LEVEL=]
          [possible values: none, full, error, warn, info, debug, trace]

      --log-socket-format <LOG_SOCKET_FORMAT>
          The format for socket output

          [env: SURREAL_LOG_SOCKET_FORMAT=]
          [default: text]
          [possible values: text, json]

      --log-file-enabled
          Whether to enable log file output

          [env: SURREAL_LOG_FILE_ENABLED=]

      --log-file-path <LOG_FILE_PATH>
          The directory where log files will be stored

          [env: SURREAL_LOG_FILE_PATH=]
          [default: logs]

      --log-file-name <LOG_FILE_NAME>
          The name of the log file

          [env: SURREAL_LOG_FILE_NAME=]
          [default: surrealdb.log]

      --log-file-format <LOG_FILE_FORMAT>
          The format for log file output

          [env: SURREAL_LOG_FILE_FORMAT=]
          [default: text]
          [possible values: text, json]

      --log-file-rotation <LOG_FILE_ROTATION>
          The log file rotation interval

          [env: SURREAL_LOG_FILE_ROTATION=]
          [default: daily]
          [possible values: daily, hourly, never]
```

**Build your own command**

Select the storage, authentication, network and capability options you want, and the builder writes the matching `surreal db start` command. Every other option is under **Advanced options**. Notes under the command describe what the chosen combination means, such as which data is lost when the server stops, or who can reach the server without signing in. The **surreal db start details** tab describes each option in full.

<DemoEmbed title="Start command builder" height={760} pages={[{ "label": "Start command builder", "src": "/docs/start-builder/builder.html" }]} />

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/upgrade

# surreal upgrade db

How to upgrade the database component with the launcher command surreal upgrade db, to the latest release, a specific version, or an alpha, beta or nightly build. surreal db upgrade is refused.

The launcher upgrades the database, so the command is `surreal upgrade db`, not a `surreal db` subcommand. It replaces the installed database component with another version: the latest stable release, a specific version, or a pre-release build. This page sits with the database commands because it is the way to change the version of `surreal db`.

<Synopsis>
surreal upgrade db[@<VERSION>] [OPTIONS]
</Synopsis>

`surreal db upgrade` is refused, and exits with status 2 and a message that points to the launcher command:

```text
error: The CLI manages the database's versions, so `surreal db upgrade` is not run. Use `surreal upgrade db`, or `surreal upgrade db@<VERSION>` for a particular release.
```

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--nightly",
            "description": "Upgrade the database to the latest nightly build. Equivalent to `db@nightly`."
        },
        {
            "name": "--alpha",
            "description": "Upgrade the database to the latest alpha release. Equivalent to `db@alpha`."
        },
        {
            "name": "--beta",
            "description": "Upgrade the database to the latest beta release. Equivalent to `db@beta`."
        },
        {
            "name": "--version",
            "value": "<VERSION>",
            "description": "Upgrade the database to this version. Equivalent to `db@<VERSION>`."
        },
        {
            "name": "--dry-run",
            "description": "Say what would change without changing it."
        }
    ]}
/>

The launcher's other options, such as `--json`, `--plain`, `--yes` and `--no-input`, apply as well. They are listed in the [launcher reference](/docs/reference/cli/launcher.md).

## Example usage

Upgrade the database to the newest release on the channel it already follows, a stable release along its major line or a pre-release along its own channel.

```bash
surreal upgrade db
```

Running `surreal upgrade` with no component upgrades the launcher and every component that has a default version, the database included. Use `surreal upgrade cli` to upgrade the launcher alone.

## Install a specific version

Name the version after `db@`, or use the `--version` flag.

```bash
surreal upgrade db@3.2.4

# The same, with the flag
surreal upgrade db --version 3.2.4
```

For a list of available versions and their release notes, see the [releases](/releases) page. To keep an older version installed beside a newer one, use `surreal install db@<VERSION>` and choose between them with `surreal use`, as described in [Components](/docs/explore/cli/components.md).

## Install the alpha, beta or nightly release

Use the `--alpha`, `--beta` or `--nightly` flag, or the matching `db@alpha`, `db@beta` or `db@nightly` form.

```bash
surreal upgrade db --alpha
surreal upgrade db --beta
surreal upgrade db --nightly
```

## Preview an upgrade

Use `--dry-run` to see what would change without changing it.

```bash
surreal upgrade db --dry-run
```

## Where the database is installed

The launcher keeps each installed version of the database under its home directory, `~/.surrealdb` by default or the directory in `SURREAL_CLI_HOME`, and an upgrade installs the new version there. [`surreal cloud`](/docs/reference/cli/cloud.md) uses this same installation when `surreal cloud instance sql`, `import` or `export` needs the database, and offers to install it if it is missing.

## Command help

To see the help information and usage instructions, run `surreal upgrade --help` in a terminal.

```bash
surreal upgrade --help
```

The output of the above command:

```text
Upgrade the CLI and every installed component

Usage: surreal upgrade [OPTIONS] [COMPONENT[@VERSION] | cli]...

Arguments:
  [COMPONENT[@VERSION] | cli]...  Upgrade only these, each to the newest release on the channel its
                                   default follows - a stable release along its major line, a
                                   prerelease along its own channel - or to the version named,
                                   either way. `cli` is the launcher. Without any: the launcher and
                                   every component with a default version

Options:
      --nightly            Upgrade the database to the latest nightly build: `db@nightly`
      --alpha              Upgrade the database to the latest alpha release: `db@alpha`
      --beta               Upgrade the database to the latest beta release: `db@beta`
      --version <VERSION>  Upgrade the database to this version: `db@<VERSION>`
      --dry-run            Say what would change without changing it
      --json               Print one JSON document on stdout
      --plain              Plain text: no tables, no colour, no animation
      --color <WHEN>       When to use colour [default: auto] [possible values: auto, always, never]
  -q, --quiet              Print only results and errors
  -y, --yes                Answer yes to every question
      --no-input           Never prompt; fail where a question would be asked
  -h, --help               Print help
```

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/validate

# validate

A command to confirm whether one or more SurrealQL files are valid or not.

Validate SurrealQL query files, or a query read from standard input, with the [installed](/docs/explore/cli/install.md) `surreal` CLI and without connecting to a server.

<Synopsis>
surreal db validate [OPTIONS] [PATTERNS]...
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[PATTERNS]...",
            "default": "**/*.surql",
            "description": "Glob pattern for the files to validate. Several paths or patterns can be given at once."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--stdin",
            "description": "Read the query from standard input instead of from files."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

## Example usage

To perform validation on a SurrealQL local file, in a terminal run the `surreal db validate` command with the required argument.

Using the command on its own will validate all the `.surql` files in the current directory.

```bash
surreal db validate
```

```text
user1.surql: OK
user2.surql: OK
user3.surql: OK
```

You can perform validation on a single file, regardless of extension.

```bash
surreal db validate user.surql
surreal db validate user.txt
```

You can also perform validation on multiple files using a single glob pattern:

```bash
# equivalent to "surreal db validate"
surreal db validate **/*.surql
```

Finally, you can also perform validation on multiple files using multiple paths/patterns:

```bash
surreal db validate index.surql schemas/*.surql queries/*.surql
surreal db validate *.(txt|surql)
```

If any files are invalid, the command will abort at this point and return an error.

```bash
surreal db validate
```

```text
user1.surql: OK
user2.surql: KO
Parse error: Unexpected token `an identifier`, expected Eof
 --> [1:15]
  |
1 | CREATE person SE name = "Billy";
  |               ^^
```

## Validating input from stdin

_(since v3.1.0)_

In addition to files, this command can also be used on the terminal with the `--stdin` flag to validate input from stdin.

An example of a valid query:

```bash
echo "SELECT * FROM example;" | surreal db validate --stdin
```

```text
<stdin>: OK
```

An example of an invalid query:

```bash
echo "CREATE WHERE value = ''" | surreal db validate --stdin
```

```text
<stdin>: FAIL
Parse error: Unexpected token `VALUE`, expected Eof
 --> [1:14]
  |
1 | CREATE WHERE value = ''
  |              ^^^^^
```

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db validate --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `validate` command.

```bash
surreal db validate --help
```

The output of the above command:

```text
Validate SurrealQL query files

Usage: surreal validate [OPTIONS] [PATTERNS]...

Arguments:
  [PATTERNS]...  Glob pattern for the files to validate [default: **/*.surql]

Options:
  --stdin  Read query from standard input
  -h, --help  Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values:
                                               none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible
                                               values: daily, hourly, never]
```

---

Source: https://surrealdb.com/docs/reference/cli/db/commands/version

# version

surreal db version prints the database component version with its platform and architecture, or the version of a remote server with --endpoint. The launcher has its own surreal version and surreal --version.

Output the version of the database component, or the version of a remote server. The launcher has its own version commands, described [below](#launcher-versions).

<Synopsis>
surreal db version [OPTIONS]
</Synopsis>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--endpoint",
            "short": "-e",
            "value": "<ENDPOINT>",
            "description": "Remote database server URL to connect to. When given, only the server's version number is printed."
        },
        {
            "name": "--log",
            "short": "-l",
            "value": "<LOG>",
            "default": "info",
            "env": "SURREAL_LOG",
            "description": "The logging level for the command-line tool. Possible values: `none`, `full`, `error`, `warn`, `info`, `debug`, `trace`."
        }
    ]}
/>

## Example usage

To display the version of the database component, along with the platform and architecture, run `surreal db version` without any further arguments.

```bash
surreal db version
```

Sample output:

```text
3.3.4 for macos on aarch64
```

If an endpoint is specified, only the version number of the server is displayed.

```bash
surreal db version --endpoint http://localhost:8000
```

Output:

```text
3.3.4
```

## Launcher versions {#launcher-versions}

`surreal version` and `surreal --version` belong to the launcher and do not forward to the database. `surreal version` prints a table with each component, the version that runs, where that version came from, and its path. `surreal --version` prints the launcher version and the version of each component.

```bash
surreal version
```

```text
COMPONENT  VERSION  SOURCE  INSTALLED  PATH
cli        0.1.0    cli                /usr/local/bin/surreal
db         3.3.4    pinned  3.3.4      ~/.surrealdb/components/db/3.3.4/surreal-db
cloud      0.1.0    pinned  0.1.0      ~/.surrealdb/components/cloud/0.1.0/surreal-cloud
```

```bash
surreal --version
```

```text
Surreal CLI 0.1.0
  db      3.3.4
  cloud   0.1.0
```

A component that is not installed has a blank row in the table and reads `not installed` in `surreal --version`. To query a server, or to print the database version alone, use `surreal db version`. See the [launcher reference](/docs/reference/cli/launcher.md) for the other version commands, including `surreal install`, `surreal use` and `surreal upgrade`.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal db version --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `version` command.

```bash
surreal db version --help
```

The output of the above command:

```text
Output the command-line tool and remote server version information

Usage: surreal version [OPTIONS]

Options:
  -e, --endpoint <ENDPOINT>  Remote database server url to connect to
  -h, --help                 Print help

Logging:
  -l, --log <LOG>                              The logging level for the command-line tool [env: SURREAL_LOG=] [default: info] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-format <LOG_FORMAT>                The format for terminal log output [env: SURREAL_LOG_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-socket <LOG_SOCKET>                Send logs to the specified host:port [env: SURREAL_LOG_SOCKET=]
      --log-file-level <LOG_FILE_LEVEL>        Override the logging level for file output [env: SURREAL_LOG_FILE_LEVEL=] [possible values:
                                               none, full, error, warn, info, debug, trace]
      --log-otel-level <LOG_OTEL_LEVEL>        Override the logging level for OpenTelemetry output [env: SURREAL_LOG_OTEL_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-level <LOG_SOCKET_LEVEL>    Override the logging level for unix socket output [env: SURREAL_LOG_SOCKET_LEVEL=] [possible
                                               values: none, full, error, warn, info, debug, trace]
      --log-socket-format <LOG_SOCKET_FORMAT>  The format for socket output [env: SURREAL_LOG_SOCKET_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-enabled                       Whether to enable log file output [env: SURREAL_LOG_FILE_ENABLED=]
      --log-file-path <LOG_FILE_PATH>          The directory where log files will be stored [env: SURREAL_LOG_FILE_PATH=] [default: logs]
      --log-file-name <LOG_FILE_NAME>          The name of the log file [env: SURREAL_LOG_FILE_NAME=] [default: surrealdb.log]
      --log-file-format <LOG_FILE_FORMAT>      The format for log file output [env: SURREAL_LOG_FILE_FORMAT=] [default: text] [possible
                                               values: text, json]
      --log-file-rotation <LOG_FILE_ROTATION>  The log file rotation interval [env: SURREAL_LOG_FILE_ROTATION=] [default: daily] [possible
                                               values: daily, hourly, never]
```

---

Source: https://surrealdb.com/docs/reference/cli/db/environment-variables

# Environment variables

Every SURREAL_ variable, with its default and allowed values.

Environment variables can be used to tailor the behaviour of a running SurrealDB instance.

Environment variables are divided into four types:

* **SurrealDB environment variables**: environment variables that pertain to the overall running of a SurrealDB server. Example: `SURREAL_DEFAULT_DATABASE`. Includes an [operator and internal config](#operator-and-internal-config) subsection for advanced settings.
* **Command environment variables**: environment variables that can be used in lieu of a command flag. Example: `SURREAL_CAPS_ALLOW_ALL=true surreal db start`, equivalent to `surreal db start --allow-all`.
* **Storage backend environment variables**: environment variables that pertain to a certain storage backend. Example: `SURREAL_SURREALKV_ENABLE_VLOG`.
* **SurrealDB Cloud environment variables**: environment variables that are set via the [Configure instance](/docs/manage/instances/configure.md) sidebar for a SurrealDB Cloud instance.

> [!IMPORTANT]
> Every variable on this page belongs to the database and starts with `SURREAL_`. They are read by `surreal db`, and by the server in a container that runs `surreal start` directly. The launcher has its own variables, which start with `SURREAL_CLI_` and are documented in the [launcher reference](/docs/reference/cli/launcher.md). The `surreal cloud` component reads `SURREAL_CLOUD_*` variables, described in the [Cloud reference](/docs/reference/cli/cloud.md). The prefixes are kept apart deliberately: a database credential such as `SURREAL_TOKEN` is not a Cloud credential, and must not be sent to the SurrealDB Cloud API.

Many environment variables have a maximum value equivalent to the greatest possible `usize`, which is an unsigned integer with a number of bytes depending on the target that the database runs on. For most systems this will be 64 bits, leading to a maximum size of 18_446_744_073_709_551_615 (2<sup>64</sup>), while for 32 bits the maximum will be 4_294_967_296 (2<sup>32</sup>).

## Byte size suffixes

_(since v3.0.0)_

Environment variables that set a size in bytes, such as `SURREAL_HTTP_MAX_SQL_BODY_SIZE` and `SURREAL_WEBSOCKET_MAX_MESSAGE_SIZE`, accept either a raw byte count or a number with a suffix. Suffixes are case-insensitive, and all of them are powers of 1024, so `4GB` and `4GiB` both mean 4,294,967,296 bytes.

| Suffix | Multiplier |
| ------ | ---------- |
| none, `b` | 1 |
| `k`, `kb`, `kib` | 1024 |
| `m`, `mb`, `mib` | 1024<sup>2</sup> |
| `g`, `gb`, `gib` | 1024<sup>3</sup> |

> [!WARNING]
> A value that cannot be parsed is discarded and the default is used instead - the server does not fail to start. On 2.x these variables take a raw byte count only, so a suffixed value such as `16MiB` is silently ignored there and the default remains in effect.

## SurrealDB environment variables

These environment variables can be used to configure a SurrealDB server to configure areas such as the HTTP server and client, limits, telemetry, and so on.

### Batch config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NORMAL_FETCH_SIZE</code></td>
      <td scope="row" data-label="Default">500</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of keys that should be scanned at once in general queries. Read only before 3.1.0; later versions use a fixed batch of 500.</td>
    </tr>
        <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_EXPORT_BATCH_SIZE</code></td>
      <td scope="row" data-label="Default">1000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of keys that should be scanned at once for export queries.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_COUNT_BATCH_SIZE</code>_(since v2.2.0)_</td>
      <td scope="row" data-label="Default">50,000 (10,000 on 2.x)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of keys that should be scanned at once for count queries. Read only before 3.1.0; later versions use a fixed batch of 50,000.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_INDEXING_BATCH_SIZE</code></td>
      <td scope="row" data-label="Default">250</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of keys to scan at once per concurrent indexing batch. Read only before 3.1.0; later versions use a fixed batch of 250.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TABLE_DOC_IDS_BATCH_SIZE</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">1000</td>
      <td scope="row" data-label="Allowed values">A u32</td>
      <td scope="row" data-label="Notes">The number of document ids allocated at once from a table's shared document id sequence, which full-text, HNSW and DiskANN indexes use. Larger batches reduce coordination on the distributed sequence, at the cost of larger gaps in the ids when a node is lost before it uses up its current batch. Replaces <code>SURREAL_FTS_DOC_IDS_BATCH_SIZE</code>, which 3.3.0 no longer reads.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GRAPH_FOLD_BATCH</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">1024</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How many graph adjacency keys one background fold pass packs into blocks per transaction. This bounds the transaction size on a vertex with many edges, which is then folded over several passes. See <code>SURREAL_GRAPH_FOLD_INTERVAL</code>.</td>
    </tr>
  </tbody>
</table>

### Cache config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TRANSACTION_CACHE_SIZE</code></td>
      <td scope="row" data-label="Default">512 (10,000 before 3.1.0)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Specifies the number of items which can be cached within a single transaction.</td>
    </tr>
<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DATASTORE_CACHE_SIZE</code></td>
      <td scope="row" data-label="Default">1,000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The number of definitions which can be cached across transactions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HNSW_CACHE_SIZE</code></td>
      <td scope="row" data-label="Default">268,435,456 (256 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum total size, in bytes, of the HNSW vector cache, shared across all HNSW indexes in the process. This bounds the cached element <em>vectors</em> used for distance computation - it is not a cap on total HNSW memory: the adjacency graph is loaded into resident memory on first use and stays there, outside this budget. Contrast <code>SURREAL_DISKANN_CACHE_SIZE</code>, which does page graph structure.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DISKANN_CACHE_SIZE</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">268,435,456 (256 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum total size, in bytes, of the DISKANN index cache, shared across all DISKANN indexes in the process. DISKANN graph data lives in the key-value store and is paged through this bounded cache. The DISKANN counterpart of <code>SURREAL_HNSW_CACHE_SIZE</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GRAPH_RESOLVE_CACHE_SIZE</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">67,108,864 (64 MiB)</td>
      <td scope="row" data-label="Allowed values">A u64</td>
      <td scope="row" data-label="Notes">The maximum total size, in bytes, of the cache that maps document ids back to record keys when a traversal reads numeric-encoded graph adjacency entries. Only relevant once <code>SURREAL_GRAPH_NUMERIC_IDS</code> has written numeric blocks.</td>
    </tr>
  </tbody>
</table>

### File config

Server-side filesystem access for features that read paths from disk (notably the `mapper()` filter and the `segment()` tokenizer on [`DEFINE ANALYZER`](/docs/reference/query-language/statements/define/analyzer.md)). This is separate from `SURREAL_BUCKET_FOLDER_ALLOWLIST` (below), which gates the experimental [files](/docs/learn/schema-management/files/buckets.md) feature.

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
  <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_FILE_ALLOWLIST</code></td>
      <td scope="row" data-label="Default">none (deny all)</td>
      <td scope="row" data-label="Allowed values">Colon-separated paths on Unix; semicolon-separated on Windows</td>
      <td scope="row" data-label="Notes">Directories the server may read when an analyzer uses <code>mapper('&lt;path&gt;')</code>. An empty or unset value denies every path. Each mapper file must resolve under one of the listed directories. See <a href="/docs/reference/query-language/statements/define/analyzer.md#mapperpath">DEFINE ANALYZER - mapper</a>.</td>
    </tr>
  <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SEGMENT_DICTIONARY_PATH</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">none (use the dictionaries built into the binary)</td>
      <td scope="row" data-label="Allowed values">A path to a directory</td>
      <td scope="row" data-label="Notes">Directory holding the morphological dictionaries the <code>segment(&lt;language&gt;)</code> tokenizer reads, one subdirectory per language: <code>chinese</code>, <code>japanese</code>, <code>korean</code>. The released binaries already carry all three, so this is only needed to replace them with a custom or updated dictionary, or for a build made without them. Each language is overridden only if the directory holds a dictionary for it; the rest keep using the built-in one. See <a href="/docs/reference/query-language/statements/define/analyzer.md#segmentlanguage">DEFINE ANALYZER - segment</a>.</td>
    </tr>
  <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_BUCKET_FOLDER_ALLOWLIST</code></td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">Comma-separated paths</td>
      <td scope="row" data-label="Notes">Specifies a list of paths in which files can be accessed.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GLOBAL_BUCKET</code></td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">Specifies the name of a global bucket for file data.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GLOBAL_BUCKET_ENFORCED</code></td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to enforce a global bucket for file data.</td>
    </tr>
  </tbody>
</table>

### HTTP client config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>

  <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_HTTP_REDIRECTS</code></td>
      <td scope="row" data-label="Default">10</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of HTTP redirects allowed within http functions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_HTTP_IDLE_CONNECTIONS_PER_HOST</code></td>
      <td scope="row" data-label="Default">128</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of idle HTTP connections to maintain per host.</td>
    </tr>
        <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_HTTP_IDLE_CONNECTIONS</code></td>
      <td scope="row" data-label="Default">1000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of idle HTTP connections to maintain. Has no effect: earlier versions read the value without applying it to the HTTP client, and 3.3.0 no longer reads it. Use <code>SURREAL_MAX_HTTP_IDLE_CONNECTIONS_PER_HOST</code> instead.</td>
    </tr>
<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_IDLE_TIMEOUT_SECS</code></td>
      <td scope="row" data-label="Default">90</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The timeout for idle HTTP connections before closing.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_CONNECT_TIMEOUT_SECS</code></td>
      <td scope="row" data-label="Default">30</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The timeout for connecting to HTTP endpoints.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALDB_USER_AGENT</code></td>
      <td scope="row" data-label="Default">SurrealDB</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The USER-AGENT string used by HTTP requests. Before 3.1.0 this variable was named <code>SURREAL_USER_AGENT</code>, which later versions ignore.</td>
    </tr>
  </tbody>
</table>

### HTTP server config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>

<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NET_MAX_CONCURRENT_REQUESTS</code></td>
      <td scope="row" data-label="Default">1,048,576</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How many concurrent network requests can be handled at once</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_ML_BODY_SIZE</code></td>
      <td scope="row" data-label="Default">4,294,967,296 (4 GiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum HTTP body size of the HTTP /ml endpoints. Counted cumulatively across the whole request, not per chunk.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_MCP_BODY_SIZE</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">4,194,304 (4 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum HTTP body size of the HTTP <code>/mcp</code> endpoint. See [Embedded MCP](/docs/build/ai-agents/mcp/embedded.md).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_SQL_BODY_SIZE</code></td>
      <td scope="row" data-label="Default">1,048,576 (1 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum HTTP body size of the HTTP /sql endpoint</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_GQL_BODY_SIZE</code>_(since v3.2.0)_</td>
      <td scope="row" data-label="Default">1,048,576 (1 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum HTTP body size of the HTTP <code>/gql</code> endpoint. See <a href="/docs/learn/querying/gql/via-http.md">GQL via HTTP</a>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_API_BODY_SIZE</code></td>
      <td scope="row" data-label="Default">4,194,304 (4 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum HTTP body size of the HTTP /api endpoint.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_RPC_BODY_SIZE</code></td>
      <td scope="row" data-label="Default">4,194,304 (4 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum HTTP body size of the HTTP /rpc endpoint.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GRPC_MAX_MESSAGE_SIZE</code></td>
      <td scope="row" data-label="Default">The value of <code>SURREAL_HTTP_MAX_RPC_BODY_SIZE</code>, so 4,194,304 (4 MiB) unless that is raised</td>
      <td scope="row" data-label="Allowed values">A usize, clamped to a ceiling of 4,294,967,295 (4 GiB less one byte), the largest size a gRPC length prefix can express. A value below the floor needed to carry one streaming file chunk is raised to it.</td>
      <td scope="row" data-label="Notes">From 3.3.0. The largest single gRPC message the server will accept or emit, also reported to clients as <code>Limits.max_message_bytes</code>. This is the gRPC counterpart of <code>SURREAL_WEBSOCKET_MAX_MESSAGE_SIZE</code>: raise it for a transaction that writes a large batch in one request.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GRPC_MAX_ATTACHED_SESSIONS</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">16384</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of explicitly attached sessions the gRPC transport keeps at once. The count is for the whole process rather than per connection, because a gRPC channel reconnects transparently and one session's requests can arrive on different connections. Per-request sessions do not count towards it.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GRPC_NOTIFICATION_BUFFER</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">1024</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How many live query notifications can be buffered for a gRPC subscription whose client is not keeping up. When the buffer is full, the server ends that subscription with a <code>RESOURCE_EXHAUSTED</code> status instead of waiting.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_KEY_BODY_SIZE</code></td>
      <td scope="row" data-label="Default">16,384 (16 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum HTTP body size of the HTTP /key endpoints</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_SIGNUP_BODY_SIZE</code></td>
      <td scope="row" data-label="Default">1024 (1 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum HTTP body size of the HTTP /signup endpoint.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_IMPORT_BODY_SIZE</code></td>
      <td scope="row" data-label="Default">4,294,967,296 (4 GiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum HTTP body size of the HTTP <code>/import</code> endpoint. Counted cumulatively across the whole request, not per chunk. Also enforced on the gRPC import stream. See [Request size limits](/docs/reference/rest-api/http-protocol.md#request-size-limits).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_MAX_SIGNIN_BODY_SIZE</code></td>
      <td scope="row" data-label="Default">1024 (1 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum HTTP body size of the HTTP /signin endpoints</td>
    </tr>
  </tbody>
</table>

> [!NOTE]
> Before SurrealDB 3.3.0, `SURREAL_HTTP_MAX_SIGNUP_BODY_SIZE` and `SURREAL_HTTP_MAX_SIGNIN_BODY_SIZE` were applied to each other's endpoint. Both default to 1 KiB, so no default deployment was affected, but raising one of them had no effect on the endpoint it names and the other endpoint kept returning `413 Payload Too Large`. If you worked around this by tuning the opposite variable, move the value back to the one that matches the endpoint.

### MCP config

_(since v3.1.0)_

Used by the built-in [Model Context Protocol](/docs/build/ai-agents/mcp/embedded.md) server (`/mcp` on `surreal db start`, `surreal db mcp` on stdio). Stdio namespace/database selection uses `SURREAL_MCP_NS` and `SURREAL_MCP_DB` on the [`mcp`](/docs/reference/cli/db/commands/mcp.md) subcommand.

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MCP_QUERY_TIMEOUT_SECS</code></td>
      <td scope="row" data-label="Default">60</td>
      <td scope="row" data-label="Allowed values">Seconds (integer); <code>0</code> disables</td>
      <td scope="row" data-label="Notes">Outer timeout on each MCP tool execution.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MCP_MAX_RESULT_BYTES</code></td>
      <td scope="row" data-label="Default">262,144 (256 KiB)</td>
      <td scope="row" data-label="Allowed values">Bytes (integer); <code>0</code> disables</td>
      <td scope="row" data-label="Notes">Maximum serialised tool / resource response size; larger results are truncated with a marker.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MCP_RUN_MAX_ARGS</code></td>
      <td scope="row" data-label="Default">64</td>
      <td scope="row" data-label="Allowed values">A positive integer</td>
      <td scope="row" data-label="Notes">Maximum arguments for a single <code>run</code> tool call.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MCP_PARAMS_MAX_KEYS</code></td>
      <td scope="row" data-label="Default">256</td>
      <td scope="row" data-label="Allowed values">A positive integer</td>
      <td scope="row" data-label="Notes">Maximum top-level keys in MCP parameter / data objects.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MCP_PARAMS_MAX_QL_BYTES</code></td>
      <td scope="row" data-label="Default">4,096 (4 KiB)</td>
      <td scope="row" data-label="Allowed values">A positive integer; values ≤ 0 fall back to the default</td>
      <td scope="row" data-label="Notes">Maximum byte length of a single <code>$ql</code> SurrealQL pass-through string inside a <code>*_data</code> payload.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MCP_SCHEMA_RESOURCE_MAX_TABLES</code></td>
      <td scope="row" data-label="Default">200</td>
      <td scope="row" data-label="Allowed values">A positive integer; values ≤ 0 fall back to the default</td>
      <td scope="row" data-label="Notes">Maximum tables the database-level schema resource enriches with per-table fields / indexes / events. Tables beyond the cap keep a bare <code>DEFINE TABLE</code> and the body includes a <code>tables_truncated_at</code> marker.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MCP_ALLOWED_HOSTS</code> _(since v3.2.1)_</td>
      <td scope="row" data-label="Default">loopback only (<code>localhost</code>, <code>127.0.0.1</code>, <code>::1</code>)</td>
      <td scope="row" data-label="Allowed values">Comma-separated exact hostnames</td>
      <td scope="row" data-label="Notes">Hostnames accepted in the HTTP <code>Host</code> header for <code>/mcp</code> (DNS-rebinding guard). A non-empty list <strong>replaces</strong> the loopback default, so include <code>localhost</code> yourself if you still need it. Entries without a port match any port. Ignored when <code>SURREAL_MCP_ALLOW_ALL_HOSTS</code> is set.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MCP_ALLOW_ALL_HOSTS</code> _(since v3.2.1)_</td>
      <td scope="row" data-label="Default"><code>false</code></td>
      <td scope="row" data-label="Allowed values"><code>true</code> / <code>1</code> to enable</td>
      <td scope="row" data-label="Notes">Disables the <code>Host</code>-header allowlist (accept any <code>Host</code>). Escape hatch for a trusted proxy or load balancer. Takes precedence over <code>SURREAL_MCP_ALLOWED_HOSTS</code>.</td>
    </tr>
  </tbody>
</table>

### GQL config

_(since v3.2.0)_

Resource limits for [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md) `MATCH` execution. Errors name the limit when it is exceeded. From **3.3.0**, GQL is enabled by default; on **3.2.x**, enable it with [`--allow-experimental gql`](/docs/reference/cli/db/commands/start.md#experimental-capabilities).

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GQL_MAX_PATH_ROWS</code></td>
      <td scope="row" data-label="Default">1,000,000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Per source row, bounds live and emitted rows during variable-length path expansion in a GQL <code>MATCH</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GQL_MAX_JOIN_BUILD_ROWS</code></td>
      <td scope="row" data-label="Default">1,000,000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Bounds the in-memory build side of hash joins between GQL match patterns (and distinct-operator working sets).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GQL_MAX_OUTPUT_ROWS</code></td>
      <td scope="row" data-label="Default">1,000,000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Bounds cumulative rows emitted by GQL hash-join operators (including cross joins).</td>
    </tr>
  </tbody>
</table>

### Limits config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>

    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GRAPH_INLINE_PROPS_CAP</code> _(since v3.3.0)_</td>
      <td scope="row" data-label="Default">64</td>
      <td scope="row" data-label="Allowed values">A number of bytes, without a unit suffix</td>
      <td scope="row" data-label="Notes">Largest encoded payload an <a href="/docs/reference/query-language/statements/define/field.md#using-inline-to-filter-a-traversal-during-the-scan"><code>INLINE</code></a> field set may occupy inside a graph adjacency entry. A larger payload spills, leaving the values on the edge record only.</td>
    </tr>

    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_BITMAP_BRANCH_BUDGET</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">250,000</td>
      <td scope="row" data-label="Allowed values">A usize; <code>0</code> disables the budget</td>
      <td scope="row" data-label="Notes">How many index entries one range branch of a bitmap plan, which combines several indexes into a bitmap of candidate records, may read before that branch is abandoned. The abandoned branch's condition is then checked as an ordinary <code>WHERE</code> filter, so an unselective range cannot make the bitmap plan slower than scanning. The same budget bounds the allow-list built for a pre-filtered vector search (see <a href="#vector-search-config">Vector search config</a>).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_EXTERNAL_SORTING_BUFFER_LIMIT</code></td>
      <td scope="row" data-label="Default">50000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The number of result records which will trigger on-disk sorting.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_STRING_SIMILARITY_LIMIT</code></td>
      <td scope="row" data-label="Default">16384</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum input string length for similarity/distance functions</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_FAN_OUT_ROW_THRESHOLD</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">4</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">A batch with fewer items than this is evaluated one item at a time. A larger batch has its items evaluated concurrently, but only when the evaluation is read-only. Set it above the batch size to turn concurrent evaluation off.</td>
    </tr>
     <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GENERATION_ALLOCATION_LIMIT</code></td>
      <td scope="row" data-label="Default">1,048,576</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Limits memory allocation for certain built-in functions (e.g., string::replace) to avoid uncontrolled memory usage. Default is 1,048,576 bytes (computed as 2<sup>20</sup>).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_IDIOM_RECURSION_LIMIT</code></td>
      <td scope="row" data-label="Default">256</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum recursive idiom path depth allowed. Read only before 3.1.0; later versions use a fixed limit of 256.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_COMPUTATION_DEPTH</code></td>
      <td scope="row" data-label="Default">120</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Specifies how deep recursive computation will go before erroring.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_CONCURRENT_TASKS</code></td>
      <td scope="row" data-label="Default">64</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Specifies how many concurrent jobs can be buffered in the worker channel.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_EXPRESSION_PARSING_DEPTH</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">128</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How deep the parser will build an expression operator tree. This bounds long operator chains such as <code>1 + 1 + 1 + …</code> and prefix/postfix chains, which consume neither of the other two parsing budgets.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_OBJECT_PARSING_DEPTH</code></td>
      <td scope="row" data-label="Default">100</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How deep the parser will parse nested objects and arrays. Type annotations such as <code>array&lt;option&lt;array&lt;int&gt;&gt;&gt;</code> count against it as well. For data arriving over an SDK or RPC this is the governing limit. For a query written as text, see <a href="#nesting-depth-in-queries-versus-data">Nesting depth in queries versus data</a>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_ORDER_LIMIT_PRIORITY_QUEUE_SIZE</code>_(since v2.2.0)_</td>
      <td scope="row" data-label="Default">1000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum size of the priority queue triggering usage of the priority queue for the result collector.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_QUERY_PARSING_DEPTH</code></td>
      <td scope="row" data-label="Default">20</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How deep the parser will parse recursive queries (queries within queries). Object and array literals written into a query count against this budget as well as against <code>SURREAL_MAX_OBJECT_PARSING_DEPTH</code>, and this one is the smaller of the two - see <a href="#nesting-depth-in-queries-versus-data">Nesting depth in queries versus data</a>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_OPERATOR_BUFFER_SIZE</code></td>
      <td scope="row" data-label="Default">2</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The number of batches each operator buffers ahead of downstream demand. Set to 0 to disable operator-level pipeline buffering.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_REGEX_SIZE_LIMIT</code></td>
      <td scope="row" data-label="Default">10,485,760 (10 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Limits the maximum allowed size (in bytes) for regular expressions. This prevents excessive memory consumption when building complex or very large regex patterns.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SCAN_BATCH_BYTES</code>_(since v3.3.1)_</td>
      <td scope="row" data-label="Default">2,097,152 (2 MiB)</td>
      <td scope="row" data-label="Allowed values">A byte count, or a size such as <code>512KiB</code></td>
      <td scope="row" data-label="Notes">The approximate decoded size of each batch of records that a query fetches from an index result, such as the matches of a full-text search. A batch also holds at most 1,000 records. Every operator after the scan holds whole batches, so the memory a query uses grows with this value. Lower it to limit memory on a small node, or raise it for fewer, larger batches. <code>0</code> and values that cannot be parsed are ignored.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TRANSACTION_MAX_WRITE_KEYS</code>_(since v3.2.4)_</td>
      <td scope="row" data-label="Default">0 (disabled)</td>
      <td scope="row" data-label="Allowed values">A <code>u64</code>; <code>0</code> disables</td>
      <td scope="row" data-label="Notes">Maximum number of key writes a single statement transaction may buffer before it is aborted and rolled back. Cascaded deletes, full-text index maintenance, graph-edge cleanup, and commit-time changefeed / live-query events all count toward the limit; each range delete counts as one write. Tripping the guard poisons the transaction so an explicit client <code>COMMIT</code> cannot persist a partial statement. Internal maintenance transactions (index builds, compaction, GC) are not bounded. On TiKV, range deletes may expand further inside the storage layer - size distributed clusters with both this limit and <code>SURREAL_TIKV_DELR_MAX_KEYS</code> in mind. Scale / multi-node deployments often use a limit in the 50,000-100,000 range; leave at <code>0</code> for typical single-node use.</td>
    </tr>
  </tbody>
</table>

#### Nesting depth in queries versus data

Four separate budgets bound recursion, and which one a deeply nested value hits depends on how it reaches the server.

A value written into query text is read by the SurrealQL parser, and each level of an object or array costs one level of `SURREAL_MAX_QUERY_PARSING_DEPTH` as well as one of `SURREAL_MAX_OBJECT_PARSING_DEPTH`. Since the query budget defaults to `20` against the object budget's `100`, the query budget is what a nested literal runs out of first.

A value arriving as **data** (the body of an SDK call, an RPC message, or a WebSocket frame) is never parsed as SurrealQL, so it spends only the object budget and nests roughly five times deeper for the same default configuration.

The practical effect is that one document can be accepted through an SDK and rejected as a literal in `surreal db sql`, which reads as an inconsistency but is two budgets doing their own jobs. A structure alternating object and array, as in `{ inner: [{ inner: [ … ] }] }`, spends two levels per step:

| Path | Governing default | Levels of `{ inner: [ … ] }` |
| --- | --- | --- |
| Query text (`surreal db sql`, `/sql`) | `SURREAL_MAX_QUERY_PARSING_DEPTH` = 20 | 9 |
| SDK, RPC, WebSocket | `SURREAL_MAX_OBJECT_PARSING_DEPTH` = 100 | 49 |

Exceeding either is a parse error, not a crash, giving the error `Exceeded query recursion depth limit` for the first, `Exceeded object recursion depth limit` for the second. To send deep literals as text, raise `SURREAL_MAX_QUERY_PARSING_DEPTH` - raising the object budget alone changes nothing on that path.

> [!NOTE]
> Two further depth limits are fixed and cannot be configured: recursive idiom paths stop at 256, and the binary protocol's decoder rejects frames nested beyond 512. Neither is reachable before the parsing budgets above under a default configuration.

### Runtime config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KVS_THREADPOOL_SIZE</code> _(since v3.1.1)_</td>
      <td scope="row" data-label="Default">Number of CPU cores (minimum 16)</td>
      <td scope="row" data-label="Allowed values">A usize, at least 4</td>
      <td scope="row" data-label="Notes">Number of worker threads the in-memory, RocksDB and SurrealKV engines use for storage work. A value below 4 or a value that is not a number logs a warning and the default is used.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_RUNTIME_MAX_BLOCKING_THREADS</code></td>
      <td scope="row" data-label="Default">512</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Number of threads which can be started for blocking operations.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_RUNTIME_STACK_SIZE</code></td>
      <td scope="row" data-label="Default">10,485,760 (10 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Runtime thread memory stack size. Default stack size is doubled if compiled from source in Debug mode.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_RUNTIME_WORKER_THREADS</code></td>
      <td scope="row" data-label="Default">Number of CPU cores (minimum 4)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Number of runtime worker threads used to start. See <a href="/docs/learn/querying/performance/performance-best-practices.md#cpu-cores-and-concurrent-queries">CPU cores and concurrent queries</a>.</td>
    </tr>
  </tbody>
</table>

### Scripting config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SCRIPTING_MAX_STACK_SIZE</code></td>
      <td scope="row" data-label="Default">262_144 (256 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum stack size of the JavaScript function runtime.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SCRIPTING_MAX_MEMORY_LIMIT</code></td>
      <td scope="row" data-label="Default">2,097,152 (2 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum memory limit of the JavaScript function runtime.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SCRIPTING_MAX_TIME_LIMIT</code></td>
      <td scope="row" data-label="Default">5000 (5000 milliseconds or 5 seconds)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum allowed time in milliseconds that a JavaScript function is allowed to run for.</td>
    </tr>
  </tbody>
</table>

### Security config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_INSECURE_FORWARD_ACCESS_ERRORS</code></td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Forward all signup/signin/authenticate query errors to a client performing authentication. Do not use in production.</td>
    </tr>
  </tbody>
</table>

### Surrealism config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LAZY_SURREALISM</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to lazy-load Surrealism modules instead of eagerly compiling them at server startup.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALISM_CACHE_SIZE</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">100</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The number of surrealism modules which can be cached across transactions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALISM_MAX_POOL_SIZE</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">8</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Per-module controller pool size ceiling for Surrealism WASM modules. Has no effect: the server does not read this variable, so the ceiling stays at 8.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALISM_MAX_MEMORY</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">None (unlimited)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Per-module WASM linear memory ceiling in bytes.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALISM_MAX_EXECUTION_TIME</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">None (unlimited)</td>
      <td scope="row" data-label="Allowed values">A u64 (milliseconds)</td>
      <td scope="row" data-label="Notes">Per-invocation execution time ceiling for Surrealism WASM modules.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALISM_MAX_KV_ENTRIES</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">None (unlimited)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Per-module KV store entry count ceiling.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALISM_MAX_KV_VALUE_BYTES</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">None (unlimited)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Per-module KV store maximum value size in bytes.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALISM_LOG_LEVEL</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">debug</td>
      <td scope="row" data-label="Allowed values">none, full, error, warn, info, debug, trace</td>
      <td scope="row" data-label="Notes">Controls the tracing level at which Surrealism module stdout is emitted.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALISM_SILO_ENDPOINT</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default"><code>https://silo.surrealdb.com</code></td>
      <td scope="row" data-label="Allowed values">A URL</td>
      <td scope="row" data-label="Notes">The base URL that a <code>silo::</code> module executable in <a href="/docs/reference/query-language/statements/define/module.md">DEFINE MODULE</a> is fetched from, as <code>{endpoint}/{organisation}/{package}/{major}.{minor}.{patch}.surli</code>. A trailing slash is removed. Set it to a mirror to serve packages from somewhere else.</td>
    </tr>
  </tbody>
</table>

### Telemetry config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TELEMETRY_DISABLE_METRICS</code></td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to disable sending metrics to the GRPC OTEL collector.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TELEMETRY_DISABLE_TRACING</code></td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to disable sending traces to the GRPC OTEL collector.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TELEMETRY_NAMESPACE</code></td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">If set then use this as value for the namespace label when sending telemetry</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TELEMETRY_PROVIDER</code></td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">otlp</td>
      <td scope="row" data-label="Notes">If set to "otlp" then telemetry is sent to the GRPC OTEL collector.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TOKIO_CONSOLE_ENABLED</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to enable [tokio console](https://github.com/tokio-rs/console).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TOKIO_CONSOLE_RETENTION</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">60</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How long, in seconds, to retain data for completed events.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TOKIO_CONSOLE_SOCKET_ADDR</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">String to a socket address</td>
      <td scope="row" data-label="Notes">The socket address that Tokio Console will bind to.</td>
    </tr>
  </tbody>
</table>

### Vector search config

_(since v3.3.0)_

These variables tune pre-filtered vector search. When a KNN query on an HNSW or DiskANN index has `WHERE` conditions that other indexes can answer, SurrealDB first evaluates those conditions into an allow-list of matching records and restricts the vector search to it. See [How the filter is applied](/docs/learn/data-models/vector-search/similarity-search.md#how-the-filter-is-applied).

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KNN_PREFILTER_ENABLED</code></td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether KNN queries build an allow-list from their <code>WHERE</code> conditions. When <code>false</code>, the conditions are checked for each candidate during the index traversal instead, as before 3.3.0.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KNN_PREFILTER_EXACT_THRESHOLD</code></td>
      <td scope="row" data-label="Default">2000</td>
      <td scope="row" data-label="Allowed values">A u64</td>
      <td scope="row" data-label="Notes">The allow-list size at or below which the search does not use the index graph at all, and instead scores every allowed record exactly. At this size an exact search is cheaper than a traversal and always finds the true nearest neighbours.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KNN_PREFILTER_EF_BOOST</code></td>
      <td scope="row" data-label="Default">4</td>
      <td scope="row" data-label="Allowed values">A u32</td>
      <td scope="row" data-label="Notes">The multiplier applied to the search width (<code>ef</code>) of a traversal restricted to an allow-list. A selective allow-list rejects most candidates, and a wider search recovers the recall that loses. The result is capped by <code>SURREAL_KNN_PREFILTER_EF_MAX</code> and is never lower than the <code>ef</code> the query asks for.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KNN_PREFILTER_EF_BOOST_THRESHOLD</code></td>
      <td scope="row" data-label="Default">100,000</td>
      <td scope="row" data-label="Allowed values">A u64</td>
      <td scope="row" data-label="Notes">The allow-list size above which the traversal stops multiplying <code>ef</code>, because a large allow-list rejects few candidates. Results are still restricted to the allow-list at every size.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KNN_PREFILTER_EF_MAX</code></td>
      <td scope="row" data-label="Default">1024</td>
      <td scope="row" data-label="Allowed values">A u32</td>
      <td scope="row" data-label="Notes">The ceiling for the multiplied <code>ef</code>. It never lowers the <code>ef</code> a query asks for.</td>
    </tr>
  </tbody>
</table>

The allow-list is built within the entry budget set by [`SURREAL_BITMAP_BRANCH_BUDGET`](#limits-config). When the budget is exceeded, the query falls back to checking the conditions during the traversal. A vector index created before 3.3.0 uses the allow-list only after it is rebuilt with [`REBUILD INDEX`](/docs/reference/query-language/statements/rebuild.md).

### WebSocket config

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEBSOCKET_MAX_MESSAGE_SIZE</code></td>
      <td scope="row" data-label="Default">134,217,728 (128 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum WebSocket message size, which is also used as the maximum frame size. From 3.0.0 this replaces <code>SURREAL_WEBSOCKET_MAX_FRAME_SIZE</code>, which no longer exists and has no effect if set.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEBSOCKET_MAX_WRITE_BUFFER_SIZE</code></td>
      <td scope="row" data-label="Default">Greatest possible usize value</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum write buffer size before backpressure is applied.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEBSOCKET_READ_BUFFER_SIZE</code></td>
      <td scope="row" data-label="Default">131,072 (128 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The size of the read buffer for WebSocket connections. This controls how much data can be buffered when reading from WebSocket connections. Larger values can improve performance for high-throughput connections but consume more memory per connection.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEBSOCKET_WRITE_BUFFER_SIZE</code></td>
      <td scope="row" data-label="Default">131,072 (128 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The size of the write buffer for WebSocket connections. This controls how much data can be buffered when writing to WebSocket connections. Larger values can improve performance for high-throughput connections but consume more memory per connection.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEBSOCKET_RESPONSE_BUFFER_SIZE</code></td>
      <td scope="row" data-label="Default">0</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How many responses can be buffered when delivering to the client.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEBSOCKET_RESPONSE_CHANNEL_SIZE</code></td>
      <td scope="row" data-label="Default">100</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Number of messages that can be queued for sending via WebSocket. From 3.3.0, replies and live query notifications have separate queues on each connection, each holding this many messages, and a value below 2 is raised to 2. A connection whose notification queue is full is closed, with a close frame giving the reason, rather than dropping notifications.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEBSOCKET_RESPONSE_FLUSH_PERIOD</code></td>
      <td scope="row" data-label="Default">3</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How often (in milliseconds) any buffered responses are flushed to the WebSocket client.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEBSOCKET_MAX_CONCURRENT_STREAMS</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">32</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum number of streaming queries (the <code>query_stream</code> RPC method) running at once on a single WebSocket connection. Each open stream holds an executing query, with its read snapshot or transaction, for as long as the client keeps reading. A request over the limit is refused with an error rather than queued.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEBSOCKET_STREAM_SEND_TIMEOUT</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">30</td>
      <td scope="row" data-label="Allowed values">Seconds (integer), clamped to 1-86,400</td>
      <td scope="row" data-label="Notes">How long a streaming query may wait to hand one frame to the connection before the stream is stopped. The wait resets for every frame, so it limits a stalled client rather than the length of a stream, and it applies even when <code>SURREAL_QUERY_TIMEOUT</code> is not set.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_TRANSACTIONS_PER_CONNECTION</code>_(since v3.2.2)_</td>
      <td scope="row" data-label="Default">64</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum concurrently open client-managed transactions (<code>begin</code> RPC) on a WebSocket connection's implicit default session. Further <code>begin</code> calls return <code>Too many open transactions</code> until a slot is freed by <code>commit</code>, <code>cancel</code>, <code>reset</code>, or disconnect. Does not apply to attached sessions (see <code>SURREAL_MAX_TRANSACTIONS_PER_SESSION</code>).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MAX_TRANSACTIONS_PER_SESSION</code>_(since v3.2.2)_</td>
      <td scope="row" data-label="Default">64</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Maximum concurrently open client-managed transactions within a single attached WebSocket session. Counted per session and independent of the connection default-session limit. Detaching or resetting a session cancels its open transactions and frees their slots.</td>
    </tr>
  </tbody>
</table>

### Operator and internal config

_(since v3.2.0)_

These settings are for operators, benchmarks, and advanced debugging - not typical application configuration. They are documented so core contributors and self-hosted deployments can find configuration options that already exist in the engine. Changing them can affect performance, reproducibility, or live-query behaviour; leave defaults in place unless you have a specific reason to tune them.

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_RAND_SEED</code></td>
      <td scope="row" data-label="Default">none (non-deterministic)</td>
      <td scope="row" data-label="Allowed values">A <code>u64</code></td>
      <td scope="row" data-label="Notes">Seeds the engine-wide RNG for reproducible runs in benchmarks and tests. Do not use in production.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LIVE_QUERY_ENGINE</code></td>
      <td scope="row" data-label="Default"><code>inline</code></td>
      <td scope="row" data-label="Allowed values"><code>inline</code>, <code>router</code></td>
      <td scope="row" data-label="Notes">Selects the live-query execution engine. <code>inline</code> is the historical behaviour; <code>router</code> decouples write cost from subscriber count (experimental rollout).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LIVE_QUERY_RETENTION</code></td>
      <td scope="row" data-label="Default">1h</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">How long the router engine retains live-query event history for subscriber resume. Only applies when <code>SURREAL_LIVE_QUERY_ENGINE=router</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HNSW_BUILD_SEED</code></td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A <code>u64</code></td>
      <td scope="row" data-label="Notes">Seeds HNSW index construction for reproducible graph layouts in benchmarks. Do not use in production.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GRAPH_NUMERIC_IDS</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether background graph folds write numeric-encoded adjacency blocks, which store each entry's record id as a compact table-level document id. Existing blocks are converted on their next fold, and reads handle both encodings. Size the lookup cache with <code>SURREAL_GRAPH_RESOLVE_CACHE_SIZE</code>.</td>
    </tr>
  </tbody>
</table>

### Other environment variables

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '40%'}}>Environment variable</th>
      <th scope="col" style={{width: '20%'}}>Default</th>
      <th scope="col" style={{width: '20%'}}>Allowed values</th>
      <th scope="col" style={{width: '20%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_BUILD_METADATA</code></td>
      <td scope="row" data-label="Default">Automatically populated</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The version identifier of this build. Defaults to the CARGO_PKG_VERSION environment variable if not specified.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DATASTORE_AOL</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">never</td>
      <td scope="row" data-label="Allowed values">never|sync|async</td>
      <td scope="row" data-label="Notes">Append-only log mode. Only used by the memory engine.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DATASTORE_PERSIST</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">String to a file path</td>
      <td scope="row" data-label="Notes">Filesystem path for persistence. Only used by the memory engine.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DATASTORE_RETENTION</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">0 (unlimited)</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">Version retention period as a duration string. Used by the rocksdb and surrealkv engines. From 3.3.0 the memory engine refuses a non-zero value and the server does not start. _(since v3.3.0)_</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DATASTORE_SNAPSHOT</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">never</td>
      <td scope="row" data-label="Allowed values">never|duration</td>
      <td scope="row" data-label="Notes">Snapshot interval. Only used by the memory engine.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DATASTORE_SYNC_DATA</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">every</td>
      <td scope="row" data-label="Allowed values">never|every|duration</td>
      <td scope="row" data-label="Notes">The sync mode for the database. Used by memory, rocksdb, and surrealkv engines.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DATASTORE_VERSIONED</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true|false|0|1</td>
      <td scope="row" data-label="Notes">Whether MVCC versioning is enabled, for use with the <a href="/docs/reference/query-language/statements/select.md#the-version-clause"><code>VERSION</code> clause</a>. Used by the rocksdb and surrealkv engines. From 3.3.0 the memory engine does not support versioned queries, and refuses <code>true</code> so that the server does not start. _(since v3.3.0)_</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MEMORY_THRESHOLD</code></td>
      <td scope="row" data-label="Default">0 (disabled)</td>
      <td scope="row" data-label="Allowed values">A usize or suffixed integer</td>
      <td scope="row" data-label="Notes">The tracked memory, in bytes, above which queries are refused with <code>The query was not executed due to the memory threshold being reached</code>. The value can be specified as bytes (b, or without any suffix), kibibytes (k, kb, or kib), mebibytes (m, mb, or mib), or gibibytes (g, gb, or gib); Kubernetes-style <code>Gi</code> and <code>Mi</code> are not accepted. Unset or <code>0</code> disables the guard, and any other value is raised to at least 1 MiB. Tracked memory is the size the allocator reserves for each live heap allocation plus the RocksDB block cache, which is less than the process's resident memory, so set the threshold below the container's memory limit and above <code>SURREAL_ROCKSDB_BLOCK_CACHE_SIZE</code>: at or below the cache size, a full cache refuses every query until the server restarts. From 3.3.0 the reserved size rather than the requested size is counted, so a threshold carried over from an earlier release refuses queries at a lighter workload and may need raising. _(since v3.3.0)_ From 3.3.1 the threshold also stops a query that is already running: a query that reads records checks it after each batch, and fails with the same error once tracked memory is above it, rather than growing until the operating system stops the server. _(since v3.3.1)_</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_REGEX_CACHE_SIZE</code></td>
      <td scope="row" data-label="Default">1000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The number of computed regexes which can be cached in the engine.</td>
    </tr>
  </tbody>
</table>

## Command environment variables

Many of the arguments passed into [the CLI](/docs/reference/cli/db/commands/start.md) can be set using the above environment variables instead.

As each of these environment variables correspond to a flag or a required argument passed into a command, it is good practice to put together a command that matches the environment variables you wish to set. Once the database server conforms to your expected behaviour, you can then pull out the values passed into each flag for your environment variables.

For example, take the following command to start the database.

```bash
surreal db start --user root --pass secret --allow-net --deny-funcs "crypto::md5, http::post, http::delete"
```

If we now wanted to use environment variables instead of the `--allow-net` and `--deny-funcs` flags, we would use the `SURREAL_CAPS_ALLOW_NET` and `SURREAL_CAPS_DENY_FUNC` environment variables.

As the `--allow-net` flag was passed in without a following value, the same will be the case with the `SURREAL_CAPS_ALLOW_NET` environment variable, becoming `SURREAL_CAPS_ALLOW_NET=`. The `--deny-funcs` flag can also be used on its own to deny execution of all functions, but in this case is followed by a string to indicate which exact functions are not allowed to be executed. As such, the `SURREAL_CAPS_DENY_FUNC` environment variable must also be followed by a string, becoming `SURREAL_CAPS_DENY_FUNC="crypto::md5, http::post, http::delete"`.

The command would then look like the following:

**Bash**

```bash
SURREAL_CAPS_ALLOW_NET
SURREAL_CAPS_DENY_FUNC="crypto::md5, http::post, http::delete"
surreal db start --user root --pass secret
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_NET
$env:SURREAL_CAPS_DENY_FUNC="crypto::md5, http::post, http::delete"
surreal db start --user root --pass secret
```

A command environment variable that takes a boolean will be set to true if the flag is present, and following it with `true` will cause an error.

For example, the `SURREAL_CAPS_ALLOW_ALL` environment variable is used to set whether to allow all capabilities such as scripting and allowing network access. The flag `--allow-all` is all that is needed to set to `true`. But as an environment variable, the value `true` must be included to override its default `false` value.

**Bash**

```bash title="SURREAL_CAPS_ALLOW_ALL example"
# set to default false
surreal db start

# Same, but implicitly shown
SURREAL_CAPS_ALLOW_ALL=false surreal db start

# Set to true
SURREAL_CAPS_ALLOW_ALL=true surreal db start

# Set to true
surreal db start --allow-all

# Error: only --allow-all needed to set to true
surreal db start --allow-all true
```

**PowerShell**

```powershell title="SURREAL_CAPS_ALLOW_ALL example"
# set to default false
surreal db start

# Same, but implicitly shown
$env:SURREAL_CAPS_ALLOW_ALL = "false"
surreal db start

# Set to true
$env:SURREAL_CAPS_ALLOW_ALL = "true"
surreal db start

# Set to true
surreal db start --allow-all

# Error: only --allow-all needed to set to true
surreal db start --allow-all true
```

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '25%'}}>Environment variable</th>
      <th scope="col" style={{width: '15%'}}>Command arg</th>
      <th scope="col" style={{width: '12%'}}>For command(s)</th>
      <th scope="col" style={{width: '12%'}}>Default</th>
      <th scope="col" style={{width: '18%'}}>Allowed values</th>
      <th scope="col" style={{width: '18%'}}>Details</th>
    </tr>
  </thead>
  <tbody>
  <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ASYNC_EVENT_PROCESSING_INTERVAL</code>_(since v3.0.0)_ </td>
      <td scope="row" data-label="Command arg"><code>async-event-interval</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">5s</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The interval at which to process async events.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_AUTH_LEVEL</code></td>
      <td scope="row" data-label="Command arg"><code>auth-level</code></td>
      <td scope="row" data-label="Command">`export`, `import`, `sql`</td>
      <td scope="row" data-label="Default">root</td>
      <td scope="row" data-label="Allowed values">root, namespace, ns, database, db</td>
      <td scope="row" data-label="Notes">Authentication level to use when connecting.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_BIND</code></td>
      <td scope="row" data-label="Command arg"><code>bind</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">127.0.0.1:8000</td>
      <td scope="row" data-label="Allowed values">String to an address</td>
      <td scope="row" data-label="Notes">The hostname or IP address(es) to listen for connections on.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_POSTGRES_BIND</code></td>
      <td scope="row" data-label="Command arg"><code>postgres-bind</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">(disabled)</td>
      <td scope="row" data-label="Allowed values">String to an address</td>
      <td scope="row" data-label="Notes">The hostname or IP address to listen for <a href="/docs/reference/rest-api/postgres-protocol.md">Postgres wire protocol</a> connections on.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_ALLOW_ALL</code></td>
      <td scope="row" data-label="Command arg"><code>allow-all</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Allow all capabilities.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_ALLOW_ARBITRARY_QUERY</code></td>
      <td scope="row" data-label="Command arg"><code>allow-arbitrary-query</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">guest, record, system (comma-separated)</td>
      <td scope="row" data-label="Notes">Allows arbitrary queries to be used by user groups except when specifically denied. Alternatively, you can provide a comma-separated list of user groups to allow specifically denied user groups to prevail over any other allowed user group.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_ALLOW_EVAL_QUERY</code>_(since v3.2.0)_</td>
      <td scope="row" data-label="Command arg"><code>allow-eval-query</code></td>
      <td scope="row" data-label="Command">`start`, `sql`</td>
      <td scope="row" data-label="Default">none (denied for all subjects)</td>
      <td scope="row" data-label="Allowed values">guest, record, system (comma-separated)</td>
      <td scope="row" data-label="Notes">Allow <code>eval::surql</code> and <code>eval::gql</code> for listed subject groups. Not enabled by <code>--allow-all</code>. Still subject to <a href="/docs/learn/security/authorization/capabilities.md#arbitrary-queries">arbitrary-query</a> restrictions - <code>--deny-arbitrary-query</code> blocks <code>eval</code> for that subject even when eval is allowed here. For remote clients, set on the <code>start</code> process only. See <a href="/docs/reference/cli/db/commands/sql.md#capabilities-and-remote-connections">Capabilities and remote connections</a>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_ALLOW_EXPERIMENTAL</code></td>
      <td scope="row" data-label="Command arg"><code>allow-experimental</code></td>
      <td scope="row" data-label="Command">`start`, `sql`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">files, surrealism (comma-separated; legacy <code>gql</code> accepted but unused from 3.3.0)</td>
      <td scope="row" data-label="Notes">Allow execution of experimental features. For remote clients, set on the <code>start</code> process. On <code>surreal db sql</code>, affects embedded engines and REPL parse validation only. See <a href="/docs/reference/cli/db/commands/start.md#experimental-capabilities">experimental capabilities</a>. From 3.3.0, ISO GQL (Cypher Query Language) is on by default and does not need this variable.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_ALLOW_FUNC</code></td>
      <td scope="row" data-label="Command arg"><code>allow-funcs</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">all functions allowed</td>
      <td scope="row" data-label="Allowed values">Empty, <code>*</code>, or comma-separated function paths</td>
      <td scope="row" data-label="Notes">Allow execution of all functions except for functions that are specifically denied. Set to an empty value or <code>*</code> to allow all functions. Use a comma-separated list (for example, <code>array,string::len,http::get</code>) to allow specific function families or names. Values such as <code>true</code> are not valid. The environment variable name is singular (<code>FUNC</code>), matching the <code>--allow-funcs</code> flag.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_ALLOW_GUESTS</code></td>
      <td scope="row" data-label="Command arg"><code>allow-guests</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Allow guest users to execute queries.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_ALLOW_NET</code></td>
      <td scope="row" data-label="Command arg"><code>allow-net</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">Comma-separated list of paths</td>
      <td scope="row" data-label="Notes">Allow all or certain outbound network access.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_ALLOW_SCRIPT</code></td>
      <td scope="row" data-label="Command arg"><code>allow-scripting</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Allow execution of embedded scripting functions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_ALLOW_INSECURE_STORABLE_CLOSURES</code>_(since v2.5.0)_</td>
      <td scope="row" data-label="Command arg"><code>allow-insecure-storable-closures</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Takes a boolean. From 2.5.0, closures cannot be stored, which removes a potential attack surface; on 2.5.x and 2.6.x this capability allows them again. Removed in 3.0.0, where closures can never be stored and the flag is rejected.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_DENY_ALL</code></td>
      <td scope="row" data-label="Command arg"><code>deny-all</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Deny all capabilities.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_DENY_EVAL_QUERY</code>_(since v3.2.0)_</td>
      <td scope="row" data-label="Command arg"><code>deny-eval-query</code></td>
      <td scope="row" data-label="Command">`start`, `sql`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">guest, record, system (comma-separated)</td>
      <td scope="row" data-label="Notes">Deny <code>eval::surql</code> and <code>eval::gql</code> for listed subject groups. Deny prevails over allow at the same specificity. For remote clients, set on the <code>start</code> process.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_DENY_FUNC</code></td>
      <td scope="row" data-label="Command arg"><code>deny-funcs</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false, comma-separated list</td>
      <td scope="row" data-label="Notes">Deny execution of all or certain functions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_DENY_GUESTS</code></td>
      <td scope="row" data-label="Command arg"><code>deny-guests</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Deny guest users from executing queries.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_DENY_NET</code></td>
      <td scope="row" data-label="Command arg"><code>deny-net</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false, comma-separated list</td>
      <td scope="row" data-label="Notes">Deny all or certain outbound access paths.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CAPS_DENY_SCRIPT</code></td>
      <td scope="row" data-label="Command arg"><code>deny-scripting</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Deny execution of embedded scripting functions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CHANGEFEED_GC_INTERVAL</code></td>
      <td scope="row" data-label="Command arg"><code>changefeed-gc-interval</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">30s</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The interval at which to perform changefeed garbage collection.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_CLIENT_IP</code></td>
      <td scope="row" data-label="Command arg"><code>client-ip</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">none, socket, CF-Connecting-IP, Fly-Client-IP, True-Client-IP, X-Real-IP, X-Forwarded-For, Forwarded</td>
      <td scope="row" data-label="Notes">The method of detecting the client's IP address. _(since v3.1.0)_ <code>Forwarded</code> parses the RFC 7239 <code>Forwarded</code> header (<code>for=</code> parameter).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DATABASE</code></td>
      <td scope="row" data-label="Command arg"><code>database</code></td>
      <td scope="row" data-label="Command">`sql`</td>
      <td scope="row" data-label="Default">main</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The database selected when starting the REPL.</td>
    </tr>
<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DATABASE</code></td>
      <td scope="row" data-label="Command arg"><code>database</code></td>
      <td scope="row" data-label="Command">`export`, `import`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The database selected for the import or export.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DEFAULT_DATABASE</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Command arg"><code>default-database</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">main</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The default database to use when starting a SurrealDB instance.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DEFAULT_NAMESPACE</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Command arg"><code>default-namespace</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">main</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The default namespace to use when starting a SurrealDB instance.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DURABLE_SESSIONS</code>_(since v3.2.2)_</td>
      <td scope="row" data-label="Command arg"><code>durable-sessions</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Persist client-attached HTTP RPC sessions in the datastore so they survive restarts and can be resumed on any node sharing the storage. Off by default. WebSocket sessions are not durable. The persisted copy includes authentication state and is stored unencrypted. See <a href="/docs/reference/cli/db/commands/start.md#durable-http-rpc-sessions">Durable HTTP RPC sessions</a>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DURABLE_SESSION_GC_INTERVAL</code>_(since v3.2.2)_</td>
      <td scope="row" data-label="Command arg"><code>durable-session-gc-interval</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">60s</td>
      <td scope="row" data-label="Allowed values">A duration; <code>0</code> disables the background sweep</td>
      <td scope="row" data-label="Notes">How often expired durable HTTP RPC sessions are purged. Lazy expiry on load still applies when the sweep is disabled. Only meaningful when durable sessions are enabled.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_DURABLE_SESSION_TTL</code>_(since v3.2.2)_</td>
      <td scope="row" data-label="Command arg"><code>durable-session-ttl</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">24h</td>
      <td scope="row" data-label="Allowed values">A duration greater than zero</td>
      <td scope="row" data-label="Notes">Idle lifetime of a persisted HTTP RPC session; each use refreshes the expiry (sliding TTL). Must be greater than zero when durable sessions are enabled.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_GRAPH_FOLD_INTERVAL</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Command arg"><code>graph-fold-interval</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">5s</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The interval between background passes that fold a vertex's per-edge graph adjacency keys into packed blocks. A pass also starts early when a write queues fold work or a traversal meets a vertex with many unfolded keys. See also <code>SURREAL_GRAPH_FOLD_BATCH</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HIDE_WELCOME</code></td>
      <td scope="row" data-label="Command arg"><code>hide-welcome</code></td>
      <td scope="row" data-label="Command">`sql`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to show the welcome message when starting the REPL.</td>
    </tr>
<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_IMPORT_FILE</code></td>
      <td scope="row" data-label="Command arg"><code>import-file</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A file path</td>
      <td scope="row" data-label="Notes">Path to a SurrealQL file that will be imported when starting the server.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_INDEX_COMPACTION_INTERVAL</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Command arg"><code>index-compaction-interval</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">5s</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The interval at which to compact queued index updates. Writes to a full-text, count, or vector index enqueue pending updates that a background task folds into the index; this controls how often that task runs. One node in a cluster performs the work for the whole cluster. Lengthening the interval allows the pending backlog to grow, which increases the work each subsequent query must do to read through it.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KEY</code></td>
      <td scope="row" data-label="Command arg"><code>key</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string 16, 24, or 32 bytes long</td>
      <td scope="row" data-label="Notes">Encryption key to use for on-disk encryption. Not currently in use.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KVS_CA</code></td>
      <td scope="row" data-label="Command arg"><code>kvs-ca</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">String to a path</td>
      <td scope="row" data-label="Notes">Path to the CA file used when connecting to the remote KV store.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KVS_CRT</code></td>
      <td scope="row" data-label="Command arg"><code>kvs-crt</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">String to a path</td>
      <td scope="row" data-label="Notes">Path to the certificate file used when connecting to the remote KV store.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_KVS_KEY</code></td>
      <td scope="row" data-label="Command arg"><code>kvs-key</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">String to a path</td>
      <td scope="row" data-label="Notes">Path to the private key file used when connecting to the remote KV store.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LAZY_SURREALISM</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Command arg"><code>lazy-surrealism</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to lazy-load Surrealism modules instead of eagerly compiling them at server startup.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG</code></td>
      <td scope="row" data-label="Command arg"><code>log</code></td>
      <td scope="row" data-label="Command">`start`, `fix`</td>
      <td scope="row" data-label="Default">info</td>
      <td scope="row" data-label="Allowed values">none, full, error, warn, info, debug, trace</td>
      <td scope="row" data-label="Notes">The logging level for the database server.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_FILE_ENABLED</code>_(since v2.4.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-file-enabled</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Toggles file output.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_FILE_FORMAT</code>_(since v2.4.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-file-format</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">text, json</td>
      <td scope="row" data-label="Notes">The format for log file output.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_FILE_LEVEL</code>_(since v2.4.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-file-level</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">none, full, error, warn, info, debug, trace</td>
      <td scope="row" data-label="Notes">Override the logging level for file output</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_FILE_NAME</code>_(since v2.4.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-file-name</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">surrealdb.log</td>
      <td scope="row" data-label="Allowed values">String to a file</td>
      <td scope="row" data-label="Notes">Filename for logs (default: `surrealdb.log`)</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_FILE_PATH</code>_(since v2.4.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-file-path</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">logs</td>
      <td scope="row" data-label="Allowed values">String to a path</td>
      <td scope="row" data-label="Notes">Sets the directory for logs</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_FILE_ROTATION</code>_(since v2.4.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-file-rotation</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">daily</td>
      <td scope="row" data-label="Allowed values">daily, hourly, never</td>
      <td scope="row" data-label="Notes">Sets the rotation duration for logs.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_FORMAT</code>_(since v2.4.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-format</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">text</td>
      <td scope="row" data-label="Allowed values">text, json</td>
      <td scope="row" data-label="Notes">Sets the format for logs.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_OTEL_LEVEL</code><br />_(since v2.4.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-otel-level</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">SURREAL_LOG logging level</td>
      <td scope="row" data-label="Allowed values">none, full, error, warn, info, debug, trace</td>
      <td scope="row" data-label="Notes">Override the logging level for OpenTelemetry</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_SOCKET</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-socket</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">String to a host:port</td>
      <td scope="row" data-label="Notes">Send logs to the specified host:port</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_SOCKET_FORMAT</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-socket-format</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">text</td>
      <td scope="row" data-label="Allowed values">text, json</td>
      <td scope="row" data-label="Notes">  Set the format of the logs to the socket.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_LOG_SOCKET_LEVEL</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Command arg"><code>log-socket-level</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">SURREAL_LOG logging level</td>
      <td scope="row" data-label="Allowed values">none, full, error, warn, info, debug, trace</td>
      <td scope="row" data-label="Notes">  Override the logging level for socket logs. Possible values: none, full, error, warn, info, debug, trace</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NAME</code></td>
      <td scope="row" data-label="Command arg"><code>name</code></td>
      <td scope="row" data-label="Command">`ml export`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The name of the model.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NAMESPACE</code></td>
      <td scope="row" data-label="Command arg"><code>namespace</code></td>
      <td scope="row" data-label="Command">`sql`</td>
      <td scope="row" data-label="Default">main</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The namespace to connect to via the REPL.</td>
    </tr>
<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NAMESPACE</code></td>
      <td scope="row" data-label="Command arg"><code>namespace</code></td>
      <td scope="row" data-label="Command">`export`, `import`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The namespace selected for the import/export operation.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NO_BANNER</code></td>
      <td scope="row" data-label="Command arg"><code>no-banner</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to hide the startup banner.</td>
    </tr>
<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NO_DEFAULTS</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Command arg"><code>no-defaults</code></td>
      <td scope="row" data-label="Command"><code>`start`</code></td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to disable default namespace and database creation. Conflicts with SURREAL_DEFAULT_DATABASE and SURREAL_DEFAULT_NAMESPACE, which set a default value for namespace and database for a new instance.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NO_IDENTIFICATION_HEADERS</code></td>
      <td scope="row" data-label="Command arg"><code>no-identification-headers</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to suppress the server name and version headers.</td>
    </tr>
<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NODE_MEMBERSHIP_CHECK_INTERVAL</code></td>
      <td scope="row" data-label="Command arg"><code>node-membership-check-interval</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">15s</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The interval at which to process and archive inactive nodes.</td>
    </tr>
  <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NODE_MEMBERSHIP_CLEANUP_INTERVAL</code></td>
      <td scope="row" data-label="Command arg"><code>node-membership-cleanup-interval</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">300s</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The interval at which to process and cleanup archived nodes.</td>
    </tr>
  <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_NODE_MEMBERSHIP_REFRESH_INTERVAL</code></td>
      <td scope="row" data-label="Command arg"><code>node-membership-refresh-interval</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">3s</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The interval at which to refresh node registration information. The <a href="/docs/reference/rest-api/http-protocol.md#ready"><code>/ready</code></a> endpoint reports the node unhealthy when its cluster heartbeat is older than three times this interval (9s by default). The multiple is fixed and cannot be configured.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_PASS</code></td>
      <td scope="row" data-label="Command arg"><code>pass</code></td>
      <td scope="row" data-label="Command">`export`, `import`, `sql`, `start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">Database authentication password to use when connecting.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var">SURREAL_PATH</td>
      <td scope="row" data-label="Command arg"><code>path</code></td>
      <td scope="row" data-label="Command">`fix`, `start`</td>
      <td scope="row" data-label="Default">memory</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">Database path used for storing data. As a required argument (albeit with a default), it is not passed in via `--path`.</td>
    </tr>
<tr>
      <td scope="row" data-label="Env var"><code>SURREAL_PLANNER_STRATEGY</code>_(since v3.0.0)_</td>
      <td scope="row" data-label="Command arg"><code>planner-strategy</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">best-effort</td>
      <td scope="row" data-label="Allowed values">best-effort|compute-only|all-read-only</td>
      <td scope="row" data-label="Notes">Which strategy to use with the new query planner introduced in SurrealDB 3.0. The default setting uses the new planner for read-only statements, falling back to the previous compute planner on unimplemented paths. The new planner can be skipped entirely by using compute-only.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_QUERY_TIMEOUT</code></td>
      <td scope="row" data-label="Command arg"><code>query-timeout</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The maximum duration that a batch of statements can run for. From 3.3.0 it is also a hard wall-clock limit on each HTTP <code>/sql</code>, <code>/gql</code> and <code>/graphql</code> request and on each RPC method call. An HTTP <code>/sql</code> or <code>/gql</code> request that exceeds it is answered with <code>504 Gateway Timeout</code>, and a <code>/graphql</code> request with a GraphQL error. The <code>begin</code>, <code>commit</code> and <code>cancel</code> RPC methods are exempt, but the calls made inside the transaction they control are still limited individually. _(since v3.3.0)_</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_RECLAIM_INTERVAL</code>_(since v3.2.0)_</td>
      <td scope="row" data-label="Command arg"><code>reclaim-interval</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">60s</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">How often the background reaper scans for tombstoned namespace, database, and index data to physically delete after a <code>REMOVE</code> statement. On the 2.x line it is available from 2.7.0 and covers namespaces and databases only.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_RECLAIM_GRACE</code>_(since v3.2.0)_</td>
      <td scope="row" data-label="Command arg"><code>reclaim-grace</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">10m</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">Minimum age a removed namespace, database, or index must reach before its data is reclaimed. The effective grace is the maximum of this value and <code>--tikv-gc-lifetime</code> on TiKV backends. Not available on 2.x.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SLOW_QUERY_LOG_THRESHOLD</code><br />_(since v2.3.8)_</td>
      <td scope="row" data-label="Command arg"><code>slow-log-threshold</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">A duration specifying the minimum execution time after which a log is made to indicate a slow query</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SLOW_QUERY_LOG_PARAM_ALLOW</code><br /><code>slow-log-param-allow</code> _(since v2.3.9)_</td>
      <td scope="row" data-label="Command arg"><code>slow-log-param-allow</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">Comma-separated strings</td>
      <td scope="row" data-label="Notes">A comma-separated list of parameter names to include in slow query logs.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SLOW_QUERY_LOG_PARAM_DENY</code>_(since v2.3.9)_</td>
      <td scope="row" data-label="Command arg"><code>slow-log-param-deny</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">Comma-separated strings</td>
      <td scope="row" data-label="Notes">A comma-separated list of parameter names to omit from slow query logs.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_STRICT</code></td>
      <td scope="row" data-label="Command arg"><code>strict</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether strict mode is enabled on this database instance. Ignored since SurrealDB 3.0 after which strictness is defined [per database](/docs/reference/query-language/statements/define/database.md#defining-a-strict-database) instead of instance.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_STARTUP_OPERATION_TIMEOUT</code></td>
      <td scope="row" data-label="Command arg"><code>startup-operation-timeout</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">60s</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">How long each built-in startup datastore operation may keep retrying before the server gives up. It is a per-operation budget rather than a total for startup, and it covers initialising the default namespace and database, initialising the root credentials, and registering this node in the cluster. Raise it where the storage backend is slow to accept the first writes.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TEMPORARY_DIRECTORY</code></td>
      <td scope="row" data-label="Command arg"><code>temporary-directory</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">String to a directory</td>
      <td scope="row" data-label="Notes">Sets the directory for storing temporary database files</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TOKEN</code></td>
      <td scope="row" data-label="Command arg"><code>token</code></td>
      <td scope="row" data-label="Command">`export`, `import`, `sql`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">Authentication token in JWT format to use when connecting.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TRANSACTION_TIMEOUT</code></td>
      <td scope="row" data-label="Command arg"><code>transaction-timeout</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The maximum duration that any single transaction can run for.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_UNAUTHENTICATED</code></td>
      <td scope="row" data-label="Command arg"><code>unauthenticated</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to allow unauthenticated access.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_USER</code></td>
      <td scope="row" data-label="Command arg"><code>user</code></td>
      <td scope="row" data-label="Command">`export`, `import`, `sql`, start</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">Database authentication username to use when connecting.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_VERSION</code></td>
      <td scope="row" data-label="Command arg"><code>version</code></td>
      <td scope="row" data-label="Command">`ml export`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">The version of the ML model.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEB_CRT</code></td>
      <td scope="row" data-label="Command arg"><code>web-crt</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">String to a path</td>
      <td scope="row" data-label="Notes">Path to the certificate file for encrypted client connections.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_WEB_KEY</code></td>
      <td scope="row" data-label="Command arg"><code>web-key</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">String to a path</td>
      <td scope="row" data-label="Notes">Path to the private key file for encrypted client connections.</td>
    </tr>
  </tbody>
</table>

### Authentication rate limiting environment variables

_(since v3.3.0)_

These variables throttle authentication attempts on the HTTP `/signin` and `/signup` endpoints, per client address. Both endpoints draw on one shared budget. The limiter is off by default, and setting any of the burst, rate, or tracked-client values to `0` also turns it off.

An attempt that exceeds the budget is answered with `429 Too Many Requests` and a `Retry-After` header giving the seconds to wait.

> [!IMPORTANT]
> The limiter keys on the address resolved by [`--client-ip`](/docs/reference/cli/db/commands/start.md), so it needs a strategy that yields one address per client. With `--client-ip socket` behind a proxy, every request resolves to the proxy's address and shares a single budget, which throttles all authentication collectively. With `--client-ip none` no address is available and the limiter does nothing. A header strategy is only as trustworthy as the proxy setting the header: a request arriving without it yields no address and is admitted unchecked, so a header strategy protects only a deployment where every request to these endpoints passes through that proxy.

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '50%'}}>Environment variable</th>
      <th scope="col" style={{width: '15%'}}>Default</th>
      <th scope="col" style={{width: '15%'}}>Allowed values</th>
      <th scope="col" style={{width: '30%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_AUTH_RATE_LIMIT_ENABLED</code></td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to rate limit authentication attempts on the HTTP <code>/signin</code> and <code>/signup</code> endpoints per client address. Only <code>true</code> and <code>false</code> are accepted; any other value is ignored and the default applies.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_AUTH_RATE_LIMIT_BURST</code></td>
      <td scope="row" data-label="Default">60</td>
      <td scope="row" data-label="Allowed values">A u32</td>
      <td scope="row" data-label="Notes">How many authentication attempts a single client address may make before being throttled. Set to <code>0</code> to disable the limiter.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_AUTH_RATE_LIMIT_PER_MINUTE</code></td>
      <td scope="row" data-label="Default">60</td>
      <td scope="row" data-label="Allowed values">A u32</td>
      <td scope="row" data-label="Notes">The sustained rate, in attempts per minute, at which a client address's budget refills. The default of 60 is one attempt per second. Set to <code>0</code> to disable the limiter.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_HTTP_AUTH_RATE_LIMIT_MAX_TRACKED_CLIENTS</code></td>
      <td scope="row" data-label="Default">16384</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">How many client addresses the limiter tracks at once. Unauthenticated callers write to this store, so it is a bounded cache: at capacity a new address evicts an existing entry rather than the store growing or the limiter switching off. An evicted address starts again from a full burst, so the sustained rate binds an address only while it stays tracked. Size this above the number of distinct addresses the instance authenticates. Set to <code>0</code> to disable the limiter.</td>
    </tr>
  </tbody>
</table>

## Storage backend environment variables

These environment variables are used to configure the storage backend for SurrealDB.

### RocksDB environment variables

Many RocksDB environment variables pertain to memory use. The default configuration results in the following rough estimates of RocksDB memory use on different instances:

| Instance memory size  | Estimate
| ------------- |:-------------:|
| 512 MiB | ~ 80MiB |
| 1 GiB | ~ 80MiB
| 2 GiB | ~ 640MiB
| 4 GiB | ~ 1.25GiB
| 8 GiB | ~ 3.25GiB
| 24 GiB | ~ 12GiB
| 128 GiB | ~ 67GiB

The available environment variables for configuring a RocksDB instance are:

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '50%'}}>Environment variable</th>
      <th scope="col" style={{width: '15%'}}>Default</th>
      <th scope="col" style={{width: '15%'}}>Allowed values</th>
      <th scope="col" style={{width: '30%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_BACKGROUND_FLUSH</code></td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">false, true</td>
      <td scope="row" data-label="Notes">Whether to enable background WAL file flushing. 2.x only: from 3.0.0, set a duration on <code>SURREAL_DATASTORE_SYNC_DATA</code> instead.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_BACKGROUND_FLUSH_INTERVAL</code></td>
      <td scope="row" data-label="Default">200 (milliseconds)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The interval in milliseconds between background flushes. 2.x only: from 3.0.0, set a duration on <code>SURREAL_DATASTORE_SYNC_DATA</code> instead.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_BLOB_COMPACTION_READAHEAD_SIZE</code></td>
      <td scope="row" data-label="Default">0</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Readahead size for blob compaction/GC.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_BLOB_COMPRESSION_TYPE</code></td>
      <td scope="row" data-label="Default">snappy</td>
      <td scope="row" data-label="Allowed values">none, snappy, lz4, zstd</td>
      <td scope="row" data-label="Notes">Compression type used for blob files.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_BLOB_FILE_SIZE</code></td>
      <td scope="row" data-label="Default">268,435,456 (256 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The target blob file size for RocksDB.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_BLOB_GC_AGE_CUTOFF</code></td>
      <td scope="row" data-label="Default">0.5</td>
      <td scope="row" data-label="Allowed values">Float between 0 and 1</td>
      <td scope="row" data-label="Notes">Fractional age cutoff for blob GC.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_BLOB_GC_FORCE_THRESHOLD</code></td>
      <td scope="row" data-label="Default">0.5</td>
      <td scope="row" data-label="Allowed values">Float between 0 and 1</td>
      <td scope="row" data-label="Notes">Discardable ratio threshold to force GC.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_BLOCK_CACHE_SIZE</code></td>
      <td scope="row" data-label="Default">Dynamically calculated via greater of ((system memory / 2) - 1 GiB) and 16MiB</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">RocksDB <a href="https://github.com/facebook/rocksdb/wiki/memory-usage-in-rocksdb">block cache size</a> in bytes</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_BLOCK_SIZE</code></td>
      <td scope="row" data-label="Default">65,536 (64 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The size of each uncompressed data block in bytes.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_COMPACTION_READAHEAD_SIZE</code></td>
      <td scope="row" data-label="Default">4 MiB (systems under 4 GiB), 8 MiB (up to 16 GiB), 16 MiB (others)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The readahead buffer size used during compaction.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_COMPACTION_STYLE</code> </td>
      <td scope="row" data-label="Default">level</td>
      <td scope="row" data-label="Allowed values">level, universal</td>
      <td scope="row" data-label="Notes">Use to specify the database compaction style.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_COMPACT_ON_SHUTDOWN</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether a graceful shutdown runs a full compaction of the database after flushing the memtables, as <code>ALTER SYSTEM COMPACT</code> does. On a large database this can take minutes or hours, which can be longer than the shutdown grace period of a container orchestrator.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_DELETION_FACTORY_DELETE_COUNT</code></td>
      <td scope="row" data-label="Default">50</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The number of deletions to track in the window.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_DELETION_FACTORY_RATIO</code></td>
      <td scope="row" data-label="Default">0.5</td>
      <td scope="row" data-label="Allowed values">A float</td>
      <td scope="row" data-label="Notes">The ratio of deletions to track in the window.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_DELETION_FACTORY_WINDOW_SIZE</code></td>
      <td scope="row" data-label="Default">1000</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The size of the window used to track deletions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_ENABLE_BLOB_FILES</code></td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to enable separate key and value file storage.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_ENABLE_BLOB_GC</code></td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to enable blob garbage collection for RocksDB.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_ENABLE_PIPELINED_WRITES</code></td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to use separate queues for WAL writes and memtable writes.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_FILE_COMPACTION_TRIGGER</code></td>
      <td scope="row" data-label="Default">4</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The number of files needed to trigger level 0 compaction.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_FILE_READS_FOR_AUTO_READAHEAD</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">2</td>
      <td scope="row" data-label="Allowed values">A u64</td>
      <td scope="row" data-label="Notes">The number of sequential reads an iterator makes on one SST file before RocksDB starts to prefetch. Set to <code>0</code> to prefetch from the first read.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_GROUPED_COMMIT_MAX_BATCH_SIZE</code></td>
      <td scope="row" data-label="Default">4096</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of transactions in a single grouped commit batch. Used to prevent unbounded memory growth while still allowing large batches for efficiency. Larger batches improve throughput but increase memory usage and commit latency.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_GROUPED_COMMIT_TIMEOUT</code></td>
      <td scope="row" data-label="Default">5ms</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">The maximum wait time in nanosecond before forcing a grouped commit. Used to ensure that transactions do not wait indefinitely when concurrency is low, and to balance between transaction latency and write throughput.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_GROUPED_COMMIT_WAIT_THRESHOLD</code></td>
      <td scope="row" data-label="Default">12</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Threshold for deciding whether to wait for more transactions. If the current batch size is greater or equal to this threshold (and below ROCKSDB_GROUPED_COMMIT_MAX_BATCH_SIZE), then the coordinator will wait up to ROCKSDB_GROUPED_COMMIT_TIMEOUT to collect more transactions. Smaller batches are flushed immediately to preserve low latency.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_INITIAL_AUTO_READAHEAD_SIZE</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">8,192 (8 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The size of the first prefetch. Each following read doubles it, up to <code>SURREAL_ROCKSDB_MAX_AUTO_READAHEAD_SIZE</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_JOBS_COUNT</code></td>
      <td scope="row" data-label="Default">Number of CPUs * 2</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of threads to use for flushing and compaction.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_KEEP_LOG_FILE_NUM</code></td>
      <td scope="row" data-label="Default">10</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of information log files to keep.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_LEVEL0_SLOWDOWN_WRITES_TRIGGER</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">8</td>
      <td scope="row" data-label="Allowed values">An i32</td>
      <td scope="row" data-label="Notes">The number of level-0 files at which RocksDB starts to slow down writes. The RocksDB default is 20.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_LEVEL0_STOP_WRITES_TRIGGER</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">12</td>
      <td scope="row" data-label="Allowed values">An i32</td>
      <td scope="row" data-label="Notes">The number of level-0 files at which RocksDB stops writes until compaction reduces the count. Keep it above <code>SURREAL_ROCKSDB_LEVEL0_SLOWDOWN_WRITES_TRIGGER</code>. The RocksDB default is 36.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_MAX_AUTO_READAHEAD_SIZE</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">262,144 (256 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The largest prefetch an iterator grows to. A larger value can make long range scans faster, but uses more memory for each iterator, so many concurrent scans multiply the cost.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_MAX_CONCURRENT_SUBCOMPACTIONS</code></td>
      <td scope="row" data-label="Default">4</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number threads which will perform compactions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_MAX_OPEN_FILES</code></td>
      <td scope="row" data-label="Default">1024</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of open files which can be opened by RocksDB.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_MAX_WRITE_BUFFER_NUMBER</code></td>
      <td scope="row" data-label="Default">2 (systems under 4 GiB), 4 (up to 16 GiB), 8 (up to 64 GiB), 32 (others)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of write buffers which can be used.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_MEMTABLE_PREFIX_BLOOM_RATIO</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">0.1</td>
      <td scope="row" data-label="Allowed values">An f64</td>
      <td scope="row" data-label="Notes">The size of the prefix bloom filter in each memtable, as a ratio of the write buffer size. RocksDB caps it at 0.25. Set to <code>0</code> to disable it. Only used when <code>SURREAL_ROCKSDB_PREFIX_EXTRACTOR_ENABLED</code> is true.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_MIN_BLOB_SIZE</code></td>
      <td scope="row" data-label="Default">4096</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The minimum size in bytes of a value for it to be stored in blob files.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_MIN_WRITE_BUFFER_NUMBER_TO_MERGE</code></td>
      <td scope="row" data-label="Default">2</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The minimum number of write buffers to merge before writing to disk.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_PERIODIC_COMPACTION_SECONDS</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">3600</td>
      <td scope="row" data-label="Allowed values">A u64</td>
      <td scope="row" data-label="Notes">The interval in seconds after which RocksDB rewrites an SST file that no other compaction has touched. This removes old versions and tombstones from data that is rarely written. Set to <code>0</code> to disable.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_PREFIX_EXTRACTOR_ENABLED</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether SST bloom filters are built on table and key-type prefixes, so that a scan can skip SST files that hold nothing from the table it reads.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_RUNTIME_RESERVE</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">2</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The number of runtime worker threads kept free for asynchronous work while other transactions wait on a storage call. A larger value keeps more threads free under load, but lowers the number of storage calls that can run at once. See <code>SURREAL_RUNTIME_WORKER_THREADS</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_SCAN_VERIFY_CHECKSUMS</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether scans and counts verify the checksum of each block when it is first read from disk. Disabling it makes cold scans faster, and is only safe on trusted storage.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_SHUTDOWN_WAIT_FOR_COMPACT_SECONDS</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">30</td>
      <td scope="row" data-label="Allowed values">A u64</td>
      <td scope="row" data-label="Notes">How long a graceful shutdown waits in seconds for a compaction that started after the final flush, so that the next start does not have to repeat it. Set to <code>0</code> to wait without a limit.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_SST_MAX_ALLOWED_SPACE_USAGE</code></td>
      <td scope="row" data-label="Default">0</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum allowed space usage for SST files in bytes. The default of 0 means unlimited and disables space monitoring. When this limit is reached, the datastore enters read-and-deletion-only mode, where only read and delete operations are allowed. This allows gradual space recovery through data deletion.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_STORAGE_LOG_LEVEL</code></td>
      <td scope="row" data-label="Default">warn</td>
      <td scope="row" data-label="Allowed values">none, full, error, warn, info, debug, trace</td>
      <td scope="row" data-label="Notes">The information log level of the RocksDB library.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_TARGET_FILE_SIZE_BASE</code></td>
      <td scope="row" data-label="Default">67,108,864 (64 MiB)</td>
      <td scope="row" data-label="Allowed values">-</td>
      <td scope="row" data-label="Notes">The target file size for compaction in bytes.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_TARGET_FILE_SIZE_MULTIPLIER</code></td>
      <td scope="row" data-label="Default">2</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The target file size multiplier for each compaction level.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_THREAD_COUNT</code></td>
      <td scope="row" data-label="Default">Number of CPUs on machine</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The number of threads to start for flushing and compaction.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_UNIVERSAL_COMPRESSION_SIZE_PERCENT</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">-1</td>
      <td scope="row" data-label="Allowed values">An i32</td>
      <td scope="row" data-label="Notes">The percentage of output files to compress. <code>-1</code> compresses all of them. Only used when <code>SURREAL_ROCKSDB_COMPACTION_STYLE</code> is <code>universal</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_UNIVERSAL_MAX_MERGE_WIDTH</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">4,294,967,295 (no limit)</td>
      <td scope="row" data-label="Allowed values">A u32</td>
      <td scope="row" data-label="Notes">The largest number of files in a single merge. Only used when <code>SURREAL_ROCKSDB_COMPACTION_STYLE</code> is <code>universal</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_UNIVERSAL_MAX_SIZE_AMPLIFICATION_PERCENT</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">200</td>
      <td scope="row" data-label="Allowed values">A u32</td>
      <td scope="row" data-label="Notes">The largest acceptable storage amplification, as a percentage of the size of the live data. Only used when <code>SURREAL_ROCKSDB_COMPACTION_STYLE</code> is <code>universal</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_UNIVERSAL_MIN_MERGE_WIDTH</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">2</td>
      <td scope="row" data-label="Allowed values">A u32</td>
      <td scope="row" data-label="Notes">The smallest number of files in a single merge. Only used when <code>SURREAL_ROCKSDB_COMPACTION_STYLE</code> is <code>universal</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_UNIVERSAL_SIZE_RATIO</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">1</td>
      <td scope="row" data-label="Allowed values">An i32</td>
      <td scope="row" data-label="Notes">How much larger, as a percentage, a file can be than the files before it and still be picked for the same merge. Only used when <code>SURREAL_ROCKSDB_COMPACTION_STYLE</code> is <code>universal</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_UNIVERSAL_STOP_STYLE</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">total</td>
      <td scope="row" data-label="Allowed values">similar_size, total</td>
      <td scope="row" data-label="Notes">How RocksDB decides that a merge has collected enough files. Only used when <code>SURREAL_ROCKSDB_COMPACTION_STYLE</code> is <code>universal</code>.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_WAL_SIZE_LIMIT</code></td>
      <td scope="row" data-label="Default">0</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The write-ahead-log size limit in MiB.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_WHOLE_KEY_FILTERING</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether SST bloom filters also hold whole keys as well as prefixes. Whole keys make point lookups faster, at the cost of larger filters. Disabling it can suit a workload that mostly scans. Only used when <code>SURREAL_ROCKSDB_PREFIX_EXTRACTOR_ENABLED</code> is true.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_ROCKSDB_WRITE_BUFFER_SIZE</code></td>
      <td scope="row" data-label="Default">32 MiB (systems under 1 GiB), 64 MiB (up to 16 GiB), 128 MiB (others)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The amount of data each write buffer can build up in memory.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SYNC_DATA</code></td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to sync writes to disk before acknowledgement. 2.x only: replaced in 3.0.0 by <code>SURREAL_DATASTORE_SYNC_DATA</code>.</td>
    </tr>
  </tbody>
</table>

### SurrealKV environment variables

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '50%'}}>Environment variable</th>
      <th scope="col" style={{width: '15%'}}>Default</th>
      <th scope="col" style={{width: '15%'}}>Allowed values</th>
      <th scope="col" style={{width: '30%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_BLOCK_CACHE_CAPACITY</code></td>
      <td scope="row" data-label="Default">Dynamically calculated via greater of ((system memory / 2) - 1 GiB) and 16MiB</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum log file size in bytes.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_BLOCK_SIZE</code></td>
      <td scope="row" data-label="Default">65_536 (64 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The block size in bytes.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_ENABLE_VLOG</code></td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to enable value log separation.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_GROUPED_COMMIT_MAX_BATCH_SIZE</code></td>
      <td scope="row" data-label="Default">4096</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The maximum number of transactions in a single grouped commit batch. This prevents unbounded memory growth while still allowing large batches for efficiency. Larger batches improve throughput but increase memory usage and commit latency.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_GROUPED_COMMIT_TIMEOUT</code></td>
      <td scope="row" data-label="Default">5ms</td>
      <td scope="row" data-label="Allowed values">A duration in nanoseconds</td>
      <td scope="row" data-label="Notes">The maximum wait time in nanoseconds before forcing a grouped commit. Ensures that transactions do not wait indefinitely under low concurrency and balances commit latency against write throughput.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_GROUPED_COMMIT_WAIT_THRESHOLD</code></td>
      <td scope="row" data-label="Default">12</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Threshold for deciding whether to wait for more transactions. If the current batch size is greater or equal to this threshold (and below SURREAL_SURREALKV_GROUPED_COMMIT_MAX_BATCH_SIZE), then the coordinator will wait up to SURREAL_SURREALKV_GROUPED_COMMIT_TIMEOUT to collect more transactions. Smaller batches are flushed immediately to preserve low latency.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_MAX_MEMTABLE_SIZE</code>_(since v3.1.0)_</td>
      <td scope="row" data-label="Default">Depends on system memory: 64 MiB below 1 GiB, 128 MiB below 4 GiB, 256 MiB below 16 GiB, 1 GiB below 64 GiB, and 4 GiB otherwise</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The size of the memory area that holds the memtable. A transaction larger than this fails, so raise it for large transactions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_VERSIONED_INDEX</code></td>
      <td scope="row" data-label="Default">false</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to enable versioned index. Only applies when versioning is enabled.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_VLOG_MAX_FILE_SIZE</code></td>
      <td scope="row" data-label="Default">64 MiB (systems under 4 GiB), 128 MiB (up to 16 GiB), 256 MiB (up to 64 GiB), 512 MiB (others)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The readahead buffer size used during compaction.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_SURREALKV_VLOG_THRESHOLD</code></td>
      <td scope="row" data-label="Default">4096 (4 KiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The value log threshold in bytes. Values larger than this are stored in the value log.</td>
    </tr>
  </tbody>
</table>

### TiKV environment variables

> [!IMPORTANT]
> These variables apply when SurrealDB is started with a `tikv://` endpoint for **local Community experimentation**. They are not the configuration surface for production multi-node HA. Prefer a managed cluster on [SurrealDB Cloud Scale](https://surrealdb.com/pricing/scale) or a self-hosted cluster with [SurrealDB Enterprise](https://surrealdb.com/enterprise). See [Run a multi-node cluster](/docs/running/multi-node.md).

<table>
  <thead>
    <tr>
      <th scope="col" style={{width: '50%'}}>Environment variable</th>
      <th scope="col" style={{width: '15%'}}>Default</th>
      <th scope="col" style={{width: '15%'}}>Allowed values</th>
      <th scope="col" style={{width: '30%'}}>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TIKV_API_VERSION</code></td>
      <td scope="row" data-label="Default">1</td>
      <td scope="row" data-label="Allowed values">A u8</td>
      <td scope="row" data-label="Notes">Which TiKV cluster API version to use.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TIKV_ASYNC_COMMIT</code></td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to use asynchronous transactions.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TIKV_KEYSPACE</code></td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A string</td>
      <td scope="row" data-label="Notes">A string specifying the keyspace identifier for data isolation.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TIKV_GRPC_MAX_DECODING_MESSAGE_SIZE</code></td>
      <td scope="row" data-label="Default">4,194,304 (4 MiB)</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">Sets the maximum decoding size of a gRPC message.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TIKV_ONE_PHASE_COMMIT</code></td>
      <td scope="row" data-label="Default">true</td>
      <td scope="row" data-label="Allowed values">true, false</td>
      <td scope="row" data-label="Notes">Whether to use one-phase transaction commit.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TIKV_REQUEST_TIMEOUT</code></td>
      <td scope="row" data-label="Default">10</td>
      <td scope="row" data-label="Allowed values">A usize</td>
      <td scope="row" data-label="Notes">The duration in seconds for requests before they time out.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TIKV_TLS_CA_PATH</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A file path</td>
      <td scope="row" data-label="Notes">Path to the CA certificate used to verify the TiKV and PD servers. Setting all three <code>SURREAL_TIKV_TLS_*</code> variables switches the connection to mutual TLS (mTLS).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TIKV_TLS_CERT_PATH</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A file path</td>
      <td scope="row" data-label="Notes">Path to the client certificate that SurrealDB presents during mTLS. It must be set together with the CA and key paths.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_TIKV_TLS_KEY_PATH</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Default">none</td>
      <td scope="row" data-label="Allowed values">A file path</td>
      <td scope="row" data-label="Notes">Path to the private key that matches the client certificate. It must be set together with the CA and certificate paths. If only some of the three paths are set, SurrealDB refuses to connect and reports which are missing.</td>
    </tr>
  </tbody>
</table>

### FoundationDB environment variables

> [!WARNING]
> FoundationDB support was removed in SurrealDB 3.0.0. These variables apply to 2.x only.

<table>
  <thead>
    <tr>
      <th scope="col">Environment variable</th>
      <th scope="col">Default value</th>
      <th scope="col">Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_FOUNDATIONDB_TRANSACTION_MAX_RETRY_DELAY</code></td>
      <td scope="row" data-label="Default">500</td>
      <td scope="row" data-label="Notes">The maximum delay between transaction retries in milliseconds.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_FOUNDATIONDB_TRANSACTION_RETRY_LIMIT</code></td>
      <td scope="row" data-label="Default">5</td>
      <td scope="row" data-label="Notes">The maximum number of times a transaction can be retried.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_FOUNDATIONDB_TRANSACTION_TIMEOUT</code></td>
      <td scope="row" data-label="Default">5000</td>
      <td scope="row" data-label="Notes">The maximum transaction timeout in milliseconds.</td>
    </tr>
  </tbody>
</table>

## SurrealDB Cloud environment variables

Instances on SurrealDB Cloud are not started with a CLI command or environment variables. Instead, they can be set from [SurrealDB Studio](/docs/manage/instances/configure.md) or with [`surreal cloud`](/docs/reference/cli/cloud.md).

---

Source: https://surrealdb.com/docs/reference/cli/db/version-history

# Environment variables and flags by version

Which SURREAL_ environment variables and surreal db command-line flags existed in each SurrealDB release from 1.0.0 to 3.3.0, when each was added or removed, and how defaults changed.

This page shows which environment variables and command-line flags each SurrealDB release had, from 1.0.0 in September 2023 to 3.3.0. Use it to check whether a setting exists in the version you run, to find what changed between two versions before an upgrade, or to see when a default changed. The [environment variables](/docs/reference/cli/db/environment-variables.md) page and the [CLI command](/docs/reference/cli/db/commands/start.md) pages describe each setting in the current release.

## Environment variables and flags

Each row is one environment variable or flag, marking the releases that had it, with the newest release on the left. Filter by name, such as `ROCKSDB` or `mcp`, and hover a row for its help text, its exact first and last versions, and the flag or environment variable it pairs with.

<DemoEmbed title="Environment variables and flags by version" height={780} pages={[{ "label": "Environment variables and flags", "src": "/docs/cli-history/history.html?view=timeline" }]} />

## Release contents

Select a release to list every environment variable and flag it has, with defaults and help text. Select a second release under **Compare with** to list only what is added, removed or given a new default when moving from the first release to the second. Pick the version you run first and the target version second, so that the list matches an upgrade or a downgrade.

<DemoEmbed title="Release contents" height={780} pages={[{ "label": "Release contents", "src": "/docs/cli-history/history.html?view=contents" }]} />

## Scope

The lists on this page are read from the SurrealDB source code at each release tag, so they include settings that are not described elsewhere in the documentation. They cover every final release from 1.0.0 onwards, and the alpha, beta and release candidate tags of each release after 1.0.0. The 1.0.0 pre-releases are left out. Dates are the publication dates of the GitHub releases.

- **Environment variables** are the `SURREAL_` names that the server reads, including those that set a command-line flag.
- **Flags and arguments** are those of the `surreal` command and each of its subcommands. A flag declared once on the `surreal` command and accepted by every subcommand, such as `--log` from 2.2.0, counts as present on each subcommand.
- **Defaults** are shown as they are written in the source code. A default that the server works out when it starts, such as a buffer size based on the available memory, is shown as computed at run time.
- **Maintenance releases** of an older line, such as 1.5.6 after the 2.0.0 pre-releases, do not count as removing anything that the newer line added.

Environment variable names that the server builds while it runs, and variables set only in Docker images or deployment files, are not listed.

## Counts by release

| Release | Published | Environment variables | Flags and arguments |
| --- | --- | ---: | ---: |
| 1.0.0 | 2023-09-13 | 33 | 58 |
| 1.1.0 | 2024-01-09 | 65 | 83 |
| 1.2.0 | 2024-02-13 | 68 | 83 |
| 1.3.0 | 2024-03-12 | 69 | 77 |
| 1.4.0 | 2024-04-09 | 69 | 77 |
| 1.5.0 | 2024-05-14 | 71 | 78 |
| 2.0.0 | 2024-09-17 | 89 | 89 |
| 2.1.0 | 2024-11-21 | 104 | 94 |
| 2.2.0 | 2025-02-11 | 131 | 99 |
| 2.3.0 | 2025-05-01 | 135 | 126 |
| 2.4.0 | 2025-11-24 | 168 | 138 |
| 2.5.0 | 2026-01-22 | 170 | 142 |
| 2.6.0 | 2026-01-27 | 170 | 143 |
| 2.7.0 | 2026-09-28 | 171 | 145 |
| 3.0.0 | 2026-02-17 | 192 | 155 |
| 3.1.0 | 2026-06-05 | 249 | 213 |
| 3.2.0 | 2026-07-06 | 268 | 222 |
| 3.3.0 | 2026-09-28 | 298 | 227 |

Across all releases from 1.0.0, 352 environment variables and 246 flags and arguments have existed. Of these, 54 environment variables and 19 flags and arguments are no longer present in 3.3.0. The 2.x releases from 2.3.0 onwards were published alongside the 3.0.0 pre-releases and 3.x, so their dates overlap with the 3.x releases.

From 3.1.0, part of the count is settings read from the configuration map rather than declared one by one: 45 in 3.1.0, 53 in 3.2.0 and 63 in 3.3.0. Each of them can be set with an environment variable whose name is the setting's key in capitals with a `SURREAL_` prefix, such as `SURREAL_ROCKSDB_MEMTABLE_PREFIX_BLOOM_RATIO` for `rocksdb_memtable_prefix_bloom_ratio`.

<DemoEmbed title="Totals per release" height={400} pages={[{ "label": "Totals per release", "src": "/docs/cli-history/history.html?view=totals" }]} />

---

Source: https://surrealdb.com/docs/reference/cli/formatter/overview

# SurrealQL formatter

The surqlfmt command automatically formats SurrealQL files for consistent style and readability.

The `surqlfmt` tool reformats `.surql` files to follow a consistent style. It adjusts whitespace, indentation, and keyword casing so that SurrealQL code is easy to read and review.

`surqlfmt` is a separate command-line tool alongside the [`surreal` CLI](/docs/reference/cli.md). It is shipped as an npm package rather than as part of the `surreal` binary, it never connects to a database, and it only reads and writes files.

## Installation

The tool can be installed through a single [npm install command](https://www.npmjs.com/package/@surrealdb/surql-fmt):

```bash
npm install -g @surrealdb/surql-fmt
```

## Usage

<Synopsis>
surqlfmt [OPTIONS] [FILES]...
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[FILES]...",
            "description": "Files to format, given as paths or glob patterns. Omit them when reading from stdin."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--write",
            "description": "Reformat each file in place instead of printing the result."
        },
        {
            "name": "--check",
            "description": "Report whether the files are already formatted, without changing them. Useful in CI."
        },
        {
            "name": "--stdin",
            "description": "Read SurrealQL from standard input and write the formatted result to standard output."
        },
        {
            "name": "--indent",
            "value": "<WIDTH>",
            "description": "Number of indent characters per level."
        },
        {
            "name": "--indent-char",
            "value": "<CHAR>",
            "description": "Character used for indentation, for example `tab`."
        },
        {
            "name": "--max-line-length",
            "value": "<LENGTH>",
            "description": "Column at which the formatter wraps long lines."
        }
    ]}
/>

The tool prints the reformatted file by default, and reformats a `.surql` file in place when `--write` is passed.

```bash
# Format a single file
surqlfmt ./query.surql

# Check if files are formatted
surqlfmt --check ./src/**/*.surql

# Format from stdin
cat query.surql | surqlfmt --stdin

# Configure indentation and line length
surqlfmt --indent 4 --indent-char tab --max-line-length 120 *.surql

# Write files in-place
surqlfmt --write ./src/**/*.surql
```

For the options of the version you have installed, run `surqlfmt --help`.

## When to use the formatter

- **Before committing** - run `surqlfmt` on any `.surql` migration or seed files to keep diffs clean.
- **In CI** - add a `surqlfmt --check` step to catch inconsistencies early.
- **During development** - pipe ad-hoc queries through the formatter for readability.

For the other command-line tools, see the [CLI tools overview](/docs/reference/cli.md).

---

Source: https://surrealdb.com/docs/reference/cli/launcher

# surreal launcher commands

Reference for the surreal launcher: install, upgrade, use, uninstall, version, --version, +VERSION, help, version names, SURREAL_CLI_* environment variables, and exit codes.

The `surreal` launcher is the command you install once. It owns every operation that installs or changes versions, and it hands everything else to a [component](/docs/explore/cli/components.md). Its own commands are `install`, `upgrade`, `use`, `uninstall` and `version`.

<Synopsis>
surreal [+VERSION] [COMPONENT] <COMMAND> [ARGS]...
surreal install [OPTIONS] <COMPONENT[@VERSION]>...
surreal upgrade [OPTIONS] [COMPONENT[@VERSION] | cli]...
surreal use [OPTIONS] <COMPONENT@VERSION>
surreal uninstall [OPTIONS] <COMPONENT[@VERSION]>...
surreal version [OPTIONS]
</Synopsis>

A component name as the first argument runs that component with everything after it, unchanged: `surreal db ...`, `surreal cloud ...`, `surreal mod ...`. The launcher never interprets those arguments. The guide to [managing components](/docs/explore/cli/components.md) explains how components are stored and chosen.

## Components

| Component | Runs as | Reference |
| --- | --- | --- |
| `db` | `surreal db <command>` | [Database commands](/docs/reference/cli/db.md) |
| `cloud` | `surreal cloud <command>` | [Cloud commands](/docs/reference/cli/cloud.md) |
| `mod` | `surreal mod <command>` | [Modules](/docs/reference/cli/modules.md) |

## Version names

Commands that take `COMPONENT[@VERSION]`, and `+VERSION`, accept the following.

| Form | Meaning |
| --- | --- |
| `3.3.2` or `v3.3.2` | That exact release |
| `2` | The newest stable release on major line 2 |
| `latest` | The newest stable release. Without a version, a component installs this, or its newest beta then alpha while it has no stable release |
| `beta`, `alpha` | The newest release on that channel |
| `nightly` | The latest nightly build. Only the database publishes one |

## surreal install

Install components, or another version of one beside those installed. The first install of a component becomes its default; later installs leave the default alone unless `--pin` is given.

<Synopsis>
surreal install [OPTIONS] <COMPONENT[@VERSION]>...
</Synopsis>

```bash
surreal install cloud
surreal install db@3.3.2 db@2 --pin
```

<OptionsTable
	title="Options"
	options={[
		{ "name": "--pin", "description": "Make each installed version the default. A version named exactly becomes the default whatever it was; a channel's release, such as `db@2`, becomes the default unless the current default is later." },
		{ "name": "--json", "description": "Print one JSON document on stdout." },
		{ "name": "--plain", "description": "Plain text: no tables, no colour, no animation." },
		{ "name": "--color", "value": "<WHEN>", "description": "When to use colour: `auto` (default), `always` or `never`." },
		{ "name": "--quiet", "short": "-q", "description": "Print only results and errors." },
		{ "name": "--yes", "short": "-y", "description": "Answer yes to every question." },
		{ "name": "--no-input", "description": "Never prompt. Fail where a question would be asked." }
	]}
/>

Every download is checked against its published SHA-256 and run once before it is put in place.

## surreal upgrade

Upgrade the launcher and every installed component, or only the ones named. Each follows the channel its default is on: a stable release stays on its major line, and a prerelease follows its own channel. Naming a version moves the default to that version, installing it first if needed. `cli` is the launcher itself.

<Synopsis>
surreal upgrade [OPTIONS] [COMPONENT[@VERSION] | cli]...
</Synopsis>

```bash
surreal upgrade
surreal upgrade db@3.3.2
surreal upgrade cli --dry-run
```

<OptionsTable
	title="Options"
	options={[
		{ "name": "--nightly", "description": "Upgrade the database to the latest nightly build. The same as `db@nightly`." },
		{ "name": "--alpha", "description": "Upgrade the database to the latest alpha release. The same as `db@alpha`." },
		{ "name": "--beta", "description": "Upgrade the database to the latest beta release. The same as `db@beta`." },
		{ "name": "--version", "value": "<VERSION>", "description": "Upgrade the database to this version. The same as `db@<VERSION>`." },
		{ "name": "--dry-run", "description": "Say what would change without changing it." },
		{ "name": "--json", "description": "Print one JSON document on stdout." },
		{ "name": "--plain", "description": "Plain text: no tables, no colour, no animation." },
		{ "name": "--color", "value": "<WHEN>", "description": "When to use colour: `auto` (default), `always` or `never`." },
		{ "name": "--quiet", "short": "-q", "description": "Print only results and errors." },
		{ "name": "--yes", "short": "-y", "description": "Answer yes to every question." },
		{ "name": "--no-input", "description": "Never prompt. Fail where a question would be asked." }
	]}
/>

`surreal db upgrade` is not run. It reports that the launcher manages the database's versions and points to `surreal upgrade db`.

## surreal use

Make an installed version of a component the default. It never downloads.

<Synopsis>
surreal use [OPTIONS] <COMPONENT@VERSION>
</Synopsis>

```bash
surreal use db@2.7.0
surreal use db@2
```

A major line or channel selects the newest installed release on it. The output options are the same as for `surreal install`.

## surreal uninstall

Remove installed versions of components.

<Synopsis>
surreal uninstall [OPTIONS] <COMPONENT[@VERSION]>...
</Synopsis>

```bash
surreal uninstall db@2.7.0
surreal uninstall cloud
```

`db@2.7.0` removes that version, and `db` alone removes every version of the component and its default. A channel such as `db@2` is refused, because it does not name a release. The default is removed by name only once nothing else is installed, so choose another with `surreal use` first. The output options are the same as for `surreal install`.

## surreal version

Show each component, the version that runs, and where it runs from.

<Synopsis>
surreal version [OPTIONS]
</Synopsis>

```text title="Sample output"
COMPONENT  VERSION  SOURCE  INSTALLED  PATH
cli        0.1.0    cli                /usr/local/bin/surreal
db         3.3.4    pinned  3.3.4      /home/you/.surrealdb/components/db/3.3.4/surreal-db
mod
cloud      0.1.0    pinned  0.1.0      /home/you/.surrealdb/components/cloud/0.1.0/surreal-cloud
```

The `SOURCE` column is `pinned` for a version in the launcher's store, `path` for an executable found beside `surreal` or on `PATH`, and `missing` or `unpinned` where nothing is selected. With `--json`, the command prints one document with the launcher, the home folder, whether offline mode is on, and each component's `name`, `version`, `source`, `installed` versions and `path`. The output options are the same as for `surreal install`.

`surreal --version` (`-V`) prints the same information on a few lines. To print a database server's version, use [`surreal db version`](/docs/reference/cli/db/commands/version.md).

## +VERSION

Run one version of a component for a single command, without changing the default.

<Synopsis>
surreal +VERSION COMPONENT [ARGS]...
</Synopsis>

```bash
surreal +2.6.5 db start --user root --pass secret
```

If the version is not installed, the launcher offers to install it, under the rules in [Offline and unattended use](/docs/explore/cli/components.md#offline-and-unattended-use).

## surreal help

`surreal help` and `surreal --help` print the launcher's help, with the banner. `--no-banner` or `SURREAL_CLI_NO_BANNER` prints it without. `surreal <COMPONENT> help` prints a component's own help.

## Deprecated forms

The database's commands still run without `db`, and print a deprecation warning that names the replacement: `surreal start` is `surreal db start`. The set is closed: `start`, `sql`, `import`, `export`, `isready`, `is-ready`, `validate`, `fix`, `ml`, `module` and `mcp`, and database options placed before a command. `surreal module` points to `surreal mod`. See [Using the database](/docs/explore/cli/database.md#the-deprecated-top-level-commands).

## Environment variables

| Variable | Effect |
| --- | --- |
| `SURREAL_CLI_HOME` | The SurrealDB home, where components are stored. Defaults to `~/.surrealdb` |
| `SURREAL_CLI_OFFLINE` | `1` forbids every network request. A missing component fails at once with exit code 12 |
| `SURREAL_CLI_DOWNLOAD_ROOT` | Replaces the download host, for a mirror. An `http` or `https` address with no credentials, query or fragment |
| `SURREAL_CLI_NO_BANNER` | Hides the banner above `surreal help` |
| `SURREAL_CLI_JSON` | The same as `--json` |
| `SURREAL_CLI_PLAIN` | The same as `--plain` |
| `SURREAL_CLI_INSTALL_DB` | Read by the install script: `1` installs the database, `0` does not. See [Install the SurrealDB CLI](/docs/explore/cli/install.md) |

The launcher reads only `SURREAL_CLI_*`. Variables that start with `SURREAL_` and not `SURREAL_CLI_` belong to the database, and [`SURREAL_CLOUD_*`](/docs/reference/cli/cloud/global-flags.md) belong to Cloud. Every component the launcher runs receives `SURREAL_INVOKED_AS`, the way it was typed, so its help reads `surreal cloud instance list`, and `SURREAL_CLI_LAUNCHER`, the launcher's absolute path, which a component uses to run another one.

## Exit codes

A component's exit code is the launcher's. The launcher's own commands use the same codes as `surreal cloud`.

| Code | Meaning |
| --- | --- |
| `0` | The command did what was asked |
| `1` | A failure that fits no other code |
| `2` | A bad invocation, such as an unknown component or an invalid version name |
| `12` | A component the command needs is not installed and was not fetched: offline, nobody there to agree, or the offer declined. The message names the command that installs it |

The full list is in [output and exit codes](/docs/reference/cli/cloud/output-and-exit-codes.md).

---

Source: https://surrealdb.com/docs/reference/cli/modules

# surreal mod

The mod component of the surreal launcher, for building and running WebAssembly modules. Its status, and the surreal db module commands that provide the same workflow today.

`mod` is the launcher component for WebAssembly module tooling: scaffolding, building, inspecting and running Surrealism modules. It is installed and versioned separately from the database, like every other component.

<Synopsis>
surreal mod <COMMAND>
</Synopsis>

> [!NOTE]
> The first release of the `mod` component is not published yet, so `surreal install mod` does not succeed. Until it is, the commands are available as [`surreal db module`](/docs/reference/cli/db/commands/module.md), from the database component. The top-level form `surreal module` is deprecated in favour of `surreal mod` and prints a warning.

## Commands available today

| Command | Purpose |
| --- | --- |
| `surreal db module init [PATH]` | Scaffold a new Surrealism module project. |
| `surreal db module build [SOURCE_PATH]` | Build a WASM module. |
| `surreal db module run <FILE> --fnc <NAME>` | Run a function with arguments. |
| `surreal db module sig <FILE> --fnc <NAME>` | Show a function's signature. |
| `surreal db module info <FILE>` | Show the module's information. |

Flags and examples are on the [`surreal db module`](/docs/reference/cli/db/commands/module.md) page. For a walkthrough, see [Working with modules](/docs/explore/cli/modules.md).

---

Source: https://surrealdb.com/docs/reference/dotnet

# .NET SDK

The official SurrealDB SDK for .NET. Provides methods for interacting with your SurrealDB database.

The SurrealDB SDK for C# and .NET enables you to interact with SurrealDB from server-side applications, systems, and APIs, allowing you to integrate SurrealDB into your website or application backend, and serve dynamic content to your users. You can use the .NET SDK to interact with your SurrealDB database instances, or to run SurrealDB as an embedded database within your C# application, with functionality for executing queries, managing data, running database functions, authenticating to the database, building user signup and authentication functionality, and subscribing to data changes with live queries. When connecting to remote database instances, connections automatically reconnect when terminated.

> [!IMPORTANT]
> The SDK requires either .NET version `8.0` or greater or .NET Standard `2.1` or higher.
> The SDK is available as a [NuGet package](https://www.nuget.org/packages/SurrealDb.Net).

> [!NOTE]
> The latest version of the SDK is `1.0.0`.
> The SDK works with SurrealDB versions `v2.0.0` and later, including the current release, `v3.3.4`.

## Getting started

- [Installation](/docs/reference/dotnet/installation.md) - Install the SDK and add it to your project.

- [Getting started guide](/docs/languages/dotnet.md) - Connect to SurrealDB and run your first queries.

## Learn

- [Concepts](/docs/reference/dotnet/core.md) - Guides for connecting, authenticating, querying, and working with data.

- [API Reference](/docs/reference/dotnet/methods.md) - Complete reference for the SDK's methods, types, and errors.

## Example projects

You can find example repositories that demonstrate how to integrate SurrealDB in a number of different environments:

- [Console App](https://github.com/surrealdb/surrealdb.net/tree/main/SurrealDb.Examples.Console) - A simple Console app example using the .NET SDK for SurrealDB.

- [Minimal APIs](https://github.com/surrealdb/surrealdb.net/tree/main/SurrealDb.Examples.MinimalApis) - A simple ASP.NET API example project using Minimal APIs.

- [Blazor Server](https://github.com/surrealdb/surrealdb.net/tree/main/SurrealDb.Examples.Blazor.Server) - A Blazor Server app example querying data and consuming Live Query from a remote database.

## Contributing

To contribute to the SDK code, submit an Issue or Pull Request in the [surrealdb.net](https://github.com/surrealdb/surrealdb.net) repository. To contribute to this documentation, submit an Issue or Pull Request in the [docs.surrealdb.com](https://github.com/surrealdb/docs.surrealdb.com) repository.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.net)
- [NuGet package](https://nuget.org/packages/surrealdb.net)

---

Source: https://surrealdb.com/docs/reference/dotnet/core

# SDK concepts

The .NET SDK for SurrealDB enables simple and advanced querying of a remote or embedded database.

This section covers the core concepts of the SurrealDB SDK for .NET. You will learn how to connect to a SurrealDB instance, manage authentication, and interact with the database.

- [Create a new Connection](/docs/reference/dotnet/core/create-a-new-connection.md)
- [Handle authentication](/docs/reference/dotnet/core/authentication.md)
- [Set parameters](/docs/reference/dotnet/core/parameters.md)
- [Data manipulation](/docs/reference/dotnet/core/data-manipulation.md)
- [Realtime Streaming](/docs/reference/dotnet/core/streaming.md)
- [Run SurrealQL queries](/docs/reference/dotnet/core/writing-surrealql.md)
- [Multiple sessions](/docs/reference/dotnet/core/multiple-sessions.md)
- [Connection Strings](/docs/reference/dotnet/core/connection-strings.md)
- [Dependency Injection](/docs/reference/dotnet/core/dependency-injection.md)
- [Logging](/docs/reference/dotnet/core/logging.md)

## Transactions

- [Transactions](/docs/reference/dotnet/core/transactions.md) - group statements from the .NET SDK

---

Source: https://surrealdb.com/docs/reference/dotnet/core/authentication

# Handle authentication

In this section, we will walk you through the process of authenticating users and securing your SurrealDB database.

SurrealDB is designed to be used in a distributed environment, so the database and the data stored in it need to be secured.
SurrealDB provides a number of methods for authenticating users and securing the database.

In your SurrealDB database, you can create authentication login using the [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md) statement which supports [JWT](/docs/reference/query-language/statements/define/access/jwt.md) and [Record](/docs/reference/query-language/statements/define/access/record.md) Access methods.

The access method used will inform the input for `Access` in the `.SignUp()` and `.SignIn()` methods.

> [!IMPORTANT]
> If you are not on Version `v3.3.4` of SurrealDB, you will use the `Scope` property instead of `Access`.

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="#signup"> <code> db.SignUp() </code></a></td>
			<td scope="row" data-label="Description">Connects to a local or remote database endpoint</td>
		</tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#signin"> <code> db.SignIn() </code></a></td>
            <td scope="row" data-label="Description">Signs in to a root, namespace, database or scope user</td>
		</tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#invalidate"> <code> db.Invalidate() </code></a></td>
            <td scope="row" data-label="Description">Invalidates the current session</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#authenticate"> <code> db.Authenticate() </code></a></td>
            <td scope="row" data-label="Description">Authenticates a user with a token</td>
        </tr>
	</tbody>
</table>

## Defining access in your application

The .NET SDK has a [`.Query()` method](/docs/reference/dotnet/core/writing-surrealql.md) which allows you to write secure SurrealQL statements from within your application. Using this method, you can define access for your users and securely manage authentication. See the code example below:

```csharp
await db.Query(
    $"""
    DEFINE ACCESS account ON DATABASE TYPE RECORD
		SIGNUP ( CREATE user SET email = $email,
	    pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h;
    """
);
```

> [!NOTE]
> Depending on the connection protocol you choose, authentication tokens and sessions lifetime work differently. Refer to the [connection options](/docs/reference/dotnet/core/create-a-new-connection.md#connection-options) documentation for more information.

## User authentication

After you have defined your authentication login, you can use the following methods to authenticate users:

## `.SignUp()` {#signup}

Signs up to a specific authentication scope / access method.

```csharp title="Method Syntax"
await db.SignUp(credentials)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>credentials</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Credentials to sign up as a scoped user.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
// With Record Access
var authParams = new AuthParams
{
    Namespace = "main",
    Database = "main",
    Access = "user",
	// Also pass any properties required by the access definition
    Email = "info@surrealdb.com",
    Password = "123456"
};

Jwt jwt = await db.SignUp(authParams);

public class AuthParams : ScopeAuth
{
	public string? Username { get; set; }
	public string? Email { get; set; }
	public string? Password { get; set; }
}
```

<br />

## `.SignIn()` {#signin}

Signs in to a root, namespace, database or scope user.

```csharp title="Method Syntax"
await db.SignIn(credentials)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>credentials</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Variables used in a signin query.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

**Root user**

```csharp
// Sign in as root user
await db.SignIn(new RootAuth { Username = "root",
    Password = "secret" });
```

**Namespace user**

```csharp
// Sign in using namespace auth
await db.SignIn(
    new NamespaceAuth
    {
        Namespace = "main", 
        Username = "johndoe", 
        Password = "password123" 
    }
);
```

**Database user**

```csharp
// Sign in using database auth
await db.SignIn(
    new DatabaseAuth
    {
        Namespace = "main", 
        Database = "main", 
        Username = "johndoe", 
        Password = "password123" 
    }
);
```

**Record Access**

```csharp
// Sign in with Record Access
var authParams = new AuthParams
{
    Namespace = "main",
    Database = "main",
    Access = "user",
    Email = "info@surrealdb.com",
    Password = "123456"
};

Jwt jwt = await db.SignIn(authParams);

public class AuthParams : ScopeAuth
{
	public string? Username { get; set; }
	public string? Email { get; set; }
	public string? Password { get; set; }
}
```

**Scopes**

```csharp
// Sign in as a scoped user
var authParams = new AuthParams
{
    Namespace = "main",
    Database = "main",
    Scope = "user",
    Email = "info@surrealdb.com",
    Password = "123456"
};

Jwt jwt = await db.SignIn(authParams);

public class AuthParams : ScopeAuth
{
	public string? Username { get; set; }
	public string? Email { get; set; }
	public string? Password { get; set; }
}
```

<br />

## `.Authenticate()` {#authenticate}

Authenticates the current connection with a JWT token.

```csharp title="Method Syntax"
await db.Authenticate(jwt)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>jwt</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The JWT object holder of the authentication token.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
var jwt = new Jwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJTdXJyZWFsREIiLCJpYXQiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMiwiZXhwIjoxODM2NDM5MDIyLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJTQyI6InVzZXIiLCJJRCI6InVzZXI6dG9iaWUifQ.N22Gp9ze0rdR06McGj1G-h2vu6a6n9IVqUbMFJlOxxA");
await db.Authenticate(jwt);
```

<br />

## `.Invalidate()` {#invalidate}

Invalidates the authentication for the current connection.

```csharp title="Method Syntax"
await db.Invalidate()
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Properties</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Properties">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
await db.Invalidate();
```

---

Source: https://surrealdb.com/docs/reference/dotnet/core/connection-strings

# Connection strings

The .NET SDK for SurrealDB supports the familiar concept of ConnectionString.

Connection Strings are an easy way to configure your application to connect to a SurrealDB instance.
They are stored in the <code>appsettings.json</code> file and can be used to configure the <code>SurrealDbClient</code>.

It is good practice to:

- set a development Connection String in <code>appsettings.Development.json</code>,
- store your production Connection String in a Secret environment variable, or even better in a Vault.

<table>
  <thead>
    <tr>
      <th scope="col">Keys</th>
      <th colspan="2" scope="col">
        Description
      </th>
      <th scope="col">Aliases</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Keys">
        <code>Endpoint</code>
        <label label="required" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        The database endpoint to connect to. <br />
        The disctinction between <code>Server</code> and <code>Client</code> can
        help you ensure you only call a distant database (server mode) or a
        local database (client mode).
      </td>
      <td scope="row" data-label="Aliases">
        <code>Server</code>
        <code>Client</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Keys">
        <code>Namespace</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        Switches to a specific namespace.
      </td>
      <td scope="row" data-label="Aliases">
        <code>NS</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Keys">
        <code>Database</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        Switches to a specific database.
      </td>
      <td scope="row" data-label="Aliases">
        <code>DB</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Keys">
        <code>Username</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        Username used to have root access.
      </td>
      <td scope="row" data-label="Aliases">
        <code>User</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Keys">
        <code>Password</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        Password used to have root access.
      </td>
      <td scope="row" data-label="Aliases">
        <code>Pass</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Keys">
        <code>Token</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        Token (JWT) used to have user access.
      </td>
      <td scope="row" data-label="Aliases"></td>
    </tr>
    <tr>
      <td scope="row" data-label="Keys">
        <code>AuthLevel</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        Auth level when connecting to the SurrealDB instance. <br />
        Valid options are <code>Root</code>, <code>Namespace</code> or{" "}
        <code>Database</code>. <br />
        Defaults to <code>Root</code>.
      </td>
      <td scope="row" data-label="Aliases"></td>
    </tr>
  </tbody>
</table>

## Examples

Here are a couple of examples of Connection Strings:

```sh
Server=http://127.0.0.1:8000;Namespace=test;Database=test;Username=root;Password=secret
```

```sh
Endpoint=http://127.0.0.1:8000;NS=test;DB=test;User=root;Pass=secret
```

```sh
Server=ws://127.0.0.1:8000;AuthLevel=Namespace;NS=test;DB=test;User=root;Pass=secret
```

```sh
Client=mem://;Namespace=test;Database=test
```

---

Source: https://surrealdb.com/docs/reference/dotnet/core/create-a-new-connection

# Create a new connection

The SurrealDB SDK for .NET enables simple and advanced querying of a remote or embedded database.

After [installing the SDK](/docs/reference/dotnet/installation.md), you can initialise a new instance of a SurrealDB client.
When creating a new connection to a SurrealDB instance, you can choose to connect to a local or remote endpoint.

```csharp
using SurrealDb.Net;

using var db = new SurrealDbClient("ws://127.0.0.1:8000/rpc");

await db.Connect();
await db.Use("main", "main");
```

From the code snippet above, you can see that the .NET SDK has a couple of methods that you can use to initialise a new project with SurrealDB.

## `SurrealDbClient`

Creates a new client, detecting the right protocol from the provided endpoint.

```csharp title="Method Syntax"
new SurrealDbClient(endpoint)
```

### Connection options

You can specify your connection protocol either as `http`, `https`, `ws`, or `wss`.
Since SurrealDB also supports RPC over WebSocket, by default, it is specified with a `/rpc` suffix.

**Local endpoint**

```csharp
// Creates a new client using a local endpoint
using var db = new SurrealDbClient("http://127.0.0.1:8000");
```

**Remote endpoint**

```csharp
// Creates a new client using a remote endpoint
using var db = new SurrealDbClient("wss://cloud.surrealdb.com/rpc");
```

**Namespace and database**

```csharp
var options = new SurrealDbOptions
{
    Endpoint = "wss://cloud.surrealdb.com/rpc",
    Namespace = "surrealdb",
    Database = "docs",
};

// Specify a namespace and database pair to use
using var db = new SurrealDbClient(options);
```

**Token**

```csharp
var options = new SurrealDbOptions
{
    Endpoint = "wss://cloud.surrealdb.com/rpc",
    Token = "......",
};

// Authenticate with an existing token
using var db = new SurrealDbClient(options);
```

**Credentials**

```csharp
var options = new SurrealDbOptions
{
    Endpoint = "wss://cloud.surrealdb.com/rpc",
    Username = "root",
    Password = "surrealdb",
};

// Authenticate using a pair of credentials
using var db = new SurrealDbClient(options);
```

> [!NOTE]
> Having to manually set all these options into a `SurrealDbOptions` object can be cumbersome.
> If you are familiar with the concept of Connection Strings, you can simply pass a connection string to the `SurrealDbClient` constructor.
> See the [Connection Strings](/docs/reference/dotnet/core/connection-strings.md) section for more information.

<br />

### Effect of connection protocol on token & session duration

The connection protocol you choose affects how authentication tokens and sessions work:

With websockets connections (`ws://`, `wss://`) you open a single long-lived stateful connection where after the initial authentication, the session duration applies and if not specified, defaults to `NONE` meaning that the session never expires unless otherwise specified.

When you connect with a HTTP connection (`http://`, `https://`), every request you make is short-lived and stateless, requiring you to authenticate every request individually for which the token is used, creating a short lived session. Hence, the token duration which defaults to 1 hour applies.

You can extend the session duration of a token or a session by setting the `DURATION` clause when creating a new access method with the [`DEFINE ACCESS METHOD`](/docs/reference/query-language/statements/define/access.md) statement or when defining a new user with the [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md) statement.

Learn more about token and session duration in our [security best practices](/docs/learn/security/best-practices/security-best-practices.md#expiration) documentation.

<br />
## `.Connect()` {#connect}

The `.Connect()` executes a connection attempt to the underlying endpoint using the provided connection options.

> [!NOTE]
>This method is automatically called before executing any other call to the SurrealDB instance.
>It means that you do not have to explicitly call this method.
>In some contexts, calling this method beforehand can improve performance by avoiding cold starts.

### Example usage

```csharp
await db.Connect();
```

<br />

## `.Use()` {#use}

Depending on the complexity of your use case, you can switch to a specific namespace and database using the `.Use()` method.
This is particularly useful if you want to switch to a different setup after connecting.

Learn more about the `.Use()` method [in the methods section](/docs/reference/dotnet/methods/use.md).

### Example usage

```csharp
await db.Use("main", "main");
```

---

Source: https://surrealdb.com/docs/reference/dotnet/core/data-manipulation

# Data manipulation

SurrealDB supports a number of methods for interacting with the database and performing CRUD operations.

SurrealDB supports a number of methods for interacting with the database and performing CRUD operations.

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="#select"> <code> db.Select() </code></a></td>
			<td scope="row" data-label="Description">Selects all records in a table, or a specific record, from the database</td>
		</tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#create"> <code> db.Create() </code></a></td>
            <td scope="row" data-label="Description">Creates a record in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#insert"> <code> db.Insert() </code></a></td>
            <td scope="row" data-label="Description">Inserts one or multiple records in the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="#insert_relation"> <code> db.InsertRelation() </code></a></td>
			<td scope="row" data-label="Description">Inserts one or multiple relations in the database</td>
		</tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#update"> <code> db.Update() </code></a></td>
            <td scope="row" data-label="Description">Updates all records in a table, or a specific record, in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#upsert"> <code> db.Upsert() </code></a></td>
            <td scope="row" data-label="Description">Creates or updates a set of records in a table, or a specific record, in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#merge"> <code> db.Merge() </code></a></td>
            <td scope="row" data-label="Description">Modifies all records in a table, or a specific record, in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#patch"> <code> db.Patch() </code></a></td>
            <td scope="row" data-label="Description">Applies JSON Patch changes to all records, or a specific record, in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#delete"> <code> db.Delete() </code></a></td>
            <td scope="row" data-label="Description">Deletes all records in a table, or a specific record, from the database</td>
        </tr>
	</tbody>
</table>

## `.Select<T>()` {#select}

Selects all records in a table, or a specific record, from the database.

```csharp title="Method Syntax"
await db.Select<T>(resource)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to select.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
// Select all records from a table
var people = await db.Select<Person>("person");

// Select a specific record from a table
var person = await db.Select<Person>(("person",
    "h5wxrf2ewk8xjxosxtyc"));
var person = await db.Select<Person>(new StringRecordId("person:h5wxrf2ewk8xjxosxtyc"));

// Select a specific record from a table, given a non-string id
var person = await db.Select<Person>(("person",
    new Guid("8424486b-85b3-4448-ac8d-5d51083391c7")));
```

<br />

## `.Create<T>()` {#create}

Creates a record in the database.

```csharp title="Method Syntax"
await db.Create<T>(resource, data)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
// Create a record with a random ID
var person = await db.Create<Person>("person");

// Create a record with a random ID & specific fields
var person = await db.Create("person", new Person { Name = "Tobie" });

// Create a record with a specific ID
var personToCreate = new Person
{
    Id = ("person", "tobie"),
    Name = "Tobie",
    Settings = new Settings
    {
        Active = true,
        Marketing = true,
    },
};
var result = await db.Create(personToCreate);
```

<br />

## `.Insert<T>()` {#insert}

Inserts one or multiple records in the database.

```csharp title="Method Syntax"
await db.Insert<T>(table, data)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>table</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Optionally pass along a table to insert into.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Either a single document/record or an array of documents/records to insert
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
var posts = new List<Post>
{
    new Post
    {
        Id = ("post", "First"),
        Title = "An article",
        Content = "This is the first article"
    },
    new Post
    {
        Id = ("post", "Second"),
        Title = "An article",
        Content = "This is the second article"
    }
};

await db.Insert("post", posts);
```

<br />

## `.InsertRelation<T>()` {#insert_relation}

Inserts one or multiple relations in the database.

```csharp title="Method Syntax"
await db.InsertRelation<T>(table, data)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>table</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Optionally pass along a table to insert into.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Either a single document/record or an array of documents/records to insert
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
await db.InsertRelation(
    new WroteRelation
    {
        In = ("user", "u1"),
        Out = ("post", "p1"),
        CreatedAt = now,
        NumberOfPages = 144
    }
);
```

<br />

## `.Update<T>()` {#update}

Updates all records in a table, or a specific record, in the database.

```csharp title="Method Syntax"
await db.Update<T>(thing, data)
```

> [!NOTE]
> This function replaces the current document / record data with the specified data.

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to update.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to update.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
var post = new Post
{
    Id = ("post", "another"),
    Title = "A new article",
    Content = "This is a new article created using the .NET SDK"
};

// Updates a single record
await db.Update(post);

var data = new Person
{
    Name = "Tobie",
    Settings = new Settings
    {
        Active = true,
        Marketing = true,
    },
};

// Updates all records inside the "person" table
await db.Update("person", data);
```

<br />

## `.Upsert<T>()` {#upsert}

Creates or updates a specific record.

```csharp title="Method Syntax"
await db.Upsert<T>(data)
```

> [!NOTE]
> This function creates a new document / record or replaces the current one with the specified data.

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
var person = new Person
{
        Id = ("person", "tobie"),
        // Id is mandatory to apply create or update
    Name = "Tobie",
    Settings = new Settings
    {
        Active = true,
        Marketing = true,
    },
};

// Create a new record when it doesn't exist
var created = await db.Upsert(person);

// Update an existing record when it does exist
var updated = await db.Upsert(person);
```

<br />

## `.Merge<T>()` {#merge}

Modifies all records in a table, or a specific record.

```csharp title="Method Syntax"
await db.Merge<T>(resource, data)
```

> [!NOTE]
> This function merges the current document / record data with the specified data.

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to merge.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The data with which to modify the records.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp title="Merging data within a single record"
// Only changes the fields specified in the merge object
var merge = new PersonMerge
{
    Id = ("person", "tobie"),
    Settings = new Settings
    {
        Active = true,
        Marketing = false,
    },
};
var result = await db.Merge<PersonMerge, Person>(merge);

// Only changes the fields specified in the Dictionary
var data = new Dictionary<string, object>
{
    { "tags", new List<string> { "developer", "engineer" } }
};

var result = await db.Merge<Person>(("person", "tobie"), data);
```

```csharp title="Merging data for every record in a table"
// Only changes the fields specified in the merge object
var merge = new PersonMerge
{
    Settings = new Settings
    {
        Active = true,
        Marketing = false,
    },
};
var result = await db.Merge<PersonMerge, Person>("person", merge);

// Only changes the fields specified in the Dictionary
var data = new Dictionary<string, object>
{
    { "tags", new List<string> { "developer", "engineer" } }
};

var result = await db.Merge<Person>("person", data);
```

<br />

## `.Patch<T>()` {#patch}

Applies JSON Patch changes to all records, or a specific record, in the database.

```csharp title="Method Syntax"
await db.Patch<T>(resource, data)
```

> [!NOTE]
> This function patches document / record data with the specified <a href="https://jsonpatch.com/">JSON Patch</a> data.

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to patch.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The JSON Patch data with which to patch the records.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
// Update a record with a specific ID
var result = await db.Patch(("person", "tobie"), patches);

// Update all records in a table
var result = await db.Patch("person", patches);
```

<br />

## `.Delete()` {#delete}

Deletes all records in a table, or a specific record, from the database.

```csharp title="Method Syntax"
await db.Delete(resource)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to delete.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
// Delete all records from a table
await db.Delete("person");

// Delete a specific record from a table
await db.Delete(("person", "h5wxrf2ewk8xjxosxtyc"));
```

---

Source: https://surrealdb.com/docs/reference/dotnet/core/dependency-injection

# Dependency injection

The SurrealDB SDK for .NET also supports the concept of Dependency Injection pattern.

The .NET SDK also supports Dependency Injection to ease the use of <code>SurrealDbClient</code> and <code>SurrealDbSession</code> in your application.

## Create a new project

Start by creating a new ASP.NET Core web app.

```sh
dotnet new webapp -o SurrealDbWeatherApi
cd SurrealDbWeatherApi
dotnet add package SurrealDb.Net
```

## Define a connection string

Open <code>appsettings.Development.json</code> and replace everything in there with the following code.
We have added a new Connection String called <code>SurrealDB</code> with the default configuration.

```json
{
  "AllowedHosts": "*",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "SurrealDB": "Server=http://127.0.0.1:8000;Namespace=test;Database=test;Username=root;Password=secret"
  }
}
```

## Register services

Open <code>Program.cs</code> and replace everything in there with the following code.
This code is using the <code>AddSurreal()</code> extension method to inject services automatically.
Configuring the SurrealDB client with the previously set Connection String takes one line of code.

> [!NOTE]
> By default, this function will register both <code>ISurrealDbSession</code> and <code>SurrealDbSession</code> using the <code>Scoped</code> service lifetime. This means that a new isolated SurrealDB session is created per scope.

```csharp
var builder = WebApplication.CreateBuilder(args);

var services = builder.Services;
var configuration = builder.Configuration;

services.AddControllers();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
services.AddSurreal(configuration.GetConnectionString("SurrealDB"));

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();

app.MapControllers();

app.Run();
```

> [!NOTE]
> In this example, we use a [Connection String](/docs/reference/dotnet/core/connection-strings.md) to configure services.
> This is the most convenient way to initialise the <code>SurrealDbClient</code> in your application.
> You can always choose to construct a Connection String manually via a <code>SurrealDbOptionsBuilder</code> and pass it to the <code>AddSurreal()</code> method.

## Consume the SurrealDB client/session

Open <code>WeatherForecastController.cs</code> and replace everything in there with the following code.
Finally, we can inject the <code>SurrealDbSession</code> inside our Controller.

```csharp
using Microsoft.AspNetCore.Mvc;

namespace SurrealDbWeatherApi.Controllers;

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private const string Table = "weatherForecast";

    private readonly SurrealDbSession _db;

    public WeatherForecastController(SurrealDbSession db)
    {
        _db = db;
    }

    [HttpGet]
    [Route("/")]
    public Task<List<WeatherForecast>> GetAll(CancellationToken cancellationToken)
    {
        return _db.Select<WeatherForecast>(Table, cancellationToken);
    }

    [HttpPost]
    [Route("/")]
    public Task<WeatherForecast> Create(CreateWeatherForecast data, CancellationToken cancellationToken)
    {
        var weatherForecast = new WeatherForecast
        {
            Date = data.Date,
            Country = data.Country,
            TemperatureC = data.TemperatureC,
            Summary = data.Summary
        };

        return _db.Create(Table, weatherForecast, cancellationToken);
    }

    // ...
    // Other methods omitted for brevity
}

public class CreateWeatherForecast
{
    public DateTime Date { get; set; }
    public string? Country { get; set; }
    public int TemperatureC { get; set; }
    public string? Summary { get; set; }
}
```

Then make sure your SurrealDB server is running on <code>127.0.0.1:8000</code> and run your app from the command line with:

```sh
dotnet run
```

---

Source: https://surrealdb.com/docs/reference/dotnet/core/logging

# Logging

In case you need to understand what your application is doing, the SurrealDB SDK has a built-in logging mechanism.

Logging helps you understand what is happening inside an application. The .NET SDK supports the built-in logging API offered by the `Microsoft.Extensions.Logging` NuGet package.

## Logging categories

The SurrealDB SDK for .NET has a set of logging categories so that you can pick what you want to display, using the respective `LogLevel`.

Example:

```json
{
  "Logging": {
    "LogLevel": {
      "SurrealDB.Connection": "Information",
      "SurrealDB.Method": "Information",
      "SurrealDB.Query": "Information",
      "SurrealDB.Serialization": "Debug"
    }
  }
}
```

This example will enable the following logging features:

- `Connection` - Logger category for messages related to connection operations.
- `Method` - Logger category for method execution, excluding `Connect` method.
- `Query` - Logger category for messages related to written or generated queries, that can be executed within `Query` or `RawQuery`.
- `Serialization` - Logger category for data serialisation and deserialisation, e.g. hexa CBOR format exchanged between the client and a SurrealDB instance.

> [!IMPORTANT]
> `Serialization` logs are only displayed when on `Debug` level to prevent data exchanges exposure. Please be sure to only enable this feature when you can guarantee that no sensitive data will be exposed.

> [!NOTE]
> `Serialization` logs display data in a CBOR format. You might need to find tools that parses CBOR data and displays it in a more human way.

## Sensitive data

To prevent data leakage, the property <code>SensitiveDataLoggingEnabled</code> of <code>SurrealDbLoggingOptions</code> is set to <code>true</code> by default.

When the feature is enabled, any data that is passed to a SurrealDB method is replaced by the placeholder value `?`. Example:

![Logging displayed from a console application](LoggingConsoleImg)

If needed, you can override this option using the <code>EnableSensitiveDataLogging</code> when building a new <code>SurrealDbOptions</code> instance.

```csharp
services.AddSurreal(
  SurrealDbOptions
    .Create()
    .FromConnectionString(configuration.GetConnectionString("SurrealDB")!)
    .EnableSensitiveDataLogging(false)
    .Build()
);
```

> [!IMPORTANT]
> Please be sure to only enable this feature when you can guarantee that no sensitive data will be exposed.

---

Source: https://surrealdb.com/docs/reference/dotnet/core/multiple-sessions

# Multiple sessions

The .NET SDK supports multiple isolated sessions within a single connection, each with their own authentication and context.

The .NET SDK allows you to create multiple isolated sessions within a single connection. Each session maintains its own namespace, database, variables, and authentication state, while sharing the underlying connection to SurrealDB. This is useful when different parts of your application need to operate under different credentials or contexts simultaneously.

<table>
  <thead>
    <tr>
      <th scope="col">Method</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Method">
        <a href="#sessions">
          <code> db.Sessions() </code>
        </a>
      </td>
      <td scope="row" data-label="Description">
        List all active sessions on the current connection.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="#createsession">
          <code> db.CreateSession() </code>
        </a>
      </td>
      <td scope="row" data-label="Description">
        Create a new isolated session on the current connection.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="#forksession">
          <code> session.ForkSession() </code>
        </a>
      </td>
      <td scope="row" data-label="Description">
        Creates a copy of a session, inheriting its state.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="#closesession">
          <code> session.CloseSession() </code>
        </a>
      </td>
      <td scope="row" data-label="Description">
        Closes the current session and disposes of it. After this method is
        called, the session cannot be used again.
      </td>
    </tr>
  </tbody>
</table>

## `.Sessions()` {#sessions}

Returns a list of all active sessions on the current connection.

```csharp title="Method Syntax"
await db.Sessions(cancellationToken)
```

### Arguments

<table>
  <thead>
    <tr>
      <th colspan="2" scope="col">
        Arguments
      </th>
      <th colspan="2" scope="col">
        Description
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="2" scope="row" data-label="Arguments">
        <code>cancellationToken</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        The cancellationToken enables graceful cancellation of asynchronous
        operations.
      </td>
    </tr>
  </tbody>
</table>

### Example usage

```csharp
// List all active sessions on the current connection
IEnumerable<Guid> sessions = await db.Sessions();
```

<br />

## `.CreateSession()` {#createsession}

Creates a new isolated session on the current connection. The new session is independent and maintains its own namespace, database, variables, and authentication state.

```csharp title="Method Syntax"
await db.CreateSession(cancellationToken)
```

### Arguments

<table>
  <thead>
    <tr>
      <th colspan="2" scope="col">
        Arguments
      </th>
      <th colspan="2" scope="col">
        Description
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="2" scope="row" data-label="Arguments">
        <code>cancellationToken</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        The cancellationToken enables graceful cancellation of asynchronous
        operations.
      </td>
    </tr>
  </tbody>
</table>

### Example usage

```csharp
// Create a new isolated session on the same connection
var session = await db.CreateSession();

// Use the session independently
await session.Use("my_namespace", "my_database");
await session.SignIn(new RootAuth { Username = "root",
    Password = "secret" });
```

<br />

## `.ForkSession()` {#forksession}

Creates a copy of the current session, inheriting its namespace, database, variables, and authentication state. Changes made in the forked session do not affect the original.

```csharp title="Method Syntax"
await session.ForkSession(cancellationToken)
```

### Arguments

<table>
  <thead>
    <tr>
      <th colspan="2" scope="col">
        Arguments
      </th>
      <th colspan="2" scope="col">
        Description
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="2" scope="row" data-label="Arguments">
        <code>cancellationToken</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        The cancellationToken enables graceful cancellation of asynchronous
        operations.
      </td>
    </tr>
  </tbody>
</table>

### Example usage

```csharp
// Fork the current session to create an independent copy
var forkedSession = await session.ForkSession();

// The forked session inherits namespace, database, and auth state
// Changes made here do not affect the original session
await forkedSession.Set("x", 42);
```

<br />

## `.CloseSession()` {#closesession}

Closes the current session and disposes of it. After this method is called, the session cannot be used again.

> [!NOTE]
> `CloseSession()` only closes the session itself. The underlying connection shared with the parent `SurrealDbClient` remains open.

```csharp title="Method Syntax"
await session.CloseSession(cancellationToken)
```

### Arguments

<table>
  <thead>
    <tr>
      <th colspan="2" scope="col">
        Arguments
      </th>
      <th colspan="2" scope="col">
        Description
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="2" scope="row" data-label="Arguments">
        <code>cancellationToken</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        The cancellationToken enables graceful cancellation of asynchronous
        operations.
      </td>
    </tr>
  </tbody>
</table>

### Example usage

```csharp
// Close and dispose of the session when done
await session.CloseSession();

// The session can no longer be used after this call
```

---

Source: https://surrealdb.com/docs/reference/dotnet/core/parameters

# Set parameters

In this section, you will learn how to set parameters in the .NET SDK for SurrealDB.

Within your application, you can define parameters that can be used to store and retrieve data from SurrealDB.
Parameters are used to store data in a structured format, and can be used to store data in a key-value pair format.

>[!IMPORTANT]
> Parameters allow you to define global (database-wide) parameters that are available to every client.

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="#set"> <code> db.Set(key, value) </code></a></td>
			<td scope="row" data-label="Description">Assigns a value as a parameter for this connection</td>
		</tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#unset"> <code> db.Unset(key) </code></a></td>
            <td scope="row" data-label="Description">Removes a parameter for this connection</td>
        </tr>
	</tbody>
</table>

## `.Set()` {#set}

Assigns a value as a parameter for this connection.

```csharp title="Method Syntax"
await db.Set(key, val)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>key</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the name of the variable.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>value</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Assigns the value to the variable name.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
// Assign the variable on the connection
await db.Set("name", new Name { FirstName = "Tobie",
    LastName = "Morgan Hitchcock" });

// Use the variable in a subsequent query
await db.Query($"CREATE person SET name = $name");

// Use the variable in a subsequent query
await db.Query($"SELECT * FROM person WHERE name.first_name = $name.first_name");
```

<br />

## `.Unset()` {#unset}

Removes a parameter for this connection.

```csharp title="Method Syntax"
await db.Unset(key)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>key</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
await db.Unset("name");
```

---

Source: https://surrealdb.com/docs/reference/dotnet/core/streaming

# Realtime data streaming

The SurrealDB SDK for .NET allows you to create live queries that listen for changes in the database and automatically update your application when changes occur.

You can use the SurrealDB SDK to create live queries that listen for changes in the database and automatically update your application when changes occur.
This feature is useful for building real-time applications that need to respond to changes in the database.

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="#listen-live"> <code> db.ListenLive(queryUuid) </code></a></td>
			<td scope="row" data-label="Description">Listen responses from an existing live query</td>
		</tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#live-query"> <code> db.LiveQuery(sql) </code></a></td>
            <td scope="row" data-label="Description">Initiate a live query from a SurrealQL statement</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#live-raw-query"> <code> db.LiveRawQuery(sql, params) </code></a></td>
			<td scope="row" data-label="Description">Initiate a live query from a SurrealQL statement, based on a raw SurrealQL query</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="#live-table"> <code> db.LiveTable(table, diff) </code></a></td>
			<td scope="row" data-label="Description">Initiate a live query from a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="#kill"> <code> db.Kill(queryUuid) </code></a></td>
			<td scope="row" data-label="Description">Kills a running live query by it's UUID</td>
		</tr>
	</tbody>
</table>

## `.ListenLive<T>()` {#listen-live}

Listens to responses from an existing live query.

```csharp title="Method Syntax"
db.ListenLive<T>(queryUuid)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>queryUuid</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The UUID of the live query to consume.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
await using var liveQuery = db.ListenLive<Person>(queryUuid);

// Consume the live query...
```

You can then consume the live query using either an `IAsyncEnumerable` or an `Observable`.

#### Using an `IAsyncEnumerable`

:::note
___NOTE:___ This will block the current thread until the query is killed.
:::

```csharp title="Option 1: Consume the live query via an IAsyncEnumerable"
await foreach (var response in liveQuery)
{
    // Either an Open, Create, Update, Delete or Close notification...

    if (response is SurrealDbLiveQueryOpenResponse)
    {
        // Do something...
    }
    if (response is SurrealDbLiveQueryCreateResponse<Person> create)
    {
        // Use the `Result` record
    }
    if (response is SurrealDbLiveQueryUpdateResponse<Person> update)
    {
        // Use the `Result` record
    }
    if (response is SurrealDbLiveQueryDeleteResponse<Person> delete)
    {
        // Use the `Result` record
    }
    if (response is SurrealDbLiveQueryCloseResponse)
    {
        // Do something...
    }
}
```

#### Using an `Observable`

```csharp title="Option 2: Consume the live query via an Observable"
liveQuery
    .ToObservable()
    .Subscribe((response) => 
    {
        // Either an Open, Create, Update, Delete or Close notification...

        if (response is SurrealDbLiveQueryOpenResponse)
        {
            // Do something...
        }
        if (response is SurrealDbLiveQueryCreateResponse<Person> create)
        {
            // Use the `Result` record
        }
        if (response is SurrealDbLiveQueryUpdateResponse<Person> update)
        {
            // Use the `Result` record
        }
        if (response is SurrealDbLiveQueryDeleteResponse<Person> delete)
        {
            // Use the `Result` record
        }
        if (response is SurrealDbLiveQueryCloseResponse)
        {
            // Do something...
        }
    });
```

You can also use the `OfType` operator to filter the responses.

```csharp
liveQuery
    .ToObservable()
    .OfType<SurrealDbLiveQueryCreateResponse<Person>>()
    .Select(response => response.Result)
    .Subscribe((record) => 
    {
        // Use the created record
    });
```

Note that this pattern is already simplified via methods available on the `SurrealDbLiveQuery` object.
You can learn more about these methods in the [LiveQuery methods](#surrealdblivequery-methods) section.

<br />

## `.LiveQuery<T>()` {#live-query}

Initiate a live query from a SurrealQL statement.

```csharp title="Method Syntax"
await db.LiveQuery<T>(sql)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
const string table = "person"; 
await using var liveQuery = await db.LiveQuery<Person>($"LIVE SELECT * FROM type::table({table});");

// Consume the live query...
```

### `SurrealDbLiveQuery` methods

The `SurrealDbLiveQuery` object provides the following methods:

#### `GetResults()` {#get-results}

Returns an enumerator that iterates asynchronously through the collection of results
(all actions `CREATE`, `UPDATE` and `DELETE`, except `OPEN` and `CLOSE`).

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
await using var liveQuery = await db.LiveRawQuery<Person>("LIVE SELECT * FROM person;");

// highlight-next-line
await foreach (var response in liveQuery.GetResults())
{
    // Either a Create, Update or Delete notification...

    if (response is SurrealDbLiveQueryCreateResponse<Person> create)
    {
        // Use the `Result` record
    }
    if (response is SurrealDbLiveQueryUpdateResponse<Person> update)
    {
        // Use the `Result` record
    }
    if (response is SurrealDbLiveQueryDeleteResponse<Person> delete)
    {
        // Use the `Result` record
    }
}
```

#### `GetCreatedRecords()` {#get-created-records}

Returns an enumerator that iterates asynchronously through the collection of created records.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
await using var liveQuery = await db.LiveRawQuery<Person>("LIVE SELECT * FROM person;");

// highlight-next-line
await foreach (var record in liveQuery.GetCreatedRecords())
{
    // Use the created record
}
```

#### `GetUpdatedRecords()` {#get-updated-records}

Returns an enumerator that iterates asynchronously through the collection of updated records.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
await using var liveQuery = await db.LiveRawQuery<Person>("LIVE SELECT * FROM person;");

// highlight-next-line
await foreach (var record in liveQuery.GetUpdatedRecords())
{
    // Use the updated record
}
```

#### `GetDeletedRecords()` {#get-deleted-records}

Returns an enumerator that iterates asynchronously through the collection of deleted records.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
await using var liveQuery = await db.LiveRawQuery<Person>("LIVE SELECT * FROM person;");

// highlight-next-line
await foreach (var record in liveQuery.GetDeletedRecords())
{
    // Use the deleted record
}
```

#### `KillAsync()` {#kill-async}

Kills the underlying live query.

```csharp title="Method Syntax"
await liveQuery.KillAsync(cancellationToken)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
var liveQuery = await db.LiveRawQuery<Person>("LIVE SELECT * FROM person;");

// Consume the live query...

// Manually kill the live query
// highlight-next-line
await liveQuery.KillAsync();
```

<br />

## `.LiveRawQuery<T>()` {#live-raw-query}

Initiate a live query from a SurrealQL statement, based on a raw SurrealQL query.

```csharp title="Method Syntax"
await db.LiveRawQuery<T>(sql, params)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>params</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```csharp
await using var liveQuery = await db.LiveRawQuery<Person>("LIVE SELECT * FROM person;");

// Consume the live query...
```

<br />

## `.LiveTable<T>()` {#live-table}

Initiate a live query from a table.

```csharp title="Method Syntax"
await db.LiveTable<T>(table, diff)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>table</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The table name to listen for changes for.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>diff</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                If set to true, live notifications will include an array of JSON Patch objects, rather than the entire record for each notification.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```csharp
await using var liveQuery = await db.LiveTable<Person>("person");

// Consume the live query...
```

<br />

## `.Kill()` {#kill}

Kills a running live query by its UUID.

```csharp title="Method Syntax"
await db.Kill(queryUuid)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>queryUuid</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The UUID of the live query you wish to kill.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```csharp
await db.Kill(queryUuid);
```

<br />

## Live actions

A live query event can be one of the following:

<table>
    <thead>
        <tr>
            <th colspan="1" scope="col">Action</th>
            <th colspan="1" scope="col">Result</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="1" scope="row" data-label="Action">
                `OPEN`
            </td>
            <td colspan="1" scope="row" data-label="Result">
                N/A
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Emitted when the live query is opened in the server.
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Action">
                `CLOSE`
            </td>
            <td colspan="1" scope="row" data-label="Result">
                `SocketClosed` or `QueryKilled`
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Emitted when the live query is closed due to it either being killed or the connection being disconnected.
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Action">
                `CREATE`
            </td>
            <td colspan="1" scope="row" data-label="Result">
                `Result`
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Emitted when a record within your subscription gets created
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Action">
                `UPDATE`
            </td>
            <td colspan="1" scope="row" data-label="Result">
                `Result`
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Emitted when a record within your subscription gets updated
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row" data-label="Action">
                `CREATE`
            </td>
            <td colspan="1" scope="row" data-label="Result">
                `Result`
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Emitted when a record within your subscription gets deleted
            </td>
        </tr>
    </tbody>
</table>

---

Source: https://surrealdb.com/docs/reference/dotnet/core/transactions

# Transactions

The .NET SDK supports atomic transactions for executing multiple queries that succeed or fail together.

Transactions allow you to execute a group of queries atomically, meaning either all changes are applied or none are. This is essential for maintaining data consistency when performing related operations that must not be partially applied.

<table>
  <thead>
    <tr>
      <th scope="col">Method</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Method">
        <a href="#begintransaction">
          <code> session.BeginTransaction() </code>
        </a>
      </td>
      <td scope="row" data-label="Description">
        Create a new transaction scoped to the current session.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="#commit">
          <code> txn.Commit() </code>
        </a>
      </td>
      <td scope="row" data-label="Description">
        Commit the transaction to the database, applying all changes made within
        the transaction scope.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="#cancel">
          <code> txn.Cancel() </code>
        </a>
      </td>
      <td scope="row" data-label="Description">
        Cancel and discard all changes made in the transaction.
      </td>
    </tr>
  </tbody>
</table>

## `.BeginTransaction()` {#begintransaction}

Creates a new transaction scoped to the current session. Transactions allow you to execute multiple queries atomically.

```csharp title="Method Syntax"
await session.BeginTransaction(cancellationToken)
```

### Arguments

<table>
  <thead>
    <tr>
      <th colspan="2" scope="col">
        Arguments
      </th>
      <th colspan="2" scope="col">
        Description
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="2" scope="row" data-label="Arguments">
        <code>cancellationToken</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        The cancellationToken enables graceful cancellation of asynchronous
        operations.
      </td>
    </tr>
  </tbody>
</table>

### Example usage

```csharp
// Begin a new transaction on the current session
await using var txn = await db.BeginTransaction();

// Execute queries within the transaction
await txn.Create("person", new { Name = "John" });
await txn.Create("person", new { Name = "Jane" });

// Commit all changes atomically
await txn.Commit();
```

<br />

## `.Commit()` {#commit}

Commits the transaction to the database, applying all changes made within the transaction scope.

> [!NOTE]
> After committing, the transaction cannot be used again.

```csharp title="Method Syntax"
await txn.Commit(cancellationToken)
```

### Arguments

<table>
  <thead>
    <tr>
      <th colspan="2" scope="col">
        Arguments
      </th>
      <th colspan="2" scope="col">
        Description
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="2" scope="row" data-label="Arguments">
        <code>cancellationToken</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        The cancellationToken enables graceful cancellation of asynchronous
        operations.
      </td>
    </tr>
  </tbody>
</table>

### Example usage

```csharp
await using var txn = await db.BeginTransaction();

await txn.Create("order", new { Total = 99.99 });
await txn.Create("invoice", new { OrderId = "order:1" });

// Commit all changes - either both succeed or neither does
await txn.Commit();
```

<br />

## `.Cancel()` {#cancel}

Cancels and discards all changes made in the transaction.

> [!NOTE]
> After canceling, the transaction cannot be used again.

```csharp title="Method Syntax"
await txn.Cancel(cancellationToken)
```

### Arguments

<table>
  <thead>
    <tr>
      <th colspan="2" scope="col">
        Arguments
      </th>
      <th colspan="2" scope="col">
        Description
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="2" scope="row" data-label="Arguments">
        <code>cancellationToken</code>
        <label label="optional" />
      </td>
      <td colspan="2" scope="row" data-label="Description">
        The cancellationToken enables graceful cancellation of asynchronous
        operations.
      </td>
    </tr>
  </tbody>
</table>

### Example usage

```csharp
await using var txn = await db.BeginTransaction();

try
{
    await txn.Create("order", new { Total = 99.99 });

    // ... more operations

    await txn.Commit();
}
catch
{
    // Discard all changes on error
    await txn.Cancel();
}
```

---

Source: https://surrealdb.com/docs/reference/dotnet/core/writing-surrealql

# Run SurrealQL queries

SurrealDB supports a number of methods for interacting with the database and performing CRUD operations.

The methods below are used to interact with the database and perform CRUD operations. You can also use the `query` method to run [SurrealQL statements](/docs/reference/query-language/statements/overview.md) against the database.

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="#query"> <code> db.Query() </code></a></td>
			<td scope="row" data-label="Description">Runs a set of SurrealQL statements against the database</td>
		</tr>
        <tr>
            <td scope="row" data-label="Method"><a href="#raw_query"> <code> db.RawQuery() </code></a></td>
            <td scope="row" data-label="Description">Runs a set of SurrealQL statements against the database, based on a raw SurrealQL query</td>
        </tr>
	</tbody>
</table>

## `.Query()` {#query}

Runs a set of SurrealQL statements against the database.

```csharp title="Method Syntax"
await db.Query(sql)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
// Execute query with params
const string table = "person";
var result = await db.Query($"CREATE person; SELECT * FROM type::table({table});");

// Get the first result from the first query
var created = result.GetValue<Person>(0);

// Get all of the results from the second query
var people = result.GetValue<List<Person>>(1);
```

<br />

## `.RawQuery()` {#raw_query}

Runs a set of SurrealQL statements against the database, based on a raw SurrealQL query.

```csharp title="Method Syntax"
await db.RawQuery(sql, params)
```

### Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>params</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```csharp
// Assign the variable on the connection
var @params = new Dictionary<string, object> { { "table",
    "person" } };
var result = await db.RawQuery("CREATE person; SELECT * FROM type::table($table);", @params);

// Get the first result from the first query
var created = result.GetValue<Person>(0);

// Get all of the results from the second query
var people = result.GetValue<List<Person>>(1);
```

<br />

---

Source: https://surrealdb.com/docs/reference/dotnet/data-types

# Data types

The .NET SDK translates all datatypes native to SurrealQL into either datatypes native to .NET, or a custom implementation. This document describes all datatypes, and links to their respective documentation.

The .NET SDK translates datatypes native to SurrealQL into either datatypes native to .NET, or a custom implementation.
This document describes all datatypes, and links to their respective documentation.

## Data types overview

<table>
  <thead>
    <tr>
      <th colspan="1" scope="col">
        SurrealQL type
      </th>
      <th colspan="1" scope="col">
        Kind
      </th>
      <th colspan="2" scope="col">
        Documentation
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="1" scope="row">
        <code>string</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/en-us/dotnet/api/system.string">
          <code>String</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>int</code>, <code>float</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <span>Any number type, e.g. </span>
        <a href="https://learn.microsoft.com/en-us/dotnet/api/system.int32">
          <code>Int32</code>
        </a>
        <span>,</span>
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.single">
          <code>Single</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>bool</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/en-us/dotnet/api/system.boolean">
          <code>Boolean</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>null</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/null">
          <code>null</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>none</code>
      </td>
      <td colspan="1" scope="row">
        Custom
      </td>
      <td colspan="2" scope="row">
        <a href="/docs/reference/dotnet/data-types.md#none">
          <code>None</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>array</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        Any <code>IEnumerable</code> representation
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>object</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        Any <code>Object</code> representation
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>set</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        Any <code>HashSet</code> representation
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>datetime</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.datetime">
          <code>DateTime</code>
        </a>
        <span>or</span>
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.dateonly">
          <code>DateOnly</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>bytes</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <code>byte[]</code>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>uuid</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.guid">
          <code>Guid</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>duration</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.timespan">
          <code>TimeSpan</code>
        </a>
        <span>or</span>
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.timeonly">
          <code>TimeOnly</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>decimal</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.decimal">
          <code>Decimal</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>geometry</code>
      </td>
      <td colspan="1" scope="row">
        via
        [Microsoft.Spatial](https://www.nuget.org/packages/Microsoft.Spatial)
      </td>
      <td colspan="2" scope="row">
        <code>Geometry</code> or <code>Geography</code> representations
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>range</code>
      </td>
      <td colspan="1" scope="row">
        Custom
      </td>
      <td colspan="2" scope="row">
        <a href="/docs/reference/dotnet/data-types.md#range">
          <code>Range</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>file</code>
      </td>
      <td colspan="1" scope="row">
        Custom
      </td>
      <td colspan="2" scope="row">
        <a href="/docs/reference/dotnet/data-types.md#surrealfile">
          <code>SurrealFile</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>record</code>
      </td>
      <td colspan="1" scope="row">
        Custom
      </td>
      <td colspan="2" scope="row">
        <a href="/docs/reference/dotnet/data-types.md#recordid">
          <code>RecordId</code>
        </a>
      </td>
    </tr>
  </tbody>
</table>

<br />
<br />

## `None`

The `None` type is a custom type that represents the absence of a value.

```csharp title="Signature"
public readonly struct None { }
```

### Working with `None`

```csharp title="Constructing"
var none = new None();

// Change the value of a record to None
var myRecord = new MyRecord();
myRecord.Value = new None();
```

<br />

## `Range`

A `Range` represents a bounded or unbounded range of values. Ranges are used in SurrealQL for selecting slices of records by ID or filtering numeric and temporal values.

```csharp title="Signature"
public readonly struct Range<TStart, TEnd>
{
    public RangeBound<TStart>? Start { get; }

    public RangeBound<TEnd>? End { get; }
}
```

A `RecordIdRange` is a specialization for querying a range of records from a table.

```csharp title="Signature"
public readonly struct RecordIdRange<TStart, TEnd>
{
    public string Table { get; }

    public Range<TStart, TEnd> Range { get; }
}
```

### Working with `Range`

```csharp title="Constructing"
var fullRange = Range.Full(); // Full range (no exclusion)

var startRange = Range.StartFrom<int>(new(1, RangeBoundType.Inclusive)); // Equivalent to 1..

var endRange = Range.EndTo<string>(new("x", RangeBoundType.Exclusive)); // Equivalent to .."x"

var explicitRange = Range.FromRange(2..10); // From C# Range
```

<br />

## `SurrealFile`

A `SurrealFile` represents a reference to a file stored in SurrealDB.

```csharp title="Signature"
public readonly struct SurrealFile
{
    public string Bucket { get; }
    public string Path { get; } = "/";
}
```

### Working with `SurrealFile`

```csharp title="Constructing"
var ref = new SurrealFile("bucket", "/some/key/to/a/file.txt");
```

File references are returned when working with [file uploads](/docs/reference/query-language/language-primitives/data-types/files.md) and contain metadata about the stored file.

```csharp
var user = await db.Select(("user", "john"));

Console.WriteLine(user.Avatar.Bucket);
Console.WriteLine(user.Avatar.Path);
```

<br />

## `RecordId`

When you receive a RecordId back from SurrealDB, it will always be represented as a `RecordId`.
The class holds `Table` and `Id` fields, representing the table name, and a unique identifier for the record on that table.

```csharp title="Signature"
public class RecordId
{
    public string Table { get; }

    public T DeserializeId<T>();

    // ... The rest is omitted for brevity
}
```

The `RecordId` is a non-generic class, allowing you to extract the `Id` field by providing the output type via the `DeserializeId` method.
This can be helpful when the `RecordId` is used in a generic context, for when you store the `Id` as an Object or an Array for example.

For cases where you are aware of the type of the `Id` field, you can use the generic version of `RecordId` to avoid the need for manual deserialisation.

```csharp title="Signature with generics"
public class RecordIdOf<T> : RecordId
{
    public T Id { get; }
}
```

Because the default type of an `Id` in SurrealDB is `string`, you can choose to use the default provided type `RecordIdOfString`.

```csharp title="Default RecordId"
public class RecordIdOfString : RecordIdOf<string>
{
    // The available properties, inherited from `RecordId` and `RecordIdOf<string>`
    public string Table { get; }
    public string Id { get; }
}
```

### Working with `RecordId`

The simplest and most common way to construct a `RecordId` is with a tuple `(table, id)`.

```csharp title="Constructing"
// Table is "person"
// Unique identifier on the table is "john"
RecordId personId = ("person", "john");
// or
var personId = (RecordId)("person", "john");
```

This tuple is implicitly converted into a `RecordId` object.
You can use it with all SDK methods:

```csharp title="Using RecordId"
await db.Select<Person>(("person", "john"));
await db.Delete(("person", "john"));
```

You are not exclusively limited to the `string` type for the `Id` part. Several overloads exist for different `id` types:

```csharp title="Constructing"
RecordId rid1 = ("person", "alice");           // string
RecordId rid2 = ("person", 123);               // int
RecordId rid3 = ("person", 123L);              // long
RecordId rid4 = ("person", (short)5);          // short
RecordId rid5 = ("person", (byte)7);           // byte
RecordId rid6 = ("person", Guid.NewGuid());    // Guid
```

### Extracting data

The .NET SDK handles serialisation and deserialisation of the `Table` and `Id` parts in Record Id.
The serialisation is done automatically when sending data to the server.
However, deserialisation may need to be done manually according to the data type of the `Id` field.
Below are some examples:

```csharp title="Simple record id"
RecordId rid = ("person", "john");
string table = rid.Table; // "person"
string id = rid.DeserializeId<string>(); // "john"
```

```csharp title="Record id with simple data type (other than string)"
RecordId rid = ("table", 42);
string table = rid.Table; // "table"
int id = rid.DeserializeId<int>(); // 42
```

```csharp title="Record id with complex data types"
var rid = new RecordIdOf<CityId>("table", new CityId { City = "London" });
var id = rid.DeserializeId<CityId>(); // CityId { City = "London" }

var rid = new RecordIdOf<(string, int)>("table", ("London", 42));
var id = rid.DeserializeId<(string, int)>(); // ("London", 42)
```

### Send back string

If you need to send back a Record Id in string format, you can do so with the `StringRecordId` class.

We do not implement the parsing of Record Ids in the .NET SDK, as that would mean that we need to be able to parse any SurrealQL value, which comes with a cost.
Instead you can send it over as a string with `StringRecordId`, allowing the server to handle the parsing.

```csharp title="Signature"
public class StringRecordId
{
    public string Value { get; }
}
```

### Working with a `StringRecordId`

```csharp title="Constructing"
// Table is "person"
// Unique identifier on the table is "john"
var rid = new StringRecordId("person:john");

// Alternatively, a StringRecordId can be inferred explicitly from a string
var rid = (StringRecordId)"person:john";
await client.Select<Person>((StringRecordId)"person:john");
```

### Working with `RecordIdOfString`

For string-based identifiers, you can also use the specialised type RecordIdOfString:

```csharp title="Using RecordIdOfString"
var rid = new RecordIdOfString("person", "john");
// or
Console.WriteLine(rid.Table); // "person"
Console.WriteLine(rid.Id);    // "john"
```

### Working with `RecordIdOf<T>`

For complex or structured identifiers, use the generic type RecordIdOf\<T\>:

```csharp title="Using RecordIdOf<T>"
public class CityId
{
    public string City { get; set; } = string.Empty;
}

var rid = new RecordIdOf<CityId>("city", new CityId { City = "London" });
```

This enables strongly-typed IDs that map directly to your domain objects.

### Inheriting from record

If your model class inherits from Record, it will automatically include an Id property of type `RecordId`.

```csharp title="Inheriting from Record"
public class Person : Record
{
    public string Name { get; set; } = string.Empty;
}

// Example usage
var person = new Person { Name = "Alice" };
Console.WriteLine(person.Id); // RecordId ("person", "…")
```

### Using data annotations

The SDK supports attributes for serialisation and deserialisation.

#### CBOR serialisation

Use [CborProperty](https://github.com/dahomey-technologies/Dahomey.Cbor) to map C# properties to SurrealDB fields:

```csharp title="Using CborProperty"
[CborProperty("first_name")]
public string FirstName { get; set; } = string.Empty;
```

#### RecordIdJsonConverter

Use RecordIdJsonConverter to indicate that a property should be serialised as a RecordId reference to another table:

```csharp title="Using RecordIdJsonConverter"
[RecordIdJsonConverter("payment_details")]
public RecordId? PaymentDetails { get; set; }

[RecordIdJsonConverter("payment_details")]
public RecordId? PaymentDetails { get; set; }
```

#### Combining attributes

You can combine both attributes on the same property:

```csharp title="Combining attributes"
[CborProperty("payment_details")]
[RecordIdJsonConverter("payment_details")]
public RecordId? PaymentDetails { get; set; }
```

---

Source: https://surrealdb.com/docs/reference/dotnet/embedding

# Embedding

The documentation for embedding SurrealDB within .NET.

SurrealDB is designed to be run in many different ways and in many environments.
Due to the [separation of the storage and compute](/docs/learn/data-models/architecture.md) layers, SurrealDB can be run in embedded mode, from within a number of different language environments.
In .NET, SurrealDB can be run as an [in-memory database](#memory-provider), or it can persist data using a [file-based storage engine](#file-providers).

## Memory provider

The memory provider is a simple in-memory database that is useful in some contexts.
It is useful for testing scenarios, or for small applications that do not require persistence.

```bash
dotnet add package SurrealDb.Embedded.InMemory
```

### Consume the provider as is

The simplest way to use an in-memory database instance of SurrealDB is to create an instance of the `SurrealDbMemoryClient` class.

```csharp
// highlight-next-line
using var db = new SurrealDbMemoryClient();

const string TABLE = "person";

var person = new Person
{
    Title = "Founder & CEO",
    Name = new() { FirstName = "Tobie", LastName = "Morgan Hitchcock" },
    Marketing = true
};
var created = await db.Create(TABLE, person);
Console.WriteLine(ToJsonString(created));
```

### Consume the provider via dependency injection

Following the .NET Dependency Injection pattern, you can register the in-memory provider using the `AddInMemoryProvider` extension method.
This will allow the `SurrealDbClient` to resolve the `mem://` endpoint.

```csharp
var builder = WebApplication.CreateBuilder(args);

var services = builder.Services;
var configuration = builder.Configuration;

// highlight-start
services
  .AddSurreal("Endpoint=mem://")
  .AddInMemoryProvider();
// highlight-end
```

Learn more about [Dependency Injection with SurrealDB in .NET](/docs/reference/dotnet/core/dependency-injection.md) in the SDK documentation.

Once the memory provider is configured, you can use the .NET SDK the same way you would with a remote database.
Please refer to the [.NET client SDK](/docs/reference/dotnet.md) documentation to get started with SurrealDB for .NET.

## File providers

The file provider is a more advanced storage engine that can be used to persist data to disk.

**RocksDB**

```bash
dotnet add package SurrealDb.Embedded.RocksDb
```

**SurrealKV**

```bash
dotnet add package SurrealDb.Embedded.SurrealKv
```

### Consume the provider as is

**RocksDB**

The simplest way to use a file-backed database instance of SurrealDB is to create an instance of the `SurrealDbRocksDbClient` class.
Note that the `path` to the storage is mandatory.

```csharp
// highlight-next-line
using var db = new SurrealDbRocksDbClient("data.db");

const string TABLE = "person";

var person = new Person
{
    Title = "Founder & CEO",
    Name = new() { FirstName = "Tobie", LastName = "Morgan Hitchcock" },
    Marketing = true
};
var created = await db.Create(TABLE, person);
Console.WriteLine(ToJsonString(created));
```

**SurrealKV**

The simplest way to use a file-backed database instance of SurrealDB is to create an instance of the `SurrealDbKvClient` class.
Note that the `path` to the storage is mandatory.

```csharp
// highlight-next-line
using var db = new SurrealDbKvClient("data.db");

const string TABLE = "person";

var person = new Person
{
    Title = "Founder & CEO",
    Name = new() { FirstName = "Tobie", LastName = "Morgan Hitchcock" },
    Marketing = true
};
var created = await db.Create(TABLE, person);
Console.WriteLine(ToJsonString(created));
```

### Consume the provider via dependency injection

**RocksDB**

Following the .NET Dependency Injection pattern, you can register the file provider using the `AddRocksDbProvider` extension method.
This will allow the `SurrealDbClient` to resolve the `rocksdb://` endpoint.

```csharp
var builder = WebApplication.CreateBuilder(args);

var services = builder.Services;
var configuration = builder.Configuration;

// highlight-start
services
  .AddSurreal("Endpoint=rocksdb://data.db")
  .AddRocksDbProvider();
// highlight-end
```

**SurrealKV**

Following the .NET Dependency Injection pattern, you can register the file provider using the `AddSurrealKvProvider` extension method.
This will allow the `SurrealDbClient` to resolve the `surrealkv://` endpoint.

```csharp
var builder = WebApplication.CreateBuilder(args);

var services = builder.Services;
var configuration = builder.Configuration;

// highlight-start
services
  .AddSurreal("Endpoint=surrealkv://data.db")
  .AddSurrealKvProvider();
// highlight-end
```

Learn more about [Dependency Injection with SurrealDB in .NET](/docs/reference/dotnet/core/dependency-injection.md) in the SDK documentation.

### Next step

Once the file provider is configured, you can use the .NET SDK the same way you would with a remote database.
Please refer to the [.NET client SDK](/docs/reference/dotnet.md) documentation to get started.

---

Source: https://surrealdb.com/docs/reference/dotnet/installation

# Installation

In this section, you will learn how to install the .NET SDK in your project.

Before you can use this SDK in your .NET applications regardless of your environment, you need to install and import it into your project.
The steps below show how to install and import it.

## Install the SDK

- Create a new project using your favorite IDE (Visual Studio, JetBrains Rider, etc...)
- or use an existing template from the <code>dotnet new</code> command.

Once ready, add the SurrealDB SDK to your dependencies.

**.NET CLI**

```bash
dotnet add package SurrealDb.Net
```

**PackageReference**

```xml
<PackageReference Include="SurrealDb.Net" Version="1.0.0" />
```

<br />

Alternatively, you can install the SDK via the NuGet user interface provided in your IDE.
Here is an example within Visual Studio:

<img src="~/assets/img/dotnet-nuget-search.png" alt="Visual Studio NuGet Package Manager" />

## Initialise the SDK

The SDK's initialisation may vary depending on the context of your project.

The de facto initialisation method is to create and [consume a SurrealDbClient created manually](/docs/reference/dotnet/core/create-a-new-connection.md).
Most .NET projects provide a way to configure services using [Dependency Injection](/docs/reference/dotnet/core/dependency-injection.md), which is the recommended way to use the SDK in your application.

---

Source: https://surrealdb.com/docs/reference/dotnet/methods

# SDK methods

The .NET SDK for SurrealDB has a single SurrealDB class that provides methods for querying a remote SurrealDB database.

The .NET SDK for SurrealDB has a single SurrealDB class that provides methods for querying a remote SurrealDB database.
The class is designed to be simple to use and easy to understand for developers who are new to .NET or SurrealDB.
This page lists out the methods that are available in the SurrealDB class.

## Initialisation methods

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/connect.md"> <code> db.Connect() </code></a></td>
			<td scope="row" data-label="Description">Connects the client to the underlying endpoint, also improving performance to avoid cold starts</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/use.md"> <code> db.Use(namespace, database)</code></a></td>
			<td scope="row" data-label="Description">Switch to a specific namespace and database</td>
		</tr>
		<tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/set.md"> <code>db.Set(key, value)</code></a></td>
            <td scope="row" data-label="Description">Assigns a value as a parameter for this connection</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/unset.md"> <code>db.Unset(key)</code></a></td>
            <td scope="row" data-label="Description">Removes a parameter for this connection</td>
        </tr>
			<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/health.md"> <code> db.Health() </code></a></td>
			<td scope="row" data-label="Description">Checks the status of the database server and storage engine</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/version.md"> <code> db.Version() </code></a></td>
			<td scope="row" data-label="Description">Retrieves the version of the SurrealDB instance</td>
		</tr>
</tbody>
</table>

## Query methods

<table>
    <thead>
        <tr>
            <th scope="col">Method</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/query.md"> <code>db.Query&lt;T&gt;(sql)</code></a></td>
            <td scope="row" data-label="Description">Runs a set of [SurrealQL statements](/docs/reference/query-language.md) against the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/raw-query.md"> <code>db.RawQuery&lt;T&gt;(sql, vars)</code></a></td>
            <td scope="row" data-label="Description">Runs a set of [SurrealQL statements](/docs/reference/query-language.md) against the database, based on a raw SurrealQL query</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/select.md"> <code>db.Select&lt;T&gt;(thing)</code></a></td>
            <td scope="row" data-label="Description">Selects all records in a table, or a specific record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/core/streaming.md#live-query"> <code>db.LiveQuery&lt;T&gt;(sql)</code></a></td>
            <td scope="row" data-label="Description">Initiate a live query from a [SurrealQL statement](/docs/reference/query-language.md)</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/core/streaming.md#live-raw-query"> <code>db.LiveRawQuery&lt;T&gt;(sql)</code></a></td>
            <td scope="row" data-label="Description">Initiate a live query from a [SurrealQL statement](/docs/reference/query-language.md), based on a raw SurrealQL query</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/core/streaming.md#live-table"> <code>db.LiveTable&lt;T&gt;(table, diff)</code></a></td>
            <td scope="row" data-label="Description">Initiate a live query from a table</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/core/streaming.md#listen-live"> <code>db.ListenLive&lt;T&gt;(queryUuid)</code></a></td>
            <td scope="row" data-label="Description">Listen responses from an existing live query</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/core/streaming.md#kill"> <code>db.Kill(queryUuid)</code></a></td>
            <td scope="row" data-label="Description">Kill a running live query</td>
        </tr>
    		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/run.md"> <code> db.Run(name, version, args) </code></a></td>
			<td scope="row" data-label="Description">Runs a SurrealQL function</td>
		</tr>
</tbody>
</table>

## Mutation methods

<table>
    <thead>
        <tr>
            <th scope="col">Method</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/create.md"> <code>db.Create&lt;T&gt;(thing, data)</code></a></td>
            <td scope="row" data-label="Description">Creates a record in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/insert.md"> <code>db.Insert&lt;T&gt;(thing, data)</code></a></td>
            <td scope="row" data-label="Description">Inserts one or multiple records in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/update.md"> <code>db.Update&lt;T&gt;(thing, data)</code></a></td>
            <td scope="row" data-label="Description">Updates all records in a table, or a specific record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/upsert.md"> <code>db.Upsert&lt;T&gt;(thing, data)</code></a></td>
            <td scope="row" data-label="Description">Creates or updates a specific record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/merge.md"> <code>db.Merge&lt;T&gt;(thing, data)</code></a></td>
            <td scope="row" data-label="Description">Modifies all records in a table, or a specific record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/patch.md"> <code>db.Patch&lt;T&gt;(thing, data)</code></a></td>
            <td scope="row" data-label="Description">Applies JSON Patch changes to all records in a table, or a specific record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/delete.md"> <code>db.Delete(thing)</code></a></td>
            <td scope="row" data-label="Description">Deletes all records, or a specific record</td>
        </tr>
    		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/relate.md"> <code> db.Relate(table, @in, @out, data) </code></a></td>
			<td scope="row" data-label="Description">Creates a graph relation between two records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/insert-relation.md"> <code> db.InsertRelation&lt;T&gt;(table, data) </code></a></td>
			<td scope="row" data-label="Description">Inserts one or more graph relations into a table</td>
		</tr>
</tbody>
</table>

## Live query methods

A live query streams changes as they happen. Start one from a statement or a table, listen to the stream it returns, then stop it with the query's id.

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/live-query.md"> <code> db.LiveQuery&lt;T&gt;(sql) </code></a></td>
			<td scope="row" data-label="Description">Initiates a live query from a SurrealQL statement</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/live-raw-query.md"> <code> db.LiveRawQuery&lt;T&gt;(sql, params) </code></a></td>
			<td scope="row" data-label="Description">Initiates a live query from a raw SurrealQL statement with parameters</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/live-table.md"> <code> db.LiveTable&lt;T&gt;(table, diff) </code></a></td>
			<td scope="row" data-label="Description">Initiates a live query on a whole table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/listen_live.md"> <code> db.ListenLive&lt;T&gt;(queryUuid) </code></a></td>
			<td scope="row" data-label="Description">Listens for changes on a live query that is already running</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/kill.md"> <code> db.Kill(queryUuid) </code></a></td>
			<td scope="row" data-label="Description">Stops a running live query</td>
		</tr>
	</tbody>
</table>

## Data management methods

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/export.md"> <code> db.Export(options) </code></a></td>
			<td scope="row" data-label="Description">Exports the database as a SurrealQL script</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/import.md"> <code> db.Import(string) </code></a></td>
			<td scope="row" data-label="Description">Imports a SurrealQL script into the database</td>
		</tr>
	</tbody>
</table>

## Authentication methods

<table>
    <thead>
        <tr>
            <th scope="col">Method</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/signup.md"> <code>db.SignUp(credentials)</code></a></td>
            <td scope="row" data-label="Description">Signs this connection up to a specific authentication scope</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/signin.md"> <code>db.SignIn(credentials)</code></a></td>
            <td scope="row" data-label="Description">Signs this connection in to a specific authentication scope</td>
        </tr>
		<tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/invalidate.md"> <code>db.Invalidate()</code></a></td>
            <td scope="row" data-label="Description">Invalidates the authentication for the current connection</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/authenticate.md"> <code>db.Authenticate(token)</code></a></td>
            <td scope="row" data-label="Description">Authenticates the current connection with a JWT token</td>
        </tr>
		<tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/dotnet/methods/info.md"> <code>db.Info&lt;T&gt;()</code></a></td>
            <td scope="row" data-label="Description">Returns the record of an authenticated scope user</td>
        </tr>
    </tbody>
</table>

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/authenticate

# Authenticate

Authenticates the current connection with a JWT token.

Authenticates the current connection with a JWT token.

```csharp title="Method Syntax"
await db.Authenticate(jwt)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>jwt</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The JWT object holder of the authentication token.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
Jwt jwt = new JWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJTdXJyZWFsREIiLCJpYXQiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMiwiZXhwIjoxODM2NDM5MDIyLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJTQyI6InVzZXIiLCJJRCI6InVzZXI6dG9iaWUifQ.N22Gp9ze0rdR06McGj1G-h2vu6a6n9IVqUbMFJlOxxA");
await db.Authenticate(jwt);
```

You can invalidate the authentication for the current connection using the [`Invalidate()` method](/docs/reference/dotnet/methods/invalidate.md).

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/connect

# Connect

Connects the client to the underlying endpoint, also improving performance to avoid cold starts.

Connects the client to the underlying endpoint, also improving performance to avoid cold starts.

```csharp title="Method Syntax"
await db.Connect()
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
await db.Connect();
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/create

# Create

Creates a record in the database with the SurrealDB .NET SDK.

Creates a record in the database.

```csharp title="Method Syntax"
await db.Create<T>(resource, data)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
// Create a record with a random ID
var person = await db.Create<Person>("person");

// Create a record with a random ID & specific fields
var person = await db.Create("person", new Person { Name = "Tobie" });

// Create a record with a specific ID
var personToCreate = new Person
{
    Id = ("person", "tobie"),
    Name = "Tobie",
    Settings = new Settings
    {
        Active = true,
        Marketing = true,
    },
};
var result = await db.Create(personToCreate);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/delete

# Delete

The .NET SDK for SurrealDB enables simple and advanced querying of a remote or embedded database.

`Delete` removes a single record, or every record in a table, through the .NET SDK.

```csharp title="Method Syntax"
await db.Delete(resource)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to delete.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
// Delete all records from a table
await db.Delete("person");

// Delete a specific record from a table
await db.Delete(("person", "h5wxrf2ewk8xjxosxtyc"));
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/export

# Export

Export the database as a SurrealQL script.

Export the database as a SurrealQL script.
To use this method, you need to be connected to a SurrealDB instance that is version `>= 2.1.0`.

```csharp title="Method Syntax"
await db.Export(options)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>options</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Export configuration options.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp title="Only exporting db functions in a schema variable"
var options = new ExportOptions
{
    Users = false,
    Accesses = false,
    Params = false,
    Functions = true,
    Users = false,
    Versions = false,
    Tables = false,
    Records = false,
};

string schema = await db.Export(options);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/health

# Health

Checks the status of the database server and storage engine.

Checks the status of the database server and storage engine.

```csharp title="Method Syntax"
await db.Health()
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
bool status = await db.Health();
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/import

# Import

Imports data into a SurrealDB database with the SurrealDB .NET SDK.

Imports data into a SurrealDB database.
To use this method, you need to be connected to a SurrealDB instance that is version `>= 2.0.0`.

```csharp title="Method Syntax"
await db.Import(string)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>input</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The SurrealQL script used to import data in the database.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
string input = 
        """
        DEFINE TABLE foo SCHEMALESS;
        DEFINE TABLE bar SCHEMALESS;
        CREATE foo:1 CONTENT { hello: "world" };
        CREATE bar:1 CONTENT { hello: "world" };
        DEFINE FUNCTION fn::foo() -> string {
            RETURN "bar";
        };
        """;

await db.Import(input);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/info

# Info

This method returns the record of an authenticated scope user.

This method returns the record of an authenticated scope user.

```csharp title="Method Syntax"
await db.Info<T>()
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Properties</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Properties">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
var currentUser = await db.Info<User>();
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/insert

# Insert

Inserts one or multiple records in the database.

Inserts one or multiple records in the database.

```csharp title="Method Syntax"
await db.Insert<T>(table, data)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>table</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Optionally pass along a table to insert into.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Either a single document/record or an array of documents/records to insert
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
var posts = new List<Post>
{
    new Post
    {
        Id = ("post", "First"),
        Title = "An article",
        Content = "This is the first article"
    },
    new Post
    {
        Id = ("post", "Second"),
        Title = "An article",
        Content = "This is the second article"
    }
};

await db.Insert("post", posts);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/insert-relation

# InsertRelation

The .NET SDK for SurrealDB enables you to insert a relation between two records.

Inserts one or multiple relations in the database.

```csharp title="Method Syntax"
await db.InsertRelation<T>(table, data)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>table</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Optionally pass along a table to insert into.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Either a single document/record or an array of documents/records to insert
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
await db.InsertRelation(
    new WroteRelation
    {
        In = ("user", "u1"),
        Out = ("post", "p1"),
        CreatedAt = now,
        NumberOfPages = 144
    }
);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/invalidate

# Invalidate

Invalidates the authentication for the current connection.

Invalidates the authentication for the current connection.

```csharp title="Method Syntax"
await db.Invalidate()
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Properties</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Properties">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
await db.Invalidate();
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/kill

# Kill

The .NET SDK for SurrealDB enables you to kill a running live query.

Kills a running live query by its UUID.

```csharp title="Method Syntax"
await db.Kill(queryUuid)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>queryUuid</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The UUID of the live query you wish to kill.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
await db.Kill(queryUuid);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/listen_live

# ListenLive

The .NET SDK for SurrealDB enables you to listen for changes to records in a table.

Listens to responses from an existing live query.

```csharp title="Method Syntax"
db.ListenLive<T>(queryUuid)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>queryUuid</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The UUID of the live query to consume.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
await using var liveQuery = db.ListenLive<Person>(queryUuid);

// Consume the live query...
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/live-query

# LiveQuery

Initiate a live query from a SurrealQL statement.

Initiate a live query from a SurrealQL statement.

```csharp title="Method Syntax"
await db.LiveQuery<T>(sql)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
const string table = "person"; 
await using var liveQuery = await db.LiveQuery<Person>($"LIVE SELECT * FROM type::table({table});");

// Consume the live query...
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/live-raw-query

# LiveRawQuery

Initiate a live query from a SurrealQL statement, based on a raw SurrealQL query.

Initiate a live query from a SurrealQL statement, based on a raw SurrealQL query.

```csharp title="Method Syntax"
await db.LiveRawQuery<T>(sql, params)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>params</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```csharp
await using var liveQuery = await db.LiveRawQuery<Person>("LIVE SELECT * FROM person;");

// Consume the live query...
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/live-table

# LiveTable

Initiate a live query from a table with the SurrealDB .NET SDK.

Initiate a live query from a table.

```csharp title="Method Syntax"
await db.LiveTable<T>(table, diff)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>table</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The table name to listen for changes for.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>diff</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                If set to true, live notifications will include an array of JSON Patch objects, rather than the entire record for each notification.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```csharp
await using var liveQuery = await db.LiveTable<Person>("person");

// Consume the live query...
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/merge

# Merge

Modifies all records in a table, or a specific record.

Modifies all records in a table, or a specific record.

```csharp title="Method Syntax"
await db.Merge<T>(resource, data)
```

> [!NOTE]
> This function merges the current document / record data with the specified data.

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to merge.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The data with which to modify the records.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp title="Merging data within a single record"
// Only changes the fields specified in the merge object
var merge = new PersonMerge
{
    Id = ("person", "tobie"),
    Settings = new Settings
    {
        Active = true,
        Marketing = false,
    },
};
var result = await db.Merge<PersonMerge, Person>(merge);

// Only changes the fields specified in the Dictionary
var data = new Dictionary<string, object>
{
    { "tags", new List<string> { "developer", "engineer" } }
};

var result = await db.Merge<Person>(("person", "tobie"), data);
```

```csharp title="Merging data for every record in a table"
// Only changes the fields specified in the merge object
var merge = new PersonMerge
{
    Settings = new Settings
    {
        Active = true,
        Marketing = false,
    },
};
var result = await db.Merge<PersonMerge, Person>("person", merge);

// Only changes the fields specified in the Dictionary
var data = new Dictionary<string, object>
{
    { "tags", new List<string> { "developer", "engineer" } }
};

var result = await db.Merge<Person>("person", data);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/patch

# Patch

Applies JSON Patch changes to all records, or a specific record, in the database.

Applies JSON Patch changes to all records, or a specific record, in the database.

```csharp title="Method Syntax"
await db.Patch<T>(resource, data)
```

> [!NOTE]
> This function patches document / record data with the specified <a href="https://jsonpatch.com/">JSON Patch</a> data.

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to patch.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The JSON Patch data with which to patch the records.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
// Update a record with a specific ID
var result = await db.Patch(("person", "tobie"), patches);

// Update all records in a table
var result = await db.Patch("person", patches);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/query

# Query

Runs a set of SurrealQL statements against the database.

Runs a set of SurrealQL statements against the database.

```csharp title="Method Syntax"
await db.Query(sql)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
// Execute query with params
const string table = "person";
var result = await db.Query($"CREATE person; SELECT * FROM type::table({table});");

// Get the first result from the first query
var created = result.GetValue<Person>(0);

// Get all of the results from the second query
var people = result.GetValue<List<Person>>(1);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/raw-query

# RawQuery

Runs a set of SurrealQL statements against the database, based on a raw SurrealQL query.

Runs a set of SurrealQL statements against the database, based on a raw SurrealQL query.

```csharp title="Method Syntax"
await db.RawQuery(sql, params)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>params</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
// Assign the variable on the connection
var @params = new Dictionary<string, object> { { "table",
    "person" } };
var result = await db.RawQuery("CREATE person; SELECT * FROM type::table($table);", @params);

// Get the first result from the first query
var created = result.GetValue<Person>(0);

// Get all of the results from the second query
var people = result.GetValue<List<Person>>(1);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/relate

# Relate

Creates a relation between records with the SurrealDB .NET SDK.

Creates a relation between records.

```csharp title="Method Syntax"
await db.Relate(table, @in, @out, data)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>@in</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The edge of the relation.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>@out</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The other edge of the relation.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
var data = new WroteRelation { CreatedAt = DateTime.UtcNow,
    NumberOfPages = 14 };

await db.Relate<WroteRelation, WroteRelation>(
    "wrote",
    ("user", "one"),
    ("post", "one"),
    data
);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/run

# Run

Runs a SurrealQL function with the SurrealDB .NET SDK.

Runs a [SurrealQL function](/docs/reference/query-language/functions/database-functions.md).

```csharp title="Method Syntax"
await db.Run(name, version, args)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>name</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The name of the [SurrealQL function](/docs/reference/query-language/functions/database-functions.md).
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>version</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The version of the [SurrealQL function](/docs/reference/query-language/functions/database-functions.md).
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>args</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The arguments used by the [SurrealQL function](/docs/reference/query-language/functions/database-functions.md).
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
DateTime now = await db.Run<DateTime>("time::now");

string result = await db.Run<string>("string::repeat", ["test", 3]);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/select

# Select

Selects all records in a table, or a specific record, from the database.

Selects all records in a table, or a specific record, from the database.

```csharp title="Method Syntax"
await db.Select<T>(resource)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to select.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
// Select all records from a table
var people = await db.Select<Person>("person");

// Select a specific record from a table
var person = await db.Select<Person>(("person",
    "h5wxrf2ewk8xjxosxtyc"));
var person = await db.Select<Person>(new StringRecordId("person:h5wxrf2ewk8xjxosxtyc"));

// Select a specific record from a table, given a non-string id
var person = await db.Select<Person>(("person",
    new Guid("8424486b-85b3-4448-ac8d-5d51083391c7")));
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/set

# Set

Assigns a value as a parameter for this connection.

Assigns a value as a parameter for this connection.

```csharp title="Method Syntax"
await db.Set(key, value)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>key</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the name of the variable.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>value</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Assigns the value to the variable name.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
// Assign the variable on the connection
await db.Set("name", new Name { FirstName = "Tobie",
    LastName = "Morgan Hitchcock" });

// Use the variable in a subsequent query
await db.Query($"CREATE person SET name = $name");

// Use the variable in a subsequent query
await db.Query($"SELECT * FROM person WHERE name.first_name = $name.first_name");
```

You can remove the variable from the connection using the [`Unset()` method](/docs/reference/dotnet/methods/unset.md).

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/signin

# SignIn

Signs in to a root, namespace, database or scope user.

Signs in to a root, namespace, database or scope user.

```csharp title="Method Syntax"
await db.SignIn(credentials)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>credentials</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Variables used in a signin query.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

**Root user**

```csharp
// Sign in as root user
await db.SignIn(new RootAuth { Username = "root",
    Password = "secret" });
```

**Namespace user**

```csharp
// Sign in using namespace auth
await db.SignIn(
    new NamespaceAuth
    {
        Namespace = "main", 
        Username = "johndoe", 
        Password = "password123" 
    }
);
```

**Database user**

```csharp
// Sign in using database auth
await db.SignIn(
    new DatabaseAuth
    {
        Namespace = "main", 
        Database = "main", 
        Username = "johndoe", 
        Password = "password123" 
    }
);
```

**Record Access**

```csharp
// Sign in with Record Access
var authParams = new AuthParams
{
    Namespace = "main",
    Database = "main",
    Access = "user",
    Email = "info@surrealdb.com",
    Password = "123456"
};

Jwt jwt = await db.SignIn(authParams);

public class AuthParams : ScopeAuth
{
	public string? Username { get; set; }
	public string? Email { get; set; }
	public string? Password { get; set; }
}
```

**Scopes**

```csharp
// Sign in as a scoped user
var authParams = new AuthParams
{
    Namespace = "main",
    Database = "main",
    Scope = "user",
    Email = "info@surrealdb.com",
    Password = "123456"
};

Jwt jwt = await db.SignIn(authParams);

public class AuthParams : ScopeAuth
{
	public string? Username { get; set; }
	public string? Email { get; set; }
	public string? Password { get; set; }
}
```

You can invalidate the authentication for the current connection using the [`Invalidate()` method](/docs/reference/dotnet/methods/invalidate.md).

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/signup

# SignUp

Signs up to a specific authentication scope / access method.

Signs up to a specific authentication scope / access method.

```csharp title="Method Syntax"
await db.SignUp(credentials)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>credentials</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Credentials to sign up as a scoped user.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
// With Record Access
var authParams = new AuthParams
{
    Namespace = "main",
    Database = "main",
    Access = "user",
	// Also pass any properties required by the access definition
    Email = "info@surrealdb.com",
    Password = "123456"
};

Jwt jwt = await db.SignUp(authParams);

public class AuthParams : ScopeAuth
{
	public string? Username { get; set; }
	public string? Email { get; set; }
	public string? Password { get; set; }
}
```

You can invalidate the authentication for the current connection using the [`Invalidate()` method](/docs/reference/dotnet/methods/invalidate.md).

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/unset

# Unset

Removes a parameter for this connection.

Removes a parameter for this connection.

```csharp title="Method Syntax"
await db.Unset(key)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>key</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
await db.Unset("name");
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/update

# Update

Updates all records in a table, or a specific record, in the database.

Updates all records in a table, or a specific record, in the database.

```csharp title="Method Syntax"
await db.Update<T>(thing, data)
```

> [!NOTE]
> This function replaces the current document / record data with the specified data.

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific [`RecordId`](/docs/reference/dotnet/data-types.md#recordid) to update.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to update.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
var post = new Post
{
    Id = ("post", "another"),
    Title = "A new article",
    Content = "This is a new article created using the .NET SDK"
};

// Updates a single record
await db.Update(post);

var data = new Person
{
    Name = "Tobie",
    Settings = new Settings
    {
        Active = true,
        Marketing = true,
    },
};

// Updates all records inside the "person" table
await db.Update("person", data);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/upsert

# Upsert

Creates or updates a specific record with the SurrealDB .NET SDK.

Creates or updates a specific record.

```csharp title="Method Syntax"
await db.Upsert<T>(data)
```

> [!NOTE]
> This function creates a new document / record or replaces the current one with the specified data.

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>data</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
var person = new Person
{
        Id = ("person", "tobie"),
        // Id is mandatory to apply create or update
    Name = "Tobie",
    Settings = new Settings
    {
        Active = true,
        Marketing = true,
    },
};

// Create a new record when it doesn't exist
var created = await db.Upsert(person);

// Update an existing record when it does exist
var updated = await db.Upsert(person);
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/use

# Use

Switch to a specific namespace and database.

Switch to a specific namespace and database.

```csharp title="Method Syntax"
await db.Use(ns, db)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>namespace</code>
                <label label="initially required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Switches to a specific namespace.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>database</code>
                <label label="initially required" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                Switches to a specific database.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
await db.Use("main", "main");
```

---

Source: https://surrealdb.com/docs/reference/dotnet/methods/version

# Version

Retrieves the version of the SurrealDB instance.

Retrieves the version of the SurrealDB instance.

```csharp title="Method Syntax"
await db.Version()
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="col" scope="row" data-label="Arguments">
                <code>cancellationToken</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="col" scope="row" data-label="Description">
                The cancellationToken enables graceful cancellation of asynchronous operations.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```csharp
string version = await db.Version(); // Will return "surrealdb-1.1.1"
```

---

Source: https://surrealdb.com/docs/reference/golang

# Go SDK

The official SurrealDB SDK for Go. Simple and advanced querying of a remote database from server-side applications.

The SurrealDB SDK for Go enables you to interact with SurrealDB from server-side applications, systems, APIs, and [as an embedded instance](/docs/reference/golang/embedding.md). You can use the SDK to [execute queries](/docs/reference/golang/concepts/executing-queries.md), [manage data](/docs/reference/golang/concepts/data-manipulation.md), [authenticate users](/docs/reference/golang/concepts/authentication.md), subscribe to real-time changes with [live queries](/docs/reference/golang/concepts/live-queries.md), and work with interactive [sessions](/docs/reference/golang/concepts/multiple-sessions.md) and [transactions](/docs/reference/golang/concepts/transactions.md).

> [!IMPORTANT]
> The SDK requires Go `1.23` or greater, and is available as a [go.dev package](https://pkg.go.dev/github.com/surrealdb/surrealdb.go).

> [!NOTE]
> The latest version of the SDK is `v1.7.0`.
> The SDK works with SurrealDB versions `v2.0.0` and later, including the current release, `v3.3.4`.

## Getting started

- [Installation](/docs/reference/golang/installation.md) - Install the SDK and add it to your project.

- [Getting started guide](/docs/languages/golang.md) - Connect to SurrealDB and run your first queries.

## Learn

- [Concepts](/docs/reference/golang/concepts/connecting-to-surrealdb.md) - Guides for connecting, authenticating, querying, and working with data.

- [API Reference](/docs/reference/golang/api/core/db.md) - Complete reference for the SDK's methods, types, and errors.

## Contributing

To contribute to the SDK code, submit an Issue or Pull Request in the [surrealdb.go](https://github.com/surrealdb/surrealdb.go) repository. To contribute to this documentation, submit an Issue or Pull Request in the [docs.surrealdb.com](https://github.com/surrealdb/docs.surrealdb.com) repository.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.go)
- [go.dev package](https://pkg.go.dev/github.com/surrealdb/surrealdb.go)

---

Source: https://surrealdb.com/docs/reference/golang/api/core/db

# DB

The DB struct is the main entry point for connecting to and interacting with a SurrealDB instance from Go.

The `DB` struct is the main client for interacting with SurrealDB. It holds the underlying connection and provides methods for [authentication](/docs/reference/golang/concepts/authentication.md), namespace selection, and [live query](/docs/reference/golang/concepts/live-queries.md) management. Data operations are performed through generic top-level functions that accept `*DB` as a parameter.

**Source:** [db.go](https://github.com/surrealdb/surrealdb.go/blob/main/db.go)

---

## Constructors

### `FromEndpointURLString` {#fromendpointurlstring}

Creates a new `*DB` and connects to a SurrealDB instance. The URL scheme determines the connection type (WebSocket or HTTP).

```go title="Syntax"
db, err := surrealdb.FromEndpointURLString(ctx, connectionURL)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for canceling the connection attempt.</td>
        </tr>
        <tr>
            <td><code>connectionURL</code> _(required)_</td>
            <td><code>string</code></td>
            <td>The endpoint URL. Supported schemes: <code>ws://</code>, <code>wss://</code>, <code>http://</code>, <code>https://</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*DB, error)`

#### Examples

```go title="WebSocket"
db, err := surrealdb.FromEndpointURLString(ctx, "ws://localhost:8000")
```

```go title="HTTPS"
db, err := surrealdb.FromEndpointURLString(ctx, "https://cloud.surrealdb.com")
```

### `FromConnection` {#fromconnection}

Creates a new `*DB` from a custom `connection.Connection` implementation. Calls `.Connect(ctx)` automatically.

```go title="Syntax"
db, err := surrealdb.FromConnection(ctx, conn)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for canceling the connection attempt.</td>
        </tr>
        <tr>
            <td><code>conn</code> _(required)_</td>
            <td><code>connection.Connection</code></td>
            <td>A connection implementation (e.g., <code>gorillaws.New(conf)</code> or <code>http.New(conf)</code>).</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*DB, error)`

---

## Connection methods

### `.Close()` {#close}

Closes the underlying connection and releases resources.

```go title="Syntax"
err := db.Close(ctx)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the close operation.</td>
        </tr>
    </tbody>
</table>

**Returns:** `error`

### `.Use()` {#use}

Selects the namespace and database to use for subsequent operations.

```go title="Syntax"
err := db.Use(ctx, ns, database)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>ns</code> _(required)_</td>
            <td><code>string</code></td>
            <td>The namespace to use.</td>
        </tr>
        <tr>
            <td><code>database</code> _(required)_</td>
            <td><code>string</code></td>
            <td>The database to use.</td>
        </tr>
    </tbody>
</table>

**Returns:** `error`

### `.Version()` {#version}

Returns version information about the connected SurrealDB instance.

```go title="Syntax"
ver, err := db.Version(ctx)
```

**Returns:** `(*VersionData, error)`, see [`VersionData`](/docs/reference/golang/api/types.md#versiondata)

#### Examples

```go
ver, err := db.Version(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Println(ver.Version)
```

---

## Authentication methods

### `.SignIn()` {#signin}

Signs in an existing user. The fields provided in `authData` determine the authentication level (root, namespace, database, or record).

```go title="Syntax"
token, err := db.SignIn(ctx, authData)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>authData</code> _(required)_</td>
            <td><code>any</code></td>
            <td>An <a href="/docs/reference/golang/api/types.md#auth"><code>Auth</code></a> struct or <code>map[string]any</code> with credentials.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(string, error)` - the JWT token string

#### Examples

```go title="Root signin"
token, err := db.SignIn(ctx, surrealdb.Auth{
    Username: "root",
    Password: "secret",
})
```

```go title="Record signin"
token, err := db.SignIn(ctx, map[string]any{
    "NS": "my_ns", "DB": "my_db", "AC": "user_access",
    "user": "tobie", "pass": "s3cret",
})
```

### `.SignInWithRefresh()` {#signinwithrefresh}

Signs in using a `TYPE RECORD` access method with `WITH REFRESH` enabled. Returns both an access token and a refresh token. SurrealDB v3+ only.

```go title="Syntax"
tokens, err := db.SignInWithRefresh(ctx, authData)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>authData</code> _(required)_</td>
            <td><code>any</code></td>
            <td>Credentials as <code>map[string]any</code>. Use <code>"refresh"</code> key for token refresh.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*Tokens, error)` - see [`Tokens`](/docs/reference/golang/api/types.md#tokens)

#### Examples

```go title="Initial signin"
tokens, err := db.SignInWithRefresh(ctx, map[string]any{
    "NS": "my_ns", "DB": "my_db", "AC": "user_access",
    "user": "tobie", "pass": "s3cret",
})
```

```go title="Refresh"
newTokens, err := db.SignInWithRefresh(ctx, map[string]any{
    "NS": "my_ns", "DB": "my_db", "AC": "user_access",
    "refresh": tokens.Refresh,
})
```

### `.SignUp()` {#signup}

Signs up a new record user.

```go title="Syntax"
token, err := db.SignUp(ctx, authData)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>authData</code> _(required)_</td>
            <td><code>any</code></td>
            <td>An <a href="/docs/reference/golang/api/types.md#auth"><code>Auth</code></a> struct or <code>map[string]any</code> with signup credentials.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(string, error)` - the JWT token string

### `.SignUpWithRefresh()` {#signupwithrefresh}

Signs up using a `TYPE RECORD` access method with `WITH REFRESH` enabled. SurrealDB v3+ only.

```go title="Syntax"
tokens, err := db.SignUpWithRefresh(ctx, authData)
```

**Returns:** `(*Tokens, error)` - see [`Tokens`](/docs/reference/golang/api/types.md#tokens)

### `.Authenticate()` {#authenticate}

Authenticates the connection with a JWT token.

```go title="Syntax"
err := db.Authenticate(ctx, token)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>token</code> _(required)_</td>
            <td><code>string</code></td>
            <td>The JWT token to authenticate with.</td>
        </tr>
    </tbody>
</table>

**Returns:** `error`

### `.Invalidate()` {#invalidate}

Invalidates the current authentication, returning the connection to an unauthenticated state.

```go title="Syntax"
err := db.Invalidate(ctx)
```

**Returns:** `error`

### `.Info()` {#info}

Returns the record of the currently authenticated user.

```go title="Syntax"
info, err := db.Info(ctx)
```

**Returns:** `(map[string]any, error)`

---

## Variables

### `.Let()` {#let}

Defines a variable on the connection that can be used in subsequent queries.

```go title="Syntax"
err := db.Let(ctx, key, val)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>key</code> _(required)_</td>
            <td><code>string</code></td>
            <td>The variable name (without <code>$</code> prefix).</td>
        </tr>
        <tr>
            <td><code>val</code> _(required)_</td>
            <td><code>any</code></td>
            <td>The value to assign.</td>
        </tr>
    </tbody>
</table>

**Returns:** `error`

### `.Unset()` {#unset}

Removes a previously defined variable from the connection.

```go title="Syntax"
err := db.Unset(ctx, key)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>key</code> _(required)_</td>
            <td><code>string</code></td>
            <td>The variable name to remove.</td>
        </tr>
    </tbody>
</table>

**Returns:** `error`

---

## Sessions and transactions

### `.Attach()` {#attach}

Creates a new session on the WebSocket connection. Sessions are only supported on WebSocket connections (SurrealDB v3+).

```go title="Syntax"
session, err := db.Attach(ctx)
```

**Returns:** `(*Session, error)` - see [`Session`](/docs/reference/golang/api/core/session.md)

### `.Begin()` {#begin}

Starts a new interactive transaction on the default session. Transactions are only supported on WebSocket connections (SurrealDB v3+).

```go title="Syntax"
tx, err := db.Begin(ctx)
```

**Returns:** `(*Transaction, error)` - see [`Transaction`](/docs/reference/golang/api/core/transaction.md)

---

## Query functions

These are top-level generic functions that accept [`*DB`](#constructors), [`*Session`](/docs/reference/golang/api/core/session.md), or [`*Transaction`](/docs/reference/golang/api/core/transaction.md) as the `s` parameter.

### `Query` {#query}

Executes a SurrealQL query string and returns typed results.

```go title="Syntax"
results, err := surrealdb.Query[TResult](ctx, s, sql, vars)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>TResult</code></td>
            <td>type parameter</td>
            <td>The expected result type for each statement.</td>
        </tr>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>s</code> _(required)_</td>
            <td><code>*DB | *Session | *Transaction</code></td>
            <td>The sender to execute the query on.</td>
        </tr>
        <tr>
            <td><code>sql</code> _(required)_</td>
            <td><code>string</code></td>
            <td>The SurrealQL query string.</td>
        </tr>
        <tr>
            <td><code>vars</code> _(required)_</td>
            <td><code>map[string]any</code></td>
            <td>Variables to bind into the query. Pass <code>nil</code> for no variables.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*[]QueryResult[TResult], error)` - see [`QueryResult`](/docs/reference/golang/api/types.md#queryresult)

#### Examples

```go
results, err := surrealdb.Query[[]Person](ctx, db,
    "SELECT * FROM persons WHERE age > $min",
    map[string]any{"min": 18},
)
```

### `QueryRaw` {#queryraw}

Composes and executes a batch of query statements with per-statement results.

```go title="Syntax"
err := surrealdb.QueryRaw(ctx, s, queries)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>s</code> _(required)_</td>
            <td><code>*DB | *Session | *Transaction</code></td>
            <td>The sender to execute the query on.</td>
        </tr>
        <tr>
            <td><code>queries</code> _(required)_</td>
            <td><code>*[]QueryStmt</code></td>
            <td>A pointer to a slice of <a href="/docs/reference/golang/api/types.md#querystmt"><code>QueryStmt</code></a> objects. Results are written back to each statement.</td>
        </tr>
    </tbody>
</table>

**Returns:** `error`

---

## Data functions

These are top-level generic functions that accept [`*DB`](#constructors), [`*Session`](/docs/reference/golang/api/core/session.md), or [`*Transaction`](/docs/reference/golang/api/core/transaction.md) as the `s` parameter.

### `Select` {#select}

Retrieves records from the database.

```go title="Syntax"
result, err := surrealdb.Select[TResult](ctx, s, what)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>TResult</code></td>
            <td>type parameter</td>
            <td>Use a slice type for tables, a single type for record IDs.</td>
        </tr>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>s</code> _(required)_</td>
            <td><code>*DB | *Session | *Transaction</code></td>
            <td>The sender.</td>
        </tr>
        <tr>
            <td><code>what</code> _(required)_</td>
            <td><code>string |</code> <a href="/docs/reference/golang/api/values/table.md"><code>Table</code></a> <code>|</code> <a href="/docs/reference/golang/api/values/record-id.md"><code>RecordID</code></a></td>
            <td>The table or record to select.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*TResult, error)`

### `Create` {#create}

Creates a new record.

```go title="Syntax"
result, err := surrealdb.Create[TResult](ctx, s, what, data)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>s</code> _(required)_</td>
            <td><code>*DB | *Session | *Transaction</code></td>
            <td>The sender.</td>
        </tr>
        <tr>
            <td><code>what</code> _(required)_</td>
            <td><code>string |</code> <a href="/docs/reference/golang/api/values/table.md"><code>Table</code></a> <code>|</code> <a href="/docs/reference/golang/api/values/record-id.md"><code>RecordID</code></a></td>
            <td>The table or record ID to create.</td>
        </tr>
        <tr>
            <td><code>data</code> _(required)_</td>
            <td><code>any</code></td>
            <td>The record data (struct or map).</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*TResult, error)`

### `Insert` {#insert}

Inserts one or more records into a table.

```go title="Syntax"
results, err := surrealdb.Insert[TResult](ctx, s, table, data)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>s</code> _(required)_</td>
            <td><code>*DB | *Session | *Transaction</code></td>
            <td>The sender.</td>
        </tr>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><a href="/docs/reference/golang/api/values/table.md"><code>models.Table</code></a></td>
            <td>The table to insert into.</td>
        </tr>
        <tr>
            <td><code>data</code> _(required)_</td>
            <td><code>any</code></td>
            <td>A single record or slice of records to insert.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*[]TResult, error)`

### `Update` {#update}

Replaces the entire content of a record (like a PUT).

```go title="Syntax"
result, err := surrealdb.Update[TResult](ctx, s, what, data)
```

**Returns:** `(*TResult, error)`

### `Upsert` {#upsert}

Creates a record if it does not exist, or replaces it entirely.

```go title="Syntax"
result, err := surrealdb.Upsert[TResult](ctx, s, what, data)
```

**Returns:** `(*TResult, error)`

### `Merge` {#merge}

Merges data into an existing record, preserving unmentioned fields.

```go title="Syntax"
result, err := surrealdb.Merge[TResult](ctx, s, what, data)
```

**Returns:** `(*TResult, error)`

### `Patch` {#patch}

Applies JSON Patch operations to a record or table.

```go title="Syntax"
result, err := surrealdb.Patch(ctx, s, what, patches)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>s</code> _(required)_</td>
            <td><code>*DB | *Session | *Transaction</code></td>
            <td>The sender.</td>
        </tr>
        <tr>
            <td><code>what</code> _(required)_</td>
            <td><code>string |</code> <a href="/docs/reference/golang/api/values/table.md"><code>Table</code></a> <code>|</code> <a href="/docs/reference/golang/api/values/record-id.md"><code>RecordID</code></a></td>
            <td>The table or record to patch.</td>
        </tr>
        <tr>
            <td><code>patches</code> _(required)_</td>
            <td><code>[]PatchData</code></td>
            <td>JSON Patch operations. See <a href="/docs/reference/golang/api/types.md#patchdata"><code>PatchData</code></a>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*[]PatchData, error)`

### `Delete` {#delete}

Removes records from the database.

```go title="Syntax"
result, err := surrealdb.Delete[TResult](ctx, s, what)
```

**Returns:** `(*TResult, error)`

### `Relate` {#relate}

Creates a relationship between two records with an auto-generated ID.

```go title="Syntax"
result, err := surrealdb.Relate[TResult](ctx, s, rel)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>s</code> _(required)_</td>
            <td><code>*DB | *Session | *Transaction</code></td>
            <td>The sender.</td>
        </tr>
        <tr>
            <td><code>rel</code> _(required)_</td>
            <td><code>*Relationship</code></td>
            <td>The relationship to create. See <a href="/docs/reference/golang/api/types.md#relationship"><code>Relationship</code></a>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*TResult, error)`

### `InsertRelation` {#insertrelation}

Inserts a relation record, optionally with a specified ID.

```go title="Syntax"
result, err := surrealdb.InsertRelation[TResult](ctx, s, rel)
```

**Returns:** `(*TResult, error)`

---

## Live query methods

### `Live` {#live}

Starts a live query on a table. Only available on `*DB` and `*Session`.

```go title="Syntax"
liveID, err := surrealdb.Live(ctx, s, table, diff)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
        <tr>
            <td><code>s</code> _(required)_</td>
            <td><code>*DB | *Session</code></td>
            <td>The sender (not <code>*Transaction</code>).</td>
        </tr>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><a href="/docs/reference/golang/api/values/table.md"><code>models.Table</code></a></td>
            <td>The table to watch.</td>
        </tr>
        <tr>
            <td><code>diff</code> _(required)_</td>
            <td><code>bool</code></td>
            <td>If <code>true</code>, notifications contain JSON Patch diffs.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`(*models.UUID, error)`](/docs/reference/golang/api/values/uuid.md)

### `Kill` {#kill}

Terminates a live query and closes its notification channel.

```go title="Syntax"
err := surrealdb.Kill(ctx, s, id)
```

**Returns:** `error`

### `.LiveNotifications()` {#livenotifications}

Returns the notification channel for a live query.

```go title="Syntax"
ch, err := db.LiveNotifications(liveQueryID)
```

**Returns:** `(chan` [`connection.Notification`](/docs/reference/golang/api/types.md#notification)`, error)`

### `.CloseLiveNotifications()` {#closelivenotifications}

Closes the notification channel without killing the server-side live query.

```go title="Syntax"
err := db.CloseLiveNotifications(liveQueryID)
```

**Returns:** `error`

---

## Low-level RPC

### `Send` {#send}

Sends a raw RPC request to SurrealDB. Limited to data methods: `select`, `create`, `insert`, `insert_relation`, `kill`, `live`, `merge`, `relate`, `update`, `upsert`, `patch`, `delete`, `query`.

```go title="Syntax"
err := surrealdb.Send[Result](ctx, db, res, method, params...)
```

**Returns:** `error`

---

## See also

- [Session](/docs/reference/golang/api/core/session.md) for session management reference
- [Transaction](/docs/reference/golang/api/core/transaction.md) for transaction reference
- [Types](/docs/reference/golang/api/types.md) for type definitions used by these methods
- [Errors](/docs/reference/golang/api/errors.md) for error types
- [Connecting to SurrealDB](/docs/reference/golang/concepts/connecting-to-surrealdb.md) for connection patterns

---

Source: https://surrealdb.com/docs/reference/golang/api/core/session

# Session

The Session struct represents an isolated SurrealDB session on a WebSocket connection with its own authentication and namespace state.

The `Session` struct represents an additional SurrealDB session on a WebSocket connection. Each session has its own authentication state, namespace and database selection, and connection variables. Sessions require SurrealDB v3+ and a WebSocket connection.

`Session` satisfies the [`sendable`](/docs/reference/golang/api/types.md#sendable) constraint, so all generic functions like [`Query`](/docs/reference/golang/api/core/db.md#query), [`Select`](/docs/reference/golang/api/core/db.md#select), [`Create`](/docs/reference/golang/api/core/db.md#create), etc. accept `*Session` directly.

**Source:** [session.go](https://github.com/surrealdb/surrealdb.go/blob/main/session.go)

---

## Creating a session

### `db.Attach()` {#attach}

Creates a new session on the WebSocket connection. The session starts unauthenticated and without a selected namespace or database.

```go title="Syntax"
session, err := db.Attach(ctx)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*Session, error)`

Returns `ErrSessionsNotSupported` if the connection is not WebSocket.

#### Examples

```go
session, err := db.Attach(ctx)
if err != nil {
    log.Fatal(err)
}
defer session.Detach(ctx)

session.SignIn(ctx, surrealdb.Auth{Username: "root",
    Password: "secret"})
session.Use(ctx, "my_ns", "my_db")

results, err := surrealdb.Query[[]Person](ctx, session,
    "SELECT * FROM persons", nil)
```

---

## Properties

### `.ID()` {#id}

Returns the session's UUID.

```go title="Syntax"
id := session.ID()
```

**Returns:** [`*models.UUID`](/docs/reference/golang/api/values/uuid.md)

---

## Methods

### `.Detach()` {#detach}

Removes the session from the server. After calling `.Detach()`, the session cannot be used.

```go title="Syntax"
err := session.Detach(ctx)
```

**Returns:** `error`

Returns [`ErrSessionClosed`](/docs/reference/golang/api/errors.md#sentinel-errors) if the session has already been detached.

### `.Begin()` {#begin}

Starts a new interactive transaction within this session.

```go title="Syntax"
tx, err := session.Begin(ctx)
```

**Returns:** `(*Transaction, error)` - see [`Transaction`](/docs/reference/golang/api/core/transaction.md)

Returns [`ErrSessionClosed`](/docs/reference/golang/api/errors.md#sentinel-errors) if the session has been detached.

### `.SignIn()` {#signin}

Signs in an existing user within this session.

```go title="Syntax"
token, err := session.SignIn(ctx, authData)
```

**Returns:** `(string, error)`

### `.SignInWithRefresh()` {#signinwithrefresh}

Signs in with refresh token support within this session. SurrealDB v3+ only.

```go title="Syntax"
tokens, err := session.SignInWithRefresh(ctx, authData)
```

**Returns:** [`(*Tokens, error)`](/docs/reference/golang/api/types.md#tokens)

### `.SignUp()` {#signup}

Signs up a new record user within this session.

```go title="Syntax"
token, err := session.SignUp(ctx, authData)
```

**Returns:** `(string, error)`

### `.SignUpWithRefresh()` {#signupwithrefresh}

Signs up with refresh token support within this session. SurrealDB v3+ only.

```go title="Syntax"
tokens, err := session.SignUpWithRefresh(ctx, authData)
```

**Returns:** [`(*Tokens, error)`](/docs/reference/golang/api/types.md#tokens)

### `.Authenticate()` {#authenticate}

Authenticates this session with a JWT token.

```go title="Syntax"
err := session.Authenticate(ctx, token)
```

**Returns:** `error`

### `.Invalidate()` {#invalidate}

Invalidates the current authentication for this session.

```go title="Syntax"
err := session.Invalidate(ctx)
```

**Returns:** `error`

### `.Use()` {#use}

Selects the namespace and database for this session.

```go title="Syntax"
err := session.Use(ctx, ns, database)
```

**Returns:** `error`

### `.Let()` {#let}

Defines a variable scoped to this session.

```go title="Syntax"
err := session.Let(ctx, key, val)
```

**Returns:** `error`

### `.Unset()` {#unset}

Removes a variable from this session.

```go title="Syntax"
err := session.Unset(ctx, key)
```

**Returns:** `error`

### `.Info()` {#info}

Returns the record of the currently authenticated user in this session.

```go title="Syntax"
info, err := session.Info(ctx)
```

**Returns:** `(map[string]any, error)`

### `.Version()` {#version}

Returns the SurrealDB version information.

```go title="Syntax"
ver, err := session.Version(ctx)
```

**Returns:** [`(*VersionData, error)`](/docs/reference/golang/api/types.md#versiondata)

### `.LiveNotifications()` {#livenotifications}

Returns the notification channel for a live query.

```go title="Syntax"
ch, err := session.LiveNotifications(liveQueryID)
```

**Returns:** `(chan` [`connection.Notification`](/docs/reference/golang/api/types.md#notification)`, error)`

### `.CloseLiveNotifications()` {#closelivenotifications}

Closes the notification channel for a live query.

```go title="Syntax"
err := session.CloseLiveNotifications(liveQueryID)
```

**Returns:** `error`

---

## See also

- [DB](/docs/reference/golang/api/core/db.md) for the main client reference
- [Transaction](/docs/reference/golang/api/core/transaction.md) for session-scoped transactions
- [Multiple sessions](/docs/reference/golang/concepts/multiple-sessions.md) for session usage patterns
- [Errors](/docs/reference/golang/api/errors.md) for `ErrSessionClosed` and `ErrSessionsNotSupported`

---

Source: https://surrealdb.com/docs/reference/golang/api/core/transaction

# Transaction

The Transaction struct represents an interactive SurrealDB transaction that allows executing statements one at a time with commit or cancel control.

The `Transaction` struct represents an interactive SurrealDB transaction on a WebSocket connection. Unlike text-based transactions, interactive transactions allow executing statements one at a time and conditionally committing or canceling based on results. Transactions require SurrealDB v3+ and a WebSocket connection.

`Transaction` satisfies the [`sendable`](/docs/reference/golang/api/types.md#sendable) constraint, so all generic functions like [`Query`](/docs/reference/golang/api/core/db.md#query), [`Select`](/docs/reference/golang/api/core/db.md#select), [`Create`](/docs/reference/golang/api/core/db.md#create), etc. accept `*Transaction` directly. However, transactions do not support session state changes (authentication, namespace selection, variables) or [live queries](/docs/reference/golang/concepts/live-queries.md).

**Source:** [transaction.go](https://github.com/surrealdb/surrealdb.go/blob/main/transaction.go)

---

## Creating a transaction

### `db.Begin()` {#db-begin}

Starts a transaction on the default session.

```go title="Syntax"
tx, err := db.Begin(ctx)
```

**Returns:** `(*Transaction, error)`

Returns [`ErrTransactionsNotSupported`](/docs/reference/golang/api/errors.md#sentinel-errors) if the connection is not WebSocket.

### `session.Begin()` {#session-begin}

Starts a transaction within a specific session.

```go title="Syntax"
tx, err := session.Begin(ctx)
```

**Returns:** `(*Transaction, error)`

Returns [`ErrSessionClosed`](/docs/reference/golang/api/errors.md#sentinel-errors) if the session has been detached.

#### Examples

```go
tx, err := db.Begin(ctx)
if err != nil {
    log.Fatal(err)
}
defer tx.Cancel(ctx)

_, err = surrealdb.Create[any](ctx, tx, models.Table("events"), map[string]any{
    "type": "transfer",
    "amount": 100,
})
if err != nil {
    log.Fatal(err)
}

if err := tx.Commit(ctx); err != nil {
    log.Fatal(err)
}
```

---

## Properties

### `.ID()` {#id}

Returns the transaction's UUID.

```go title="Syntax"
id := tx.ID()
```

**Returns:** [`*models.UUID`](/docs/reference/golang/api/values/uuid.md)

### `.SessionID()` {#sessionid}

Returns the session UUID if the transaction was started within a [`Session`](/docs/reference/golang/api/core/session.md). Returns `nil` for transactions started on the default session.

```go title="Syntax"
sessionID := tx.SessionID()
```

**Returns:** [`*models.UUID`](/docs/reference/golang/api/values/uuid.md)

### `.IsClosed()` {#isclosed}

Returns whether the transaction has been committed or cancelled.

```go title="Syntax"
closed := tx.IsClosed()
```

**Returns:** `bool`

---

## Methods

### `.Commit()` {#commit}

Commits the transaction, making all changes permanent. After calling `.Commit()`, the transaction cannot be used.

```go title="Syntax"
err := tx.Commit(ctx)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
    </tbody>
</table>

**Returns:** `error`

Returns [`ErrTransactionClosed`](/docs/reference/golang/api/errors.md#sentinel-errors) if the transaction has already been committed or cancelled.

### `.Cancel()` {#cancel}

Cancels the transaction, discarding all changes. After calling `.Cancel()`, the transaction cannot be used.

It is safe to call `.Cancel()` on an already committed or cancelled transaction. It returns [`ErrTransactionClosed`](/docs/reference/golang/api/errors.md#sentinel-errors) but causes no harm, making it safe for use with `defer`.

```go title="Syntax"
err := tx.Cancel(ctx)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ctx</code> _(required)_</td>
            <td><code>context.Context</code></td>
            <td>Context for the operation.</td>
        </tr>
    </tbody>
</table>

**Returns:** `error`

---

## See also

- [DB](/docs/reference/golang/api/core/db.md) for starting transactions from the main client
- [Session](/docs/reference/golang/api/core/session.md) for starting transactions from sessions
- [Transactions](/docs/reference/golang/concepts/transactions.md) for transaction usage patterns
- [Errors](/docs/reference/golang/api/errors.md) for `ErrTransactionClosed` and `ErrTransactionsNotSupported`

---

Source: https://surrealdb.com/docs/reference/golang/api/errors

# Errors

The Go SDK provides structured error types and sentinel errors for handling SurrealDB failures.

The Go SDK uses Go's standard `error` interface with typed errors that can be inspected using `errors.As` and `errors.Is`. Errors fall into three categories: structured server errors, query-level errors, and sentinel errors for common failure conditions.

**Packages:**
- `github.com/surrealdb/surrealdb.go` (type aliases)
- `github.com/surrealdb/surrealdb.go/pkg/connection` (definitions)
- `github.com/surrealdb/surrealdb.go/pkg/constants` (sentinels)

---

## Error types

### `ServerError` {#servererror}

A structured error returned by SurrealDB v3. Contains a `Kind`, `Message`, `Details`, and an optional `Cause` chain. Use `errors.As` to extract it from any error returned by the SDK.

```go
type ServerError struct {
    Code    int
    Message string
    Kind    string
    Details any
    Cause   *ServerError
}
```

**Methods:**

- `.Error()` - returns the message, joining the cause chain with `": "`
- `.Unwrap()` - returns the `Cause`, enabling `errors.Unwrap()` traversal
- `.Is(target)` - returns `true` if `target` is a `ServerError`
- `.As(target)` - populates `target` if it is `*ServerError` or `**ServerError`

**Available as:** `surrealdb.ServerError` (type alias)

#### Examples

```go
import "errors"

_, err := surrealdb.Select[Person](ctx, db, models.NewRecordID("persons", "missing"))
if err != nil {
    var se *surrealdb.ServerError
    if errors.As(err, &se) {
        fmt.Println("Kind:", se.Kind)
        fmt.Println("Message:", se.Message)
        if se.Details != nil {
            fmt.Println("Details:", se.Details)
        }
    }
}
```

### `QueryError` {#queryerror}

Represents a per-statement error within a query result. Returned in the `Error` field of [`QueryResult`](/docs/reference/golang/api/types.md#queryresult) when an individual statement fails.

```go
type QueryError struct {
    Message string
}
```

**Methods:**

- `.Error()` - returns the error message
- `.Is(target)` - returns `true` if `target` is a `*QueryError`

#### Examples

```go
results, err := surrealdb.Query[[]any](ctx, db, "INVALID SQL", nil)

if errors.Is(err, &surrealdb.QueryError{}) {
    fmt.Println("Query contained errors")
}

for _, qr := range *results {
    if qr.Error != nil {
        fmt.Println("Statement error:", qr.Error.Message)
    }
}
```

### `RPCError` {#rpcerror}

An RPC level error with `Code`, `Message`, and `Description` fields. On SurrealDB v3, `ServerError` is preferred as it provides richer information.

**Available as:** `surrealdb.RPCError` (type alias, deprecated in favour of `ServerError`)

---

## Sentinel errors

These are defined in `github.com/surrealdb/surrealdb.go/pkg/constants`:

| Error | Value | Description |
|---|---|---|
| `ErrSessionsNotSupported` | `"sessions require WebSocket connection"` | Returned when calling `.Attach()` on a non-WebSocket connection |
| `ErrTransactionsNotSupported` | `"interactive transactions require WebSocket connection"` | Returned when calling `.Begin()` on a non-WebSocket connection |
| `ErrSessionClosed` | `"session already detached"` | Returned when using a session after `.Detach()` |
| `ErrTransactionClosed` | `"transaction already committed or canceled"` | Returned when using a transaction after `.Commit()` or `.Cancel()` |
| `ErrTimeout` | `"timeout"` | Returned when an operation times out |
| `ErrNoNamespaceOrDB` | `"namespace or database or both are not set"` | Returned when operations require a namespace/database |

### Examples

```go
import "github.com/surrealdb/surrealdb.go/pkg/constants"

session, err := db.Attach(ctx)
if errors.Is(err, constants.ErrSessionsNotSupported) {
    fmt.Println("Use a WebSocket connection for sessions")
}
```

---

## See also

- [Error handling](/docs/reference/golang/concepts/error-handling.md) for error handling patterns and retry guidance
- [Types reference](/docs/reference/golang/api/types.md) for [`QueryResult`](/docs/reference/golang/api/types.md#queryresult) containing `QueryError`
- [DB reference](/docs/reference/golang/api/core/db.md) for methods that return these errors
- [Session reference](/docs/reference/golang/api/core/session.md) for `ErrSessionClosed` and `ErrSessionsNotSupported`
- [Transaction reference](/docs/reference/golang/api/core/transaction.md) for `ErrTransactionClosed` and `ErrTransactionsNotSupported`

---

Source: https://surrealdb.com/docs/reference/golang/api/types

# Types

The Go SDK defines several types for authentication, query results, relationships, and data patching.

The Go SDK defines several types used by the client methods and data manipulation functions. These types are defined in the main `surrealdb` package and the `pkg/connection` package.

**Package:** `github.com/surrealdb/surrealdb.go`

**Source:** [types.go](https://github.com/surrealdb/surrealdb.go/blob/main/types.go)

---

## Authentication

### `Auth` {#auth}

Holds authentication credentials for signing in. The fields you populate determine the authentication level.

```go
type Auth struct {
    Namespace string `json:"NS,omitempty"`
    Database  string `json:"DB,omitempty"`
    Scope     string `json:"SC,omitempty"`
    Access    string `json:"AC,omitempty"`
    Username  string `json:"user,omitempty"`
    Password  string `json:"pass,omitempty"`
}
```

| Field | JSON key | When to use |
|---|---|---|
| `Namespace` | `NS` | Namespace-level, database-level, or record-level signin |
| `Database` | `DB` | Database-level or record-level signin |
| `Scope` | `SC` | Legacy scope-based signin (SurrealDB v1.x) |
| `Access` | `AC` | Record-level signin via a defined access method |
| `Username` | `user` | All signin levels |
| `Password` | `pass` | All signin levels |

You can also use `map[string]any` with the JSON keys directly, which allows passing additional fields required by access methods.

### `Tokens` {#tokens}

Contains access and refresh tokens returned by `.SignInWithRefresh()` and `.SignUpWithRefresh()`. Only available with `TYPE RECORD` access methods that have `WITH REFRESH` enabled (SurrealDB v3+).

```go
type Tokens struct {
    Access  string `cbor:"access"`
    Refresh string `cbor:"refresh"`
}
```

| Field | Description |
|---|---|
| `Access` | JWT token for authentication. Use with `.Authenticate()`. |
| `Refresh` | Refresh token (format: `surreal-refresh-...`). Use with `.SignInWithRefresh()` to obtain new tokens. |

---

## Query results

### `QueryResult` {#queryresult}

Represents one statement's result from a `Query` call. The type parameter `T` corresponds to the expected result type.

```go
type QueryResult[T any] struct {
    Status string      `json:"status"`
    Time   string      `json:"time"`
    Result T           `json:"result"`
    Error  *QueryError `json:"-"`
}
```

| Field | Description |
|---|---|
| `Status` | `"OK"` for success, `"ERR"` for failure |
| `Time` | Execution time for this statement |
| `Result` | The typed result data |
| `Error` | Non-nil when the statement failed. See [`QueryError`](/docs/reference/golang/api/errors.md#queryerror). |

### `QueryStmt` {#querystmt}

Represents a single query statement for use with `QueryRaw`. After execution, the `Result` field is populated and `.GetResult()` can unmarshal it into a destination type.

```go
type QueryStmt struct {
    SQL    string
    Vars   map[string]any
    Result QueryResult[cbor.RawMessage]
}
```

#### Methods

- `.GetResult(dest any) error`, unmarshals the raw result into `dest`

---

## Data types

### `PatchData` {#patchdata}

Represents a single JSON Patch operation for use with the `Patch` function.

```go
type PatchData struct {
    Op    string `json:"op"`
    Path  string `json:"path"`
    Value any    `json:"value"`
}
```

Supported `Op` values: `add`, `remove`, `replace`, `move`, `copy`, `test`.

### `Relationship` {#relationship}

Represents a graph edge between two records. Used with `Relate` and `InsertRelation`.

```go
type Relationship struct {
    ID       *models.RecordID `json:"id"`
    In       models.RecordID  `json:"in"`
    Out      models.RecordID  `json:"out"`
    Relation models.Table     `json:"relation"`
    Data     map[string]any   `json:"data"`
}
```

| Field | Type | Description |
|---|---|---|
| `ID` | [`*models.RecordID`](/docs/reference/golang/api/values/record-id.md) | The relation record ID. Auto-generated by [`Relate`](/docs/reference/golang/api/core/db.md#relate), optional for [`InsertRelation`](/docs/reference/golang/api/core/db.md#insertrelation). |
| `In` | [`models.RecordID`](/docs/reference/golang/api/values/record-id.md) | The source record. |
| `Out` | [`models.RecordID`](/docs/reference/golang/api/values/record-id.md) | The target record. |
| `Relation` | [`models.Table`](/docs/reference/golang/api/values/table.md) | The relation table name. |
| `Data` | `map[string]any` | Additional data to store on the relation. |

### `VersionData` {#versiondata}

Contains version information returned by `.Version()`.

```go
type VersionData struct {
    Version   string `json:"version"`
    Build     string `json:"build"`
    Timestamp string `json:"timestamp"`
}
```

### `Notification` {#notification}

Represents a live query notification received through a notification channel.

**Package:** `github.com/surrealdb/surrealdb.go/pkg/connection`

```go
type Notification struct {
    ID     *models.UUID `json:"id,omitempty"`
    Action Action       `json:"action"`
    Result interface{}  `json:"result"`
}
```

| Field | Type | Description |
|---|---|---|
| `ID` | [`*models.UUID`](/docs/reference/golang/api/values/uuid.md) | The live query UUID |
| `Action` | `Action` | One of `CREATE`, `UPDATE`, or `DELETE` |
| `Result` | `any` | The record data or JSON Patch diff |

---

## Type constraints

### `TableOrRecord` {#tableorrecord}

A type constraint used by data manipulation functions. Accepts any of the following types:

```go
type TableOrRecord interface {
    string | models.Table | models.RecordID | []models.Table | []models.RecordID
}
```

See [`Table`](/docs/reference/golang/api/values/table.md) and [`RecordID`](/docs/reference/golang/api/values/record-id.md) for these value types.

### `sendable` {#sendable}

A type constraint for types that can execute RPC requests. Satisfied by [`*DB`](/docs/reference/golang/api/core/db.md), [`*Session`](/docs/reference/golang/api/core/session.md), and [`*Transaction`](/docs/reference/golang/api/core/transaction.md).

```go
type sendable interface {
    *DB | *Session | *Transaction
}
```

### `liveQueryable` {#livequeryable}

A type constraint for types that support [live queries](/docs/reference/golang/concepts/live-queries.md). Satisfied by [`*DB`](/docs/reference/golang/api/core/db.md) and [`*Session`](/docs/reference/golang/api/core/session.md) (not [`*Transaction`](/docs/reference/golang/api/core/transaction.md)).

```go
type liveQueryable interface {
    *DB | *Session
}
```

---

## See also

- [DB reference](/docs/reference/golang/api/core/db.md) for methods that use these types
- [Errors reference](/docs/reference/golang/api/errors.md) for error types
- [Values reference](/docs/reference/golang/api/values/record-id.md) for value types like `RecordID` and `Table`

---

Source: https://surrealdb.com/docs/reference/golang/api/values/datetime

# CustomDateTime

The CustomDateTime type wraps time.Time with SurrealDB-compatible CBOR encoding at nanosecond precision.

The `CustomDateTime` struct wraps Go's `time.Time` and handles CBOR encoding with tag 12 as specified by SurrealDB. It preserves nanosecond precision and stores datetimes as a `[seconds, nanoseconds]` pair.

**Package:** `github.com/surrealdb/surrealdb.go/pkg/models`

**Source:** [pkg/models/datetime.go](https://github.com/surrealdb/surrealdb.go/blob/main/pkg/models/datetime.go)

---

## Definition

```go
type CustomDateTime struct {
    time.Time
}
```

`CustomDateTime` embeds `time.Time`, so all standard `time.Time` methods are available directly.

---

## Methods

### `.String()` {#string}

Returns the datetime formatted as `"2006-01-02T15:04:05Z"` in UTC.

```go title="Syntax"
s := dt.String()
```

**Returns:** `string`

### `.SurrealString()` {#surrealstring}

Returns the SurrealQL representation: `<datetime> '2006-01-02T15:04:05Z'`.

```go title="Syntax"
s := dt.SurrealString()
```

**Returns:** `string`

### `.IsZero()` {#iszero}

Returns `true` if the datetime is nil or the zero time.

```go title="Syntax"
zero := dt.IsZero()
```

**Returns:** `bool`

---

## Usage

```go
import "github.com/surrealdb/surrealdb.go/pkg/models"

now := models.CustomDateTime{Time: time.Now()}

type Event struct {
    ID        *models.RecordID     `json:"id,omitempty"`
    CreatedAt models.CustomDateTime `json:"created_at"`
}
```

Zero-valued `CustomDateTime` is encoded as CBOR tag 6 (`NONE`).

---

## See also

- [RecordID](/docs/reference/golang/api/values/record-id.md) for the record identifier type
- [Duration](/docs/reference/golang/api/values/duration.md) for the duration type
- [Value types](/docs/reference/golang/concepts/value-types.md) for the full type mapping
- [Data manipulation](/docs/reference/golang/concepts/data-manipulation.md) for using datetime values in CRUD operations
- [SurrealQL datetime type](/docs/reference/query-language/language-primitives/data-types/datetimes.md) for the underlying data model

---

Source: https://surrealdb.com/docs/reference/golang/api/values/duration

# CustomDuration

The CustomDuration type wraps time.Duration with SurrealDB-compatible CBOR encoding and human-readable formatting.

The `CustomDuration` struct wraps Go's `time.Duration` and handles CBOR encoding with tag 14 as specified by SurrealDB. It stores durations as a `[seconds, nanoseconds]` pair and formats them using SurrealDB's human-readable syntax (e.g., `1d2h30m`).

**Package:** `github.com/surrealdb/surrealdb.go/pkg/models`

**Source:** [pkg/models/duration.go](https://github.com/surrealdb/surrealdb.go/blob/main/pkg/models/duration.go)

---

## Definition

```go
type CustomDuration struct {
    time.Duration
}
```

`CustomDuration` embeds `time.Duration`, so all standard `time.Duration` methods are available directly.

---

## Methods

### `.String()` {#string}

Returns the duration formatted in SurrealDB syntax (e.g., `2h30m`, `1d12h`, `500ms`).

```go title="Syntax"
s := dur.String()
```

**Returns:** `string`

### `.ToCustomDurationString()` {#tocustomdurationstring}

Converts to a `CustomDurationString` value.

```go title="Syntax"
ds := dur.ToCustomDurationString()
```

**Returns:** `CustomDurationString`

---

## Related types

### `CustomDurationString` {#customdurationstring}

A string type for durations in SurrealDB format (e.g., `"1d2h30m"`). Encoded with CBOR tag 13.

```go
type CustomDurationString string
```

#### Methods

- `.String()` - returns the string value
- `.ToDuration()` - parses into `time.Duration`
- `.ToCustomDuration()` - converts to `CustomDuration`

---

## Helper functions

### `FormatDuration` {#formatduration}

Formats nanoseconds into SurrealDB duration syntax.

```go title="Syntax"
s := models.FormatDuration(ns)
```

**Returns:** `string`

### `ParseDuration` {#parseduration}

Parses a SurrealDB duration string into nanoseconds.

```go title="Syntax"
ns, err := models.ParseDuration("1d2h30m")
```

**Returns:** `(int64, error)`

Supported units: `y`, `w`, `d`, `h`, `m`, `s`, `ms`, `us`/`µs`, `ns`.

---

## Usage

```go
import "github.com/surrealdb/surrealdb.go/pkg/models"

dur := models.CustomDuration{Duration: 2*time.Hour + 30*time.Minute}
fmt.Println(dur.String()) // "2h30m"

type Session struct {
    ID      *models.RecordID      `json:"id,omitempty"`
    Timeout models.CustomDuration  `json:"timeout"`
}
```

---

## See also

- [RecordID](/docs/reference/golang/api/values/record-id.md) for the record identifier type
- [CustomDateTime](/docs/reference/golang/api/values/datetime.md) for the datetime type
- [Value types](/docs/reference/golang/concepts/value-types.md) for the full type mapping
- [Data manipulation](/docs/reference/golang/concepts/data-manipulation.md) for using duration values in CRUD operations
- [SurrealQL duration type](/docs/reference/query-language/language-primitives/data-types/durations.md) for the underlying data model

---

Source: https://surrealdb.com/docs/reference/golang/api/values/geometry

# Geometry

The Go SDK provides typed geometry types for all SurrealDB geometry values including points, lines, polygons, and collections.

The SDK provides typed geometry structs that map to SurrealDB's GeoJSON-based geometry types. Each type handles CBOR encoding with the appropriate tag number.

**Package:** `github.com/surrealdb/surrealdb.go/pkg/models`

**Source:** [pkg/models/geometry.go](https://github.com/surrealdb/surrealdb.go/blob/main/pkg/models/geometry.go)

---

## Types

### `GeometryPoint` {#geometrypoint}

A geographic point with longitude and latitude coordinates.

```go
type GeometryPoint struct {
    Longitude float64
    Latitude  float64
}
```

CBOR tag: 88

#### Methods

- `.GetCoordinates()` - returns `[2]float64{Longitude, Latitude}`

#### Examples

```go
point := models.GeometryPoint{Longitude: -0.118, Latitude: 51.509}
```

### `GeometryLine` {#geometryline}

A line consisting of two or more points.

```go
type GeometryLine []GeometryPoint
```

CBOR tag: 89

### `GeometryPolygon` {#geometrypolygon}

A polygon consisting of one or more closed line rings.

```go
type GeometryPolygon []GeometryLine
```

CBOR tag: 90

### `GeometryMultiPoint` {#geometrymultipoint}

A collection of points.

```go
type GeometryMultiPoint []GeometryPoint
```

CBOR tag: 91

### `GeometryMultiLine` {#geometrymultiline}

A collection of lines.

```go
type GeometryMultiLine []GeometryLine
```

CBOR tag: 92

### `GeometryMultiPolygon` {#geometrymultipolygon}

A collection of polygons.

```go
type GeometryMultiPolygon []GeometryPolygon
```

CBOR tag: 93

### `GeometryCollection` {#geometrycollection}

A heterogeneous collection of geometry objects.

```go
type GeometryCollection []any
```

CBOR tag: 94

---

## Usage

```go
import "github.com/surrealdb/surrealdb.go/pkg/models"

type Location struct {
    ID       *models.RecordID      `json:"id,omitempty"`
    Name     string                `json:"name"`
    Position models.GeometryPoint  `json:"position"`
    Boundary models.GeometryPolygon `json:"boundary"`
}

line := models.GeometryLine{
    {Longitude: -0.118, Latitude: 51.509},
    {Longitude: -0.076, Latitude: 51.508},
}

polygon := models.GeometryPolygon{
    models.GeometryLine{
        {Longitude: -0.12, Latitude: 51.50},
        {Longitude: -0.08, Latitude: 51.50},
        {Longitude: -0.08, Latitude: 51.52},
        {Longitude: -0.12, Latitude: 51.52},
        {Longitude: -0.12, Latitude: 51.50},
    },
}
```

---

## See also

- [RecordID](/docs/reference/golang/api/values/record-id.md) for the record identifier type
- [Value types](/docs/reference/golang/concepts/value-types.md) for the full type mapping
- [Data manipulation](/docs/reference/golang/concepts/data-manipulation.md) for using geometry values in CRUD operations
- [SurrealQL geometry types](/docs/reference/query-language/language-primitives/data-types/geometries.md) for the underlying data model

---

Source: https://surrealdb.com/docs/reference/golang/api/values/range

# Range

The Range type represents a SurrealDB range value with inclusive or exclusive bounds.

The `Range` generic struct represents a SurrealDB range value with configurable bound types. Ranges can have inclusive or exclusive bounds on either end.

**Package:** `github.com/surrealdb/surrealdb.go/pkg/models`

**Source:** [pkg/models/range.go](https://github.com/surrealdb/surrealdb.go/blob/main/pkg/models/range.go)

---

## Definitions

### `Range` {#range-struct}

```go
type Range[T any, TBeg Bound[T], TEnd Bound[T]] struct {
    Begin *TBeg
    End   *TEnd
}
```

CBOR tag: 49

### `BoundIncluded` {#boundincluded}

An inclusive bound (the value is included in the range).

```go
type BoundIncluded[T any] struct {
    Value T
}
```

CBOR tag: 50

### `BoundExcluded` {#boundexcluded}

An exclusive bound (the value is excluded from the range).

```go
type BoundExcluded[T any] struct {
    Value T
}
```

CBOR tag: 51

### `Bound` {#bound}

The constraint interface for bound types.

```go
type Bound[T any] interface {
    BoundIncluded[T] | BoundExcluded[T]
}
```

---

## Related types

### `RecordRangeID` {#recordrangeid}

A range scoped to a specific table, used for range-based record selection.

```go
type RecordRangeID[T any, TBeg Bound[T], TEnd Bound[T]] struct {
    Range[T, TBeg, TEnd]
    Table Table
}
```

---

## Methods

### `.String()` {#string}

Returns the string representation of the range (e.g., `1..10`, `1>..=10`).

**Returns:** `string`

### `.GetJoinString()` {#getjoinstring}

Returns the range operator string (e.g., `..`, `>..`, `..=`, `>..=`).

**Returns:** `string`

---

## See also

- [Table](/docs/reference/golang/api/values/table.md) for the table name type used in `RecordRangeID`
- [Value types](/docs/reference/golang/concepts/value-types.md) for the full type mapping
- [Data manipulation](/docs/reference/golang/concepts/data-manipulation.md) for using ranges in queries
- [SurrealQL ranges](/docs/reference/query-language/language-primitives/data-types/record-ids.md#record-ranges) for the underlying data model

---

Source: https://surrealdb.com/docs/reference/golang/api/values/record-id

# RecordID

The RecordID type represents a SurrealDB record identifier consisting of a table name and an ID value.

The `RecordID` struct represents a SurrealDB record identifier. A record ID consists of a table name and an identifier value, providing a typed way to reference records without ambiguity from string parsing.

**Package:** `github.com/surrealdb/surrealdb.go/pkg/models`

**Source:** [pkg/models/record_id.go](https://github.com/surrealdb/surrealdb.go/blob/main/pkg/models/record_id.go)

---

## Definition

```go
type RecordID struct {
    Table string
    ID    any
}
```

The `ID` field can be any CBOR-serializable value: a string, integer, array, or map.

---

## Constructors

### `NewRecordID` {#newrecordid}

Creates a new `RecordID` with the given table name and ID.

```go title="Syntax"
id := models.NewRecordID(tableName, id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>tableName</code> _(required)_</td>
            <td><code>string</code></td>
            <td>The table name.</td>
        </tr>
        <tr>
            <td><code>id</code> _(required)_</td>
            <td><code>any</code></td>
            <td>The record identifier value.</td>
        </tr>
    </tbody>
</table>

**Returns:** `RecordID`

#### Examples

```go
id := models.NewRecordID("persons", "tobie")

numericID := models.NewRecordID("events", 42)

compositeID := models.NewRecordID("access", []any{"us", 2026})
```

### `ParseRecordID` {#parserecordid}

Parses a string of the form `"table:id"` into a `RecordID`. Only works for simple IDs without colons.

```go title="Syntax"
id, err := models.ParseRecordID(idStr)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>idStr</code> _(required)_</td>
            <td><code>string</code></td>
            <td>A string in the form <code>"table:id"</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `(*RecordID, error)`

Returns `ErrBadRecordID` if the string does not contain exactly one colon.

---

## Methods

### `.String()` {#string}

Returns the string representation of the record ID in the form `"table:id"`.

```go title="Syntax"
s := id.String()
```

**Returns:** `string`

### `.SurrealString()` {#surrealstring}

Returns the SurrealQL representation of the record ID in the form `r'table:id'`.

```go title="Syntax"
s := id.SurrealString()
```

**Returns:** `string`

---

## CBOR encoding

`RecordID` is encoded as CBOR tag 8 containing a two-element array `[table, id]`. The SDK handles marshaling and unmarshaling automatically when you use `RecordID` in structs.

```go
type Person struct {
    ID      *models.RecordID `json:"id,omitempty"`
    Name    string           `json:"name"`
}
```

---

## See also

- [Table](/docs/reference/golang/api/values/table.md) for the table name type
- [Value types](/docs/reference/golang/concepts/value-types.md) for the full type mapping
- [Data manipulation](/docs/reference/golang/concepts/data-manipulation.md) for using record IDs in CRUD operations
- [SurrealQL record IDs](/docs/reference/query-language/language-primitives/data-types/record-ids.md) for the underlying data model

---

Source: https://surrealdb.com/docs/reference/golang/api/values/table

# Table

The Table type represents a SurrealDB table name used to scope data operations to an entire table.

The `Table` type is a named string type representing a SurrealDB table name. It is used with data manipulation functions like `Select`, `Create`, `Insert`, and `Delete` to target all records in a table.

**Package:** `github.com/surrealdb/surrealdb.go/pkg/models`

**Source:** [pkg/models/table.go](https://github.com/surrealdb/surrealdb.go/blob/main/pkg/models/table.go)

---

## Definition

```go
type Table string
```

---

## Methods

### `.String()` {#string}

Returns the table name as a plain string.

```go title="Syntax"
s := table.String()
```

**Returns:** `string`

---

## Usage

```go
import "github.com/surrealdb/surrealdb.go/pkg/models"

persons, err := surrealdb.Select[[]Person](ctx, db, models.Table("persons"))

_, err = surrealdb.Insert[Person](ctx, db, models.Table("persons"), data)
```

`Table` is encoded as CBOR tag 7 when sent over the wire.

---

## See also

- [RecordID](/docs/reference/golang/api/values/record-id.md) for targeting specific records
- [Data manipulation](/docs/reference/golang/concepts/data-manipulation.md) for using tables in CRUD operations

---

Source: https://surrealdb.com/docs/reference/golang/api/values/uuid

# UUID

The UUID type represents a UUID v4 or v7 value with CBOR tag 37 encoding for SurrealDB.

The `UUID` struct wraps the `github.com/gofrs/uuid` package and handles CBOR encoding with tag 37 as specified by SurrealDB. It supports both UUID v4 and v7 values.

**Package:** `github.com/surrealdb/surrealdb.go/pkg/models`

**Source:** [pkg/models/uuid.go](https://github.com/surrealdb/surrealdb.go/blob/main/pkg/models/uuid.go)

---

## Definition

```go
type UUID struct {
    uuid.UUID
}
```

`UUID` embeds `github.com/gofrs/uuid.UUID`, so all methods from that package are available directly.

---

## Related types

### `UUIDString` {#uuidstring}

A string type for UUID values that prefer string representation. Encoded with CBOR tag 9.

```go
type UUIDString string
```

---

## CBOR encoding

`UUID` is encoded as CBOR tag 37 containing the 16-byte binary representation. `UUIDString` is encoded as CBOR tag 9 containing the string representation.

---

## Usage

`UUID` is returned by live query functions and session/transaction creation. You typically receive it from the SDK rather than constructing it manually.

```go
liveID, err := surrealdb.Live(ctx, db, models.Table("persons"), false)
fmt.Println(liveID.String())
```

---

## See also

- [Live queries](/docs/reference/golang/concepts/live-queries.md) for live query UUIDs
- [Session](/docs/reference/golang/api/core/session.md#id) for session UUIDs
- [Transaction](/docs/reference/golang/api/core/transaction.md#id) for transaction UUIDs
- [Value types](/docs/reference/golang/concepts/value-types.md) for the full type mapping
- [SurrealQL UUID type](/docs/reference/query-language/language-primitives/data-types/uuids.md) for the underlying data model

---

Source: https://surrealdb.com/docs/reference/golang/concepts/authentication

# Authentication

The Go SDK provides methods for signing in, signing up, and managing authentication at root, namespace, database, and record levels.

The Go SDK supports signing in at different access levels, signing up record users, and managing authentication tokens. Authentication is required before most operations and determines what data the connection can access.

This page covers the different authentication levels, how to use refresh tokens, and how to manage authentication state on a connection.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/db.md#signin"><code>db.SignIn(ctx, authData)</code></a></td>
			<td scope="row" data-label="Description">Signs in an existing user and returns a JWT token</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/db.md#signinwithrefresh"><code>db.SignInWithRefresh(ctx, authData)</code></a></td>
			<td scope="row" data-label="Description">Signs in and returns both an access token and a refresh token</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/db.md#signup"><code>db.SignUp(ctx, authData)</code></a></td>
			<td scope="row" data-label="Description">Signs up a new record user and returns a JWT token</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/db.md#signupwithrefresh"><code>db.SignUpWithRefresh(ctx, authData)</code></a></td>
			<td scope="row" data-label="Description">Signs up a new record user and returns both tokens</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/db.md#authenticate"><code>db.Authenticate(ctx, token)</code></a></td>
			<td scope="row" data-label="Description">Authenticates the connection with a JWT token</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/db.md#invalidate"><code>db.Invalidate(ctx)</code></a></td>
			<td scope="row" data-label="Description">Invalidates the current authentication</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/db.md#info"><code>db.Info(ctx)</code></a></td>
			<td scope="row" data-label="Description">Returns the record of the currently authenticated user</td>
		</tr>
	</tbody>
</table>

## Authentication levels

SurrealDB supports four authentication levels. The fields you provide in the authentication data determine which level is used.

| Level | Required fields | Access to |
|---|---|---|
| Root | `Username`, `Password` | All namespaces and databases |
| Namespace | `Namespace`, `Username`, `Password` | All databases in the namespace |
| Database | `Namespace`, `Database`, `Username`, `Password` | A single database |
| Record | `Namespace`, `Database`, `Access`, `Username`, `Password` | Records determined by the access method |

You can provide credentials using either the [`Auth`](/docs/reference/golang/api/types.md#auth) struct or a `map[string]any`.

## Signing in as a system user

To sign in as a root, namespace, or database user, provide the appropriate fields. The level is determined by which fields are set.

```go
token, err := db.SignIn(ctx, surrealdb.Auth{
	Username: "root",
	Password: "secret",
})
```

```go
token, err := db.SignIn(ctx, surrealdb.Auth{
	Namespace: "my_ns",
	Database:  "my_db",
	Username:  "db_user",
	Password:  "db_pass",
})
```

## Signing in as a record user

Record-level authentication requires the `Access` field, which specifies which [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md) method to use.

```go
token, err := db.SignIn(ctx, surrealdb.Auth{
	Namespace: "my_ns",
	Database:  "my_db",
	Access:    "user_access",
	Username:  "tobie",
	Password:  "s3cret",
})
```

You can also use a `map[string]any` to pass additional fields required by the access method:

```go
token, err := db.SignIn(ctx, map[string]any{
	"NS":   "my_ns",
	"DB":   "my_db",
	"AC":   "user_access",
	"user": "tobie",
	"pass": "s3cret",
})
```

## Signing up new record users

The `.SignUp()` method creates a new record user using a `DEFINE ACCESS ... TYPE RECORD` access method. The access method must be defined before calling `.SignUp()`.

```go
token, err := db.SignUp(ctx, map[string]any{
	"NS":    "my_ns",
	"DB":    "my_db",
	"AC":    "user_access",
	"user":  "new_user",
	"pass":  "s3cret",
	"email": "user@example.com",
})
```

## Using refresh tokens

SurrealDB v3 supports refresh tokens for `TYPE RECORD` access methods that have `WITH REFRESH` enabled. Use `.SignInWithRefresh()` or `.SignUpWithRefresh()` to receive both an access token and a refresh token.

```go
tokens, err := db.SignInWithRefresh(ctx, map[string]any{
	"NS":   "my_ns",
	"DB":   "my_db",
	"AC":   "user_access",
	"user": "tobie",
	"pass": "s3cret",
})
```

The returned [`Tokens`](/docs/reference/golang/api/types.md#tokens) contains an `Access` field (JWT) and a `Refresh` field. To obtain new tokens without re-entering credentials, pass the refresh token:

```go
newTokens, err := db.SignInWithRefresh(ctx, map[string]any{
	"NS":      "my_ns",
	"DB":      "my_db",
	"AC":      "user_access",
	"refresh": tokens.Refresh,
})
```

> [!NOTE]
> Refresh tokens are only available with `TYPE RECORD` access methods that have `WITH REFRESH` enabled (SurrealDB v3+).

## Using bearer access

For `TYPE BEARER` access methods (SurrealDB v3+), use the `key` parameter with a bearer key obtained from `ACCESS ... GRANT`. No username or password is required.

```go
token, err := db.SignIn(ctx, map[string]any{
	"NS":  "my_ns",
	"DB":  "my_db",
	"AC":  "bearer_api",
	"key": bearerKey,
})
```

## Authenticating with an existing token

Use `.Authenticate()` to apply a previously obtained JWT to the connection. This is useful when restoring a session from a stored token or transferring authentication to a new connection.

```go
if err := db.Authenticate(ctx, token); err != nil {
	log.Fatal(err)
}
```

## Invalidating authentication

Call `.Invalidate()` to remove the current authentication from the connection. After calling this, the connection returns to an unauthenticated state.

```go
if err := db.Invalidate(ctx); err != nil {
	log.Fatal(err)
}
```

## Retrieving user information

The `.Info()` method returns the record of the currently authenticated user. This is only available when signed in as a record user.

```go
info, err := db.Info(ctx)
if err != nil {
	log.Fatal(err)
}
fmt.Println(info)
```

## Learn more

- [DB API reference](/docs/reference/golang/api/core/db.md) for complete method signatures and parameters
- [Types reference](/docs/reference/golang/api/types.md) for `Auth` and `Tokens` type definitions
- [Connecting to SurrealDB](/docs/reference/golang/concepts/connecting-to-surrealdb.md) for connection protocols and their effect on authentication
- [DEFINE ACCESS statement](/docs/reference/query-language/statements/define/access.md) for configuring access methods
- [Security best practices](/docs/learn/security/best-practices/security-best-practices.md) for token and session duration configuration

---

Source: https://surrealdb.com/docs/reference/golang/concepts/connecting-to-surrealdb

# Connecting to SurrealDB

The Go SDK supports connecting to SurrealDB over WebSocket or HTTP using a URL-based connection factory.

The Go SDK supports connecting to SurrealDB over WebSocket or HTTP. The `FromEndpointURLString` factory function inspects the URL scheme and creates the appropriate connection automatically.

This page covers how to create, configure, and manage connections to a SurrealDB instance.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Function</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#fromendpointurlstring"><code>surrealdb.FromEndpointURLString(ctx, url)</code></a></td>
			<td scope="row" data-label="Description">Creates a connection from a URL string</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#fromconnection"><code>surrealdb.FromConnection(ctx, conn)</code></a></td>
			<td scope="row" data-label="Description">Creates a client from a custom connection implementation</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#close"><code>db.Close(ctx)</code></a></td>
			<td scope="row" data-label="Description">Closes the connection and releases resources</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#use"><code>db.Use(ctx, ns, db)</code></a></td>
			<td scope="row" data-label="Description">Selects a namespace and database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#version"><code>db.Version(ctx)</code></a></td>
			<td scope="row" data-label="Description">Returns the version of the connected SurrealDB instance</td>
		</tr>
	</tbody>
</table>

## Opening a connection

Use `FromEndpointURLString` to create a new client and connect to a SurrealDB instance. The function accepts a `context.Context` for cancellation and a URL string that determines the connection type.

```go
ctx := context.Background()

db, err := surrealdb.FromEndpointURLString(ctx, "ws://localhost:8000")
if err != nil {
	log.Fatal(err)
}
defer db.Close(ctx)
```

The context controls how long the connection attempt blocks. You can use `context.WithTimeout` to limit the connection time on unreliable networks:

```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

db, err := surrealdb.FromEndpointURLString(ctx, "ws://localhost:8000")
```

## Connection string protocols

The URL scheme determines the connection type and its capabilities.

| Scheme | Connection type | Description |
|---|---|---|
| `ws://` | WebSocket | Unencrypted stateful connection |
| `wss://` | WebSocket | TLS-encrypted stateful connection |
| `http://` | HTTP | Unencrypted stateless connection |
| `https://` | HTTP | TLS-encrypted stateless connection |
| `mem://` | Memory | In-memory [embedded instance](/docs/reference/golang/embedding.md) |

WebSocket connections are long-lived and stateful. They support all SDK features including [live queries](/docs/reference/golang/concepts/live-queries.md), [sessions](/docs/reference/golang/concepts/multiple-sessions.md), and [transactions](/docs/reference/golang/concepts/transactions.md).

HTTP connections are stateless. Each request is independent and requires its own authentication token. [Live queries](/docs/reference/golang/concepts/live-queries.md), [sessions](/docs/reference/golang/concepts/multiple-sessions.md), and [transactions](/docs/reference/golang/concepts/transactions.md) are not available over HTTP.

## Using a custom connection

If you need to configure the underlying connection (for example, custom TLS settings or a specific WebSocket implementation), you can create a connection manually and pass it to [`FromConnection`](/docs/reference/golang/api/core/db.md#fromconnection):

```go
import (
	"net/url"

	"github.com/surrealdb/surrealdb.go/pkg/connection"
	"github.com/surrealdb/surrealdb.go/pkg/connection/gorillaws"
)

endpoint, _ := url.Parse("ws://localhost:8000/rpc")
conf := connection.NewConfig(endpoint)
conn := gorillaws.New(conf)

db, err := surrealdb.FromConnection(ctx, conn)
```

[`FromConnection`](/docs/reference/golang/api/core/db.md#fromconnection) calls `.Connect(ctx)` on the connection for you, so you do not need to call it separately. For auto-reconnecting connections, see [Reliable connections](/docs/reference/golang/concepts/reliable-connections.md).

## Selecting a namespace and database

After connecting, use `.Use()` to select the namespace and database you want to work with. Most operations require a namespace and database to be selected first.

```go
if err := db.Use(ctx, "my_namespace", "my_database"); err != nil {
	log.Fatal(err)
}
```

You can call `.Use()` multiple times to switch between namespaces and databases on the same connection.

## Effect of connection protocol on token and session duration

WebSocket connections (`ws://`, `wss://`) are long-lived and stateful. After authentication, the session persists for the lifetime of the connection. The session duration defaults to `NONE`, meaning it never expires unless configured otherwise.

HTTP connections (`http://`, `https://`) are stateless. Each request requires its own authentication token. The token duration defaults to 1 hour.

You can configure token and session durations using the `DURATION` clause in [`DEFINE ACCESS METHOD`](/docs/reference/query-language/statements/define/access.md) or [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md) statements.

> [!NOTE]
> Learn more about token and session duration in the [security best practices](/docs/learn/security/best-practices/security-best-practices.md#expiration) documentation.

## Closing a connection

Call `.Close()` to release the underlying connection resources when you are done.

```go
db.Close(ctx)
```

Use `defer db.Close(ctx)` immediately after creating the connection to ensure cleanup happens even when errors occur.

## Learn more

- [DB API reference](/docs/reference/golang/api/core/db.md) for complete method signatures and parameters
- [Authentication](/docs/reference/golang/concepts/authentication.md) for signing in and managing user sessions
- [Error handling](/docs/reference/golang/concepts/error-handling.md) for handling connection and authentication errors

---

Source: https://surrealdb.com/docs/reference/golang/concepts/data-manipulation

# Data manipulation

The Go SDK provides generic functions for selecting, creating, updating, and deleting records in SurrealDB.

The Go SDK provides generic top-level functions for common CRUD operations on records and tables. These functions work with [`*DB`](/docs/reference/golang/api/core/db.md), [`*Session`](/docs/reference/golang/api/core/session.md), and [`*Transaction`](/docs/reference/golang/api/core/transaction.md) through the [`sendable`](/docs/reference/golang/api/types.md#sendable) constraint, and return typed results through Go generics.

This page covers how to target tables and records, and how to select, create, insert, update, merge, patch, and delete data.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Function</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#select"><code>surrealdb.Select[T](ctx, s, what)</code></a></td>
			<td scope="row" data-label="Description">Selects all records from a table, or a specific record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#create"><code>surrealdb.Create[T](ctx, s, what, data)</code></a></td>
			<td scope="row" data-label="Description">Creates a new record with optional data</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#insert"><code>surrealdb.Insert[T](ctx, s, table, data)</code></a></td>
			<td scope="row" data-label="Description">Inserts one or multiple records into a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#insertrelation"><code>surrealdb.InsertRelation[T](ctx, s, rel)</code></a></td>
			<td scope="row" data-label="Description">Inserts a relation record between two records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#relate"><code>surrealdb.Relate[T](ctx, s, rel)</code></a></td>
			<td scope="row" data-label="Description">Creates a relation with an auto-generated ID</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#update"><code>surrealdb.Update[T](ctx, s, what, data)</code></a></td>
			<td scope="row" data-label="Description">Replaces the entire content of a record or all records in a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#upsert"><code>surrealdb.Upsert[T](ctx, s, what, data)</code></a></td>
			<td scope="row" data-label="Description">Creates a record if it does not exist, or replaces it entirely</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#merge"><code>surrealdb.Merge[T](ctx, s, what, data)</code></a></td>
			<td scope="row" data-label="Description">Merges data into a record, preserving unmentioned fields</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#patch"><code>surrealdb.Patch(ctx, s, what, patches)</code></a></td>
			<td scope="row" data-label="Description">Applies JSON Patch operations to a record or table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#delete"><code>surrealdb.Delete[T](ctx, s, what)</code></a></td>
			<td scope="row" data-label="Description">Deletes a specific record or all records from a table</td>
		</tr>
	</tbody>
</table>

## Targeting tables and records

Most data manipulation functions accept a `what` parameter that determines the scope of the operation. You can pass a [`Table`](/docs/reference/golang/api/values/table.md) to target all records in a table, or a [`RecordID`](/docs/reference/golang/api/values/record-id.md) to target a specific record.

```go
import "github.com/surrealdb/surrealdb.go/pkg/models"

all, err := surrealdb.Select[[]Person](ctx, db, models.Table("persons"))

one, err := surrealdb.Select[Person](ctx, db, models.NewRecordID("persons", "tobie"))
```

When a [`Table`](/docs/reference/golang/api/values/table.md) is passed, operations that return data return a slice. When a [`RecordID`](/docs/reference/golang/api/values/record-id.md) is passed, they return a single value. Use the appropriate type parameter to match.

## Selecting records

[`Select`](/docs/reference/golang/api/core/db.md#select) retrieves records from the database. Pass a [`Table`](/docs/reference/golang/api/values/table.md) to get all records, or a [`RecordID`](/docs/reference/golang/api/values/record-id.md) to get a single record.

```go
persons, err := surrealdb.Select[[]Person](ctx, db, models.Table("persons"))
if err != nil {
	log.Fatal(err)
}

tobie, err := surrealdb.Select[Person](ctx, db, models.NewRecordID("persons", "tobie"))
if err != nil {
	log.Fatal(err)
}
```

## Creating records

[`Create`](/docs/reference/golang/api/core/db.md#create) creates a new record. Pass a [`Table`](/docs/reference/golang/api/values/table.md) to generate a random ID, or a [`RecordID`](/docs/reference/golang/api/values/record-id.md) to specify the ID explicitly. The data can be a struct or a map.

```go
person, err := surrealdb.Create[Person](ctx, db, models.Table("persons"), Person{
	Name:    "Tobie",
	Surname: "Morgan Hitchcock",
})

specific, err := surrealdb.Create[Person](ctx, db, models.NewRecordID("persons", "tobie"), map[string]any{
	"name":    "Tobie",
	"surname": "Morgan Hitchcock",
})
```

## Inserting records

[`Insert`](/docs/reference/golang/api/core/db.md#insert) inserts one or more records into a table. This is useful for bulk operations.

```go
persons, err := surrealdb.Insert[Person](ctx, db, models.Table("persons"), []map[string]any{
	{"name": "Alice", "age": 30},
	{"name": "Bob", "age": 25},
})
```

## Creating relations

The SDK provides two ways to create graph edges between records.

[`Relate`](/docs/reference/golang/api/core/db.md#relate) creates a relation with an auto-generated ID. The [`Relationship.ID`](/docs/reference/golang/api/types.md#relationship) field is ignored.

```go
rel, err := surrealdb.Relate[map[string]any](ctx, db, &surrealdb.Relationship{
	In:       models.NewRecordID("persons", "tobie"),
	Out:      models.NewRecordID("posts", "first"),
	Relation: models.Table("wrote"),
	Data:     map[string]any{"created_at": "2026-01-01T00:00:00Z"},
})
```

[`InsertRelation`](/docs/reference/golang/api/core/db.md#insertrelation) works like [`Insert`](/docs/reference/golang/api/core/db.md#insert) but for relation tables. It allows you to specify the ID explicitly via the [`Relationship.ID`](/docs/reference/golang/api/types.md#relationship) field.

```go
rel, err := surrealdb.InsertRelation[map[string]any](ctx, db, &surrealdb.Relationship{
	In:       models.NewRecordID("persons", "tobie"),
	Out:      models.NewRecordID("posts", "first"),
	Relation: models.Table("wrote"),
})
```

## Replacing records

[`Update`](/docs/reference/golang/api/core/db.md#update) replaces the entire content of a record or all records in a table. Fields not included in the new data are removed.

```go
updated, err := surrealdb.Update[Person](ctx, db, models.NewRecordID("persons", "tobie"), Person{
	Name:    "Tobie",
	Surname: "Morgan Hitchcock",
})
```

> [!NOTE]
> Because [`Update`](/docs/reference/golang/api/core/db.md#update) performs a full replacement, omitted fields are deleted from the record. Use [`Merge`](/docs/reference/golang/api/core/db.md#merge) if you want to preserve existing fields.

## Upserting records

[`Upsert`](/docs/reference/golang/api/core/db.md#upsert) creates a record if it does not already exist, or replaces it entirely if it does.

```go
person, err := surrealdb.Upsert[Person](ctx, db, models.NewRecordID("persons", "tobie"), Person{
	Name:    "Tobie",
	Surname: "Morgan Hitchcock",
})
```

## Merging data

[`Merge`](/docs/reference/golang/api/core/db.md#merge) deep-merges the provided data into the existing record, preserving fields not mentioned in the merge payload.

```go
merged, err := surrealdb.Merge[Person](ctx, db, models.NewRecordID("persons", "tobie"), map[string]any{
	"age": 35,
})
```

## Applying patches

[`Patch`](/docs/reference/golang/api/core/db.md#patch) applies [JSON Patch (RFC 6902)](https://jsonpatch.com/) operations to a record or all records in a table. Each operation is a [`PatchData`](/docs/reference/golang/api/types.md#patchdata) with `Op`, `Path`, and `Value` fields.

```go
patches, err := surrealdb.Patch(ctx, db, models.NewRecordID("persons", "tobie"), []surrealdb.PatchData{
	{Op: "replace", Path: "/surname", Value: "Hitchcock"},
	{Op: "add", Path: "/verified", Value: true},
})
```

Supported operations include `add`, `remove`, `replace`, `move`, `copy`, and `test`.

## Deleting records

[`Delete`](/docs/reference/golang/api/core/db.md#delete) removes a specific record or all records from a table. The function returns the deleted record(s).

```go
deleted, err := surrealdb.Delete[Person](ctx, db, models.NewRecordID("persons", "tobie"))

allDeleted, err := surrealdb.Delete[[]Person](ctx, db, models.Table("persons"))
```

## Learn more

- [DB API reference](/docs/reference/golang/api/core/db.md) for complete function signatures and parameters
- [Executing queries](/docs/reference/golang/concepts/executing-queries.md) for running SurrealQL statements directly
- [Value types](/docs/reference/golang/concepts/value-types.md) for the types used by data manipulation functions
- [RecordID reference](/docs/reference/golang/api/values/record-id.md) for constructing record identifiers
- [SurrealQL CRUD statements](/docs/reference/query-language/statements/overview.md) for the underlying query language

---

Source: https://surrealdb.com/docs/reference/golang/concepts/error-handling

# Error handling

The Go SDK provides structured error types for distinguishing between server errors, query errors, and transport failures.

The Go SDK uses Go's standard `error` interface and the `errors.As` / `errors.Is` functions for error handling. Errors fall into three main categories: structured [server errors](/docs/reference/golang/api/errors.md#servererror) from SurrealDB, per-statement [query errors](/docs/reference/golang/api/errors.md#queryerror), and transport-level failures.

The error kinds, wire codes and structured shape behind these are documented once in [Errors](/docs/reference/rest-api/errors.md), which applies to every SDK and protocol.

This page covers how to identify and handle each error type, and which errors are safe to retry.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Error type</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Error type"><a href="/docs/reference/golang/api/errors.md#servererror"><code>ServerError</code></a></td>
			<td scope="row" data-label="Description">Structured error from SurrealDB v3 with kind, details, and cause chain</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error type"><a href="/docs/reference/golang/api/errors.md#queryerror"><code>QueryError</code></a></td>
			<td scope="row" data-label="Description">Per-statement query failure returned within QueryResult</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error type"><a href="/docs/reference/golang/api/errors.md#rpcerror"><code>RPCError</code></a></td>
			<td scope="row" data-label="Description">RPC error (deprecated in favour of ServerError on v3)</td>
		</tr>
	</tbody>
</table>

## Handling structured server errors

SurrealDB v3 returns structured errors with a `Kind`, `Message`, `Details`, and an optional `Cause` chain. Use `errors.As` to extract a [`ServerError`](/docs/reference/golang/api/errors.md#servererror):

```go
import "errors"

_, err := surrealdb.Select[Person](ctx, db, models.NewRecordID("persons", "missing"))
if err != nil {
	var se *surrealdb.ServerError
	if errors.As(err, &se) {
		fmt.Println("Kind:", se.Kind)
		fmt.Println("Message:", se.Message)
		if se.Cause != nil {
			fmt.Println("Caused by:", se.Cause.Message)
		}
	}
}
```

The `Cause` field forms a linked list. Use `errors.Unwrap` or iterate through `Cause` to traverse the full chain.

## Handling query errors

When using `Query`, individual statements can fail without causing the entire call to error. Each [`QueryResult`](/docs/reference/golang/api/types.md#queryresult) has an `Error` field that contains a [`QueryError`](/docs/reference/golang/api/errors.md#queryerror) if that statement failed.

```go
results, err := surrealdb.Query[[]any](ctx, db,
	"SELECT * FROM persons; INVALID STATEMENT;",
	nil,
)

for i, qr := range *results {
	if qr.Error != nil {
		fmt.Printf("Statement %d failed: %s\n", i, qr.Error.Message)
	}
}
```

The `err` returned by `Query` is a joined error containing all per-statement `QueryError` values. You can check it directly:

```go
if errors.Is(err, &surrealdb.QueryError{}) {
	fmt.Println("One or more statements failed")
}
```

## Handling sentinel errors

The SDK defines several [sentinel errors](/docs/reference/golang/api/errors.md#sentinel-errors) for common failure conditions:

| Error | Description |
|---|---|
| `constants.ErrSessionsNotSupported` | Sessions require a WebSocket connection |
| `constants.ErrTransactionsNotSupported` | Interactive transactions require a WebSocket connection |
| `constants.ErrSessionClosed` | The session has been detached |
| `constants.ErrTransactionClosed` | The transaction has been committed or cancelled |

Check for these using `errors.Is`:

```go
import "github.com/surrealdb/surrealdb.go/pkg/constants"

if errors.Is(err, constants.ErrSessionClosed) {
	fmt.Println("Session was already detached")
}
```

## Deciding whether to retry

Not all errors are safe to retry. Use the following guidelines:

| Error type | Retriable | Reason |
|---|---|---|
| `ServerError` | Sometimes | Depends on the error kind; network-related kinds may be retriable |
| `QueryError` | No | The query itself is invalid or caused a logical error |
| `RPCError` (from `Query`) | Yes | The RPC failed before the query was processed |
| `RPCError` (from other methods) | No | May indicate a data-level error (e.g., duplicate record) |
| Unmarshal errors | No | Type mismatch between expected and actual response |

## Learn more

- [Errors API reference](/docs/reference/golang/api/errors.md) for complete error type definitions
- [Executing queries](/docs/reference/golang/concepts/executing-queries.md) for query-level error handling
- [Types reference](/docs/reference/golang/api/types.md) for `QueryResult` and `QueryError` definitions

---

Source: https://surrealdb.com/docs/reference/golang/concepts/executing-queries

# Executing queries

The Go SDK provides generic functions for executing SurrealQL queries with typed results and parameterised variables.

The Go SDK provides two ways to execute SurrealQL queries: [`Query`](/docs/reference/golang/api/core/db.md#query) for typed, parameterised queries and [`QueryRaw`](/docs/reference/golang/api/core/db.md#queryraw) for composing multiple statements with per-statement results. Both are generic functions that work with [`*DB`](/docs/reference/golang/api/core/db.md), [`*Session`](/docs/reference/golang/api/core/session.md), and [`*Transaction`](/docs/reference/golang/api/core/transaction.md).

This page covers running queries, parameterising them, handling multi-statement results, and managing connection-scoped variables.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Function</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#query"><code>surrealdb.Query[T](ctx, s, sql, vars)</code></a></td>
			<td scope="row" data-label="Description">Executes a SurrealQL query with typed results</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#queryraw"><code>surrealdb.QueryRaw(ctx, s, queries)</code></a></td>
			<td scope="row" data-label="Description">Executes a batch of query statements with per-statement results</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#let"><code>db.Let(ctx, key, val)</code></a></td>
			<td scope="row" data-label="Description">Defines a variable on the connection for use in queries</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#unset"><code>db.Unset(ctx, key)</code></a></td>
			<td scope="row" data-label="Description">Removes a previously defined connection variable</td>
		</tr>
	</tbody>
</table>

## Running a query

The [`Query`](/docs/reference/golang/api/core/db.md#query) function executes a SurrealQL string and returns typed results. The first type parameter specifies the expected result type. The second parameter `s` can be a [`*DB`](/docs/reference/golang/api/core/db.md), [`*Session`](/docs/reference/golang/api/core/session.md), or [`*Transaction`](/docs/reference/golang/api/core/transaction.md).

```go
results, err := surrealdb.Query[[]Person](ctx, db,
	"SELECT * FROM persons WHERE age > $min_age",
	map[string]any{"min_age": 18},
)
if err != nil {
	log.Fatal(err)
}

for _, qr := range *results {
	fmt.Println(qr.Status, qr.Result)
}
```

The function returns `*[]QueryResult[T]`, where each [`QueryResult`](/docs/reference/golang/api/types.md#queryresult) contains the `Status`, execution `Time`, `Result`, and an optional `Error` for that statement.

## Parameterising queries

Always use parameters (`$name`) instead of string interpolation to prevent injection attacks and ensure correct CBOR encoding of [value types](/docs/reference/golang/concepts/value-types.md).

```go
results, err := surrealdb.Query[[]Person](ctx, db,
	"SELECT * FROM persons WHERE name = $name AND age > $age",
	map[string]any{
		"name": "Tobie",
		"age":  25,
	},
)
```

Pass `nil` for the variables map when no parameters are needed:

```go
results, err := surrealdb.Query[[]Person](ctx, db,
	"SELECT * FROM persons",
	nil,
)
```

## Handling multi-statement queries

When a query string contains multiple statements, `Query` returns a `QueryResult` for each statement. Check the `Error` field on each result to detect per-statement failures.

```go
results, err := surrealdb.Query[[]any](ctx, db,
	"CREATE person:tobie SET name = 'Tobie'; SELECT * FROM person;",
	nil,
)

for i, qr := range *results {
	if qr.Error != nil {
		fmt.Printf("Statement %d failed: %s\n", i, qr.Error.Message)
		continue
	}
	fmt.Printf("Statement %d: %v\n", i, qr.Result)
}
```

> [!NOTE]
> The `err` returned by `Query` is a joined error containing all per-statement [`QueryError`](/docs/reference/golang/api/errors.md#queryerror) values. You can check individual statements via the `Error` field, or use `errors.Is(err, &surrealdb.QueryError{})` on the returned error.

## Composing queries with QueryRaw

`QueryRaw` lets you compose a batch of [`QueryStmt`](/docs/reference/golang/api/types.md#querystmt) objects, each with its own SQL and variables. After execution, each statement's result is available via `.GetResult()`.

```go
stmts := []surrealdb.QueryStmt{
	{SQL: "CREATE person:alice SET name = $name", Vars: map[string]any{"name": "Alice"}},
	{SQL: "SELECT * FROM person", Vars: nil},
}

if err := surrealdb.QueryRaw(ctx, db, &stmts); err != nil {
	log.Fatal(err)
}

var persons []Person
if err := stmts[1].GetResult(&persons); err != nil {
	log.Fatal(err)
}
```

## Defining connection variables

Use `.Let()` to define a variable that persists on the connection and is available in all subsequent queries. Use `.Unset()` to remove it.

```go
if err := db.Let(ctx, "app_version", "1.0.0"); err != nil {
	log.Fatal(err)
}

results, err := surrealdb.Query[[]any](ctx, db,
	"RETURN $app_version",
	nil,
)

db.Unset(ctx, "app_version")
```

Connection variables are scoped to the connection (or [session](/docs/reference/golang/concepts/multiple-sessions.md) if using sessions). They do not affect other connections. You can also build queries programmatically using the [query builder](/docs/reference/golang/concepts/query-builder.md).

## Learn more

- [DB API reference](/docs/reference/golang/api/core/db.md) for complete method signatures and parameters
- [Data manipulation](/docs/reference/golang/concepts/data-manipulation.md) for typed CRUD operations
- [Error handling](/docs/reference/golang/concepts/error-handling.md) for distinguishing between query errors and transport errors
- [Types reference](/docs/reference/golang/api/types.md) for `QueryResult` and `QueryStmt` definitions
- [SurrealQL statements](/docs/reference/query-language/statements/overview.md) for the query language syntax

---

Source: https://surrealdb.com/docs/reference/golang/concepts/live-queries

# Live queries

The Go SDK supports real-time live queries that stream change notifications from the database through Go channels.

Live queries allow your application to receive real-time notifications whenever records in a table are created, updated, or deleted. The Go SDK delivers notifications through Go channels, making it easy to integrate with goroutines and concurrent patterns.

Live queries require a WebSocket connection (`ws://` or `wss://`). They are available on [`*DB`](/docs/reference/golang/api/core/db.md) and [`*Session`](/docs/reference/golang/api/core/session.md) but not on [`*Transaction`](/docs/reference/golang/api/core/transaction.md).

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Function</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#live"><code>surrealdb.Live(ctx, s, table, diff)</code></a></td>
			<td scope="row" data-label="Description">Starts a live query on a table and returns its UUID</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#kill"><code>surrealdb.Kill(ctx, s, id)</code></a></td>
			<td scope="row" data-label="Description">Stops a live query and closes its notification channel</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#livenotifications"><code>db.LiveNotifications(id)</code></a></td>
			<td scope="row" data-label="Description">Returns the notification channel for a live query</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/golang/api/core/db.md#closelivenotifications"><code>db.CloseLiveNotifications(id)</code></a></td>
			<td scope="row" data-label="Description">Closes the notification channel without killing the server-side query</td>
		</tr>
	</tbody>
</table>

## Starting a live query

Use the [`Live`](/docs/reference/golang/api/core/db.md#live) function to subscribe to changes on a table. It returns a [`UUID`](/docs/reference/golang/api/values/uuid.md) that identifies the live query.

```go
liveID, err := surrealdb.Live(ctx, db, models.Table("persons"), false)
if err != nil {
	log.Fatal(err)
}
```

The `diff` parameter controls notification format. When `false`, notifications contain the full record. When `true`, they contain JSON Patch diffs instead.

## Receiving notifications

After starting a live query, call [`.LiveNotifications()`](/docs/reference/golang/api/core/db.md#livenotifications) on the [`*DB`](/docs/reference/golang/api/core/db.md) or [`*Session`](/docs/reference/golang/api/core/session.md) to get a channel that receives [`Notification`](/docs/reference/golang/api/types.md#notification) values.

```go
ch, err := db.LiveNotifications(liveID.String())
if err != nil {
	log.Fatal(err)
}

for notification := range ch {
	fmt.Printf("Action: %s, Result: %v\n", notification.Action, notification.Result)
}
```

Each notification includes:

| Field | Type | Description |
|---|---|---|
| `ID` | `*models.UUID` | The live query UUID |
| `Action` | `Action` | One of `CREATE`, `UPDATE`, or `DELETE` |
| `Result` | `interface{}` | The record data or JSON Patch diff |

## Processing notifications in a goroutine

A common pattern is to process live query notifications in a separate goroutine while the main goroutine continues other work.

```go
liveID, err := surrealdb.Live(ctx, db, models.Table("persons"), false)
if err != nil {
	log.Fatal(err)
}

ch, err := db.LiveNotifications(liveID.String())
if err != nil {
	log.Fatal(err)
}

go func() {
	for n := range ch {
		switch n.Action {
		case "CREATE":
			fmt.Println("New record:", n.Result)
		case "UPDATE":
			fmt.Println("Updated record:", n.Result)
		case "DELETE":
			fmt.Println("Deleted record:", n.Result)
		}
	}
}()
```

## Stopping a live query

Use the [`Kill`](/docs/reference/golang/api/core/db.md#kill) function to terminate a live query on the server and close its notification channel.

```go
if err := surrealdb.Kill(ctx, db, liveID.String()); err != nil {
	log.Fatal(err)
}
```

[`Kill`](/docs/reference/golang/api/core/db.md#kill) both sends the kill RPC to the server and closes the local notification channel. If you only want to close the channel without killing the server-side query, use [`.CloseLiveNotifications()`](/docs/reference/golang/api/core/db.md#closelivenotifications) instead.

## Learn more

- [DB API reference](/docs/reference/golang/api/core/db.md) for complete function signatures and parameters
- [Connecting to SurrealDB](/docs/reference/golang/concepts/connecting-to-surrealdb.md) for WebSocket connection requirements
- [Multiple sessions](/docs/reference/golang/concepts/multiple-sessions.md) for session-scoped live queries
- [Reliable connections](/docs/reference/golang/concepts/reliable-connections.md) for live queries that persist across reconnections
- [SurrealQL LIVE statement](/docs/reference/query-language/statements/live-select.md) for the underlying query language syntax

---

Source: https://surrealdb.com/docs/reference/golang/concepts/multiple-sessions

# Multiple sessions

The Go SDK supports creating multiple isolated sessions on a single WebSocket connection, each with its own authentication and namespace.

Sessions allow you to create isolated contexts on a single WebSocket connection. Each session has its own authentication state, namespace and database selection, and connection variables. This is useful when a single application needs to serve multiple users or tenants over one connection.

Sessions require a WebSocket connection (`ws://` or `wss://`) and SurrealDB v3 or later.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/session.md#attach"><code>db.Attach(ctx)</code></a></td>
			<td scope="row" data-label="Description">Creates a new session on the connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/session.md#detach"><code>session.Detach(ctx)</code></a></td>
			<td scope="row" data-label="Description">Removes the session from the server</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/session.md#id"><code>session.ID()</code></a></td>
			<td scope="row" data-label="Description">Returns the session's UUID</td>
		</tr>
	</tbody>
</table>

## Creating a session

Call `.Attach()` on the `*DB` to create a new session. The session starts unauthenticated and without a selected namespace or database, so you must configure it before making queries.

```go
session, err := db.Attach(ctx)
if err != nil {
	log.Fatal(err)
}
defer session.Detach(ctx)

_, err = session.SignIn(ctx, surrealdb.Auth{
	Username: "root",
	Password: "secret",
})
if err != nil {
	log.Fatal(err)
}

if err := session.Use(ctx, "my_ns", "my_db"); err != nil {
	log.Fatal(err)
}
```

## Querying with a session

Sessions satisfy the [`sendable`](/docs/reference/golang/api/types.md#sendable) constraint, so all generic functions like [`Query`](/docs/reference/golang/api/core/db.md#query), [`Select`](/docs/reference/golang/api/core/db.md#select), [`Create`](/docs/reference/golang/api/core/db.md#create), etc. accept a [`*Session`](/docs/reference/golang/api/core/session.md) directly.

```go
results, err := surrealdb.Query[[]Person](ctx, session,
	"SELECT * FROM persons",
	nil,
)
```

Each session maintains its own state. Changes to variables, authentication, or namespace on one session do not affect the parent [`*DB`](/docs/reference/golang/api/core/db.md) or other sessions.

## Session isolation

Sessions are fully isolated from each other and from the parent connection:

- Authentication is independent: signing in on a session does not affect other sessions or the [`*DB`](/docs/reference/golang/api/core/db.md).
- [`.Use()`](/docs/reference/golang/api/core/session.md#use) on a session does not change the namespace/database of other sessions.
- Variables set with [`.Let()`](/docs/reference/golang/api/core/session.md#let) are scoped to the session.
- [Live queries](/docs/reference/golang/concepts/live-queries.md) started on a session are scoped to that session.

```go
sessionA, _ := db.Attach(ctx)
defer sessionA.Detach(ctx)

sessionB, _ := db.Attach(ctx)
defer sessionB.Detach(ctx)

sessionA.SignIn(ctx, surrealdb.Auth{Username: "admin", Password: "admin"})
sessionA.Use(ctx, "ns_a", "db_a")

sessionB.SignIn(ctx, surrealdb.Auth{Namespace: "ns_b", Database: "db_b", Access: "user_access", Username: "user1", Password: "pass1"})
sessionB.Use(ctx, "ns_b", "db_b")
```

## Starting transactions from a session

Sessions can start their own transactions using `.Begin()`. The transaction inherits the session's authentication and namespace context.

```go
tx, err := session.Begin(ctx)
if err != nil {
	log.Fatal(err)
}
defer tx.Cancel(ctx)

surrealdb.Create[any](ctx, tx, models.Table("events"), map[string]any{"type": "login"})

if err := tx.Commit(ctx); err != nil {
	log.Fatal(err)
}
```

See [Transactions](/docs/reference/golang/concepts/transactions.md) for more on interactive transactions.

## Detaching a session

Call [`.Detach()`](/docs/reference/golang/api/core/session.md#detach) to remove the session from the server. After detaching, the session cannot be used and any operations on it return [`ErrSessionClosed`](/docs/reference/golang/api/errors.md#sentinel-errors).

```go
if err := session.Detach(ctx); err != nil {
	log.Fatal(err)
}
```

Use `defer session.Detach(ctx)` immediately after `.Attach()` to ensure cleanup.

## Learn more

- [Session API reference](/docs/reference/golang/api/core/session.md) for complete method signatures
- [Transactions](/docs/reference/golang/concepts/transactions.md) for interactive transaction support
- [Connecting to SurrealDB](/docs/reference/golang/concepts/connecting-to-surrealdb.md) for WebSocket connection requirements
- [Errors reference](/docs/reference/golang/api/errors.md) for session-related error types

---

Source: https://surrealdb.com/docs/reference/golang/concepts/query-builder

# Query builder

The Go SDK provides a contrib package for building type-safe SurrealQL queries programmatically with automatic parameter binding.

> [!WARNING]
> 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`](/docs/reference/golang/api/core/db.md#query).

## Installing the package

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

```go
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`](/docs/reference/golang/api/core/db.md#query):

```go
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.

```go
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`](/docs/reference/golang/api/values/record-id.md) and [`Table`](/docs/reference/golang/api/values/table.md) values directly as targets:

```go
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.

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

### Ordering and pagination

```go
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()`.

```go
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:

```go
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()`:

```go
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()`:

```go
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()`.

```go
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:

```go
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.

```go
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`:

```go
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:

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

## Using expressions

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

```go
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:

```go
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`:

```go
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;
```

> [!NOTE]
> Text-based transactions built with the query builder are different from [interactive transactions](/docs/reference/golang/concepts/transactions.md). Text-based transactions execute all statements atomically in a single RPC call. Interactive transactions allow inspecting results between statements.

## Learn more

- [Executing queries](/docs/reference/golang/concepts/executing-queries.md) for running the built queries with `surrealdb.Query`
- [Data manipulation](/docs/reference/golang/concepts/data-manipulation.md) for the typed CRUD functions as an alternative to the query builder
- [SurrealQL statements](/docs/reference/query-language/statements/overview.md) for the query language reference

---

Source: https://surrealdb.com/docs/reference/golang/concepts/reliable-connections

# Reliable connections

The Go SDK provides a contrib package for auto-reconnecting WebSocket connections with session restoration and live query persistence.

> [!WARNING]
> This feature is provided by the `contrib/rews` package, which is outside of the backward compatibility guarantees of the core SDK. Its API may change without following semantic versioning.

The `rews` (reliable WebSocket) package wraps a standard WebSocket connection and adds automatic reconnection when the connection is lost. On reconnect, it restores the previous session state including namespace, database, authentication, variables, and live queries.

This is useful for long-running applications that need to survive transient network failures without manual reconnection logic.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Function</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Function"><code>rews.New(newConn, interval, unmarshaler, logger)</code></td>
			<td scope="row" data-label="Description">Creates a new auto-reconnecting WebSocket connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><code>rews.NewExponentialBackoffRetryer()</code></td>
			<td scope="row" data-label="Description">Creates a retryer with exponential backoff and jitter</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><code>rews.NewFixedDelayRetryer(delay, maxRetries)</code></td>
			<td scope="row" data-label="Description">Creates a retryer with a fixed delay between attempts</td>
		</tr>
	</tbody>
</table>

## Setting up a reliable connection

Create a `rews.Connection` by providing a factory function that constructs the underlying WebSocket connection, a check interval for detecting disconnections, a CBOR unmarshaler, and an optional logger.

```go
import (
	"context"
	"net/url"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/rews"
	"github.com/surrealdb/surrealdb.go/pkg/connection"
	"github.com/surrealdb/surrealdb.go/pkg/connection/gorillaws"
	"github.com/surrealdb/surrealdb.go/surrealcbor"
)

endpoint, _ := url.Parse("ws://localhost:8000/rpc")
codec := surrealcbor.New()

conn := rews.New(
	func(ctx context.Context) (*gorillaws.WebSocket, error) {
		conf := connection.NewConfig(endpoint)
		conf.Marshaler = codec
		conf.Unmarshaler = codec
		return gorillaws.New(conf), nil
	},
	5*time.Second,
	codec,
	nil,
)
```

Then pass the connection to [`FromConnection`](/docs/reference/golang/api/core/db.md#fromconnection) to create a [`*DB`](/docs/reference/golang/api/core/db.md):

```go
db, err := surrealdb.FromConnection(ctx, conn)
if err != nil {
	log.Fatal(err)
}
defer db.Close(ctx)
```

## Configuring retry behaviour

By default, connection attempts are not retried. Set the `Retryer` field to enable automatic retries on connection failure.

The `ExponentialBackoffRetryer` increases the delay between retries exponentially, with optional jitter to avoid thundering herd problems:

```go
retryer := rews.NewExponentialBackoffRetryer()
retryer.MaxRetries = 10
retryer.InitialDelay = 1 * time.Second
retryer.MaxDelay = 30 * time.Second
conn.Retryer = retryer
```

| Field | Default | Description |
|---|---|---|
| `InitialDelay` | 1s | Delay before the first retry |
| `MaxDelay` | 30s | Maximum delay between retries |
| `Multiplier` | 2.0 | Exponential backoff multiplier |
| `MaxRetries` | 0 (infinite) | Maximum retry attempts, 0 for unlimited |
| `Jitter` | `true` | Add randomness to avoid synchronised retries |
| `JitterFactor` | 0.3 | Maximum jitter as a fraction of the delay |

For simpler cases, `FixedDelayRetryer` uses a constant delay:

```go
conn.Retryer = rews.NewFixedDelayRetryer(2*time.Second, 5)
```

You can also implement the `Retryer` interface for custom strategies:

```go
type Retryer interface {
	NextDelay(attempt int, lastErr error) (time.Duration, bool)
	Reset()
}
```

## What gets restored on reconnect

When the connection is lost and re-established, `rews` automatically restores:

1. **Namespace and database** - the last values passed to [`.Use()`](/docs/reference/golang/api/core/db.md#use)
2. **Authentication** - the last token from [`.SignIn()`](/docs/reference/golang/api/core/db.md#signin), [`.SignUp()`](/docs/reference/golang/api/core/db.md#signup), or [`.Authenticate()`](/docs/reference/golang/api/core/db.md#authenticate)
3. **Connection variables** - all variables set with [`.Let()`](/docs/reference/golang/api/core/db.md#let) (and removed with [`.Unset()`](/docs/reference/golang/api/core/db.md#unset))
4. **[Live queries](/docs/reference/golang/concepts/live-queries.md)** - all active live queries are re-subscribed, and notification routing is restored

> [!NOTE]
> If the authentication token has expired by the time reconnection occurs, re-authentication will fail. The application is responsible for handling token expiry, for example by using [refresh tokens](/docs/reference/golang/concepts/authentication.md#using-refresh-tokens).

## Connection states

The `rews.Connection` tracks its state through a state machine:

| State | Description |
|---|---|
| `Disconnected` | Not connected, initial state |
| `Connecting` | Connection attempt in progress |
| `Connected` | Connection established and active |
| `Closing` | Close requested, shutting down |
| `Closed` | Fully closed, cannot be reused |

## Learn more

- [Connecting to SurrealDB](/docs/reference/golang/concepts/connecting-to-surrealdb.md) for standard connection setup
- [Live queries](/docs/reference/golang/concepts/live-queries.md) for live query setup that persists across reconnections
- [DB API reference](/docs/reference/golang/api/core/db.md) for `FromConnection` usage

---

Source: https://surrealdb.com/docs/reference/golang/concepts/transactions

# Transactions

The Go SDK supports interactive transactions that let you execute statements one at a time and conditionally commit or cancel.

Interactive transactions let you group multiple operations into an atomic unit. Unlike text-based transactions (`BEGIN TRANSACTION; ... COMMIT;` - see the [query builder](/docs/reference/golang/concepts/query-builder.md#building-text-based-transactions) for composing those), interactive transactions allow you to execute statements one at a time, inspect results, and conditionally decide whether to commit or cancel.

Transactions require a WebSocket connection (`ws://` or `wss://`) and SurrealDB v3 or later.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/db.md#begin"><code>db.Begin(ctx)</code></a></td>
			<td scope="row" data-label="Description">Starts a transaction on the default session</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/session.md#begin"><code>session.Begin(ctx)</code></a></td>
			<td scope="row" data-label="Description">Starts a transaction within a session</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/transaction.md#commit"><code>tx.Commit(ctx)</code></a></td>
			<td scope="row" data-label="Description">Commits the transaction, applying all changes</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/transaction.md#cancel"><code>tx.Cancel(ctx)</code></a></td>
			<td scope="row" data-label="Description">Cancels the transaction, discarding all changes</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/transaction.md#id"><code>tx.ID()</code></a></td>
			<td scope="row" data-label="Description">Returns the transaction's UUID</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/golang/api/core/transaction.md#isclosed"><code>tx.IsClosed()</code></a></td>
			<td scope="row" data-label="Description">Returns whether the transaction has been committed or cancelled</td>
		</tr>
	</tbody>
</table>

## Starting a transaction

Call [`.Begin()`](/docs/reference/golang/api/core/db.md#begin) on a [`*DB`](/docs/reference/golang/api/core/db.md) or [`*Session`](/docs/reference/golang/api/core/session.md) to start a transaction. The transaction inherits the authentication and namespace context from the connection or session that started it.

```go
tx, err := db.Begin(ctx)
if err != nil {
	log.Fatal(err)
}
defer tx.Cancel(ctx)
```

> [!NOTE]
> Always use `defer tx.Cancel(ctx)` immediately after `.Begin()`. If the transaction has already been committed, [`.Cancel()`](/docs/reference/golang/api/core/transaction.md#cancel) returns [`ErrTransactionClosed`](/docs/reference/golang/api/errors.md#sentinel-errors) but does not cause any harm.

## Executing operations within a transaction

Transactions satisfy the [`sendable`](/docs/reference/golang/api/types.md#sendable) constraint, so all generic functions like [`Query`](/docs/reference/golang/api/core/db.md#query), [`Select`](/docs/reference/golang/api/core/db.md#select), [`Create`](/docs/reference/golang/api/core/db.md#create), [`Update`](/docs/reference/golang/api/core/db.md#update), and [`Delete`](/docs/reference/golang/api/core/db.md#delete) accept a [`*Transaction`](/docs/reference/golang/api/core/transaction.md).

```go
tx, err := db.Begin(ctx)
if err != nil {
	log.Fatal(err)
}
defer tx.Cancel(ctx)

_, err = surrealdb.Create[any](ctx, tx, models.Table("accounts"), map[string]any{
	"name":    "Alice",
	"balance": 1000,
})
if err != nil {
	log.Fatal(err)
}

_, err = surrealdb.Create[any](ctx, tx, models.Table("accounts"), map[string]any{
	"name":    "Bob",
	"balance": 500,
})
if err != nil {
	log.Fatal(err)
}

if err := tx.Commit(ctx); err != nil {
	log.Fatal(err)
}
```

Changes made within a transaction are not visible to other connections or sessions until the transaction is committed.

## Conditional commit or cancel

Because interactive transactions let you inspect results between operations, you can decide whether to commit based on runtime conditions.

```go
tx, err := db.Begin(ctx)
if err != nil {
	log.Fatal(err)
}
defer tx.Cancel(ctx)

results, err := surrealdb.Query[[]map[string]any](ctx, tx,
	"SELECT * FROM accounts WHERE name = 'Alice'",
	nil,
)
if err != nil {
	log.Fatal(err)
}

balance, ok := (*results)[0].Result[0]["balance"].(float64)
if !ok || balance < 100 {
	fmt.Println("Insufficient balance, canceling")
	return
}

_, err = surrealdb.Query[[]any](ctx, tx,
	"UPDATE accounts SET balance -= 100 WHERE name = 'Alice'; UPDATE accounts SET balance += 100 WHERE name = 'Bob';",
	nil,
)
if err != nil {
	log.Fatal(err)
}

if err := tx.Commit(ctx); err != nil {
	log.Fatal(err)
}
```

## Transaction limitations

Transactions do not support session state changes. The following operations are not available on a [`*Transaction`](/docs/reference/golang/api/core/transaction.md):

- [`.SignIn()`](/docs/reference/golang/api/core/db.md#signin), [`.SignUp()`](/docs/reference/golang/api/core/db.md#signup), [`.Authenticate()`](/docs/reference/golang/api/core/db.md#authenticate), [`.Invalidate()`](/docs/reference/golang/api/core/db.md#invalidate)
- [`.Use()`](/docs/reference/golang/api/core/db.md#use)
- [`.Let()`](/docs/reference/golang/api/core/db.md#let), [`.Unset()`](/docs/reference/golang/api/core/db.md#unset)
- [Live queries](/docs/reference/golang/concepts/live-queries.md) ([`Live`](/docs/reference/golang/api/core/db.md#live), [`Kill`](/docs/reference/golang/api/core/db.md#kill))

The namespace, database, authentication, and variables are inherited from the `*DB` or `*Session` that started the transaction.

## Learn more

- [Transaction API reference](/docs/reference/golang/api/core/transaction.md) for complete method signatures
- [Multiple sessions](/docs/reference/golang/concepts/multiple-sessions.md) for session-scoped transactions
- [Error handling](/docs/reference/golang/concepts/error-handling.md) for transaction error types
- [SurrealQL transactions](/docs/reference/query-language/statements/begin.md) for text-based transaction syntax

---

Source: https://surrealdb.com/docs/reference/golang/concepts/value-types

# Value types

The Go SDK uses typed wrappers for SurrealDB values like RecordID, UUID, DateTime, and Duration, encoded over CBOR.

The Go SDK communicates with SurrealDB using CBOR (Concise Binary Object Representation) rather than JSON. The SDK provides Go types that map to SurrealDB's data model and handle CBOR serialisation transparently when used in structs or maps.

This page covers the mapping between SurrealDB types and Go types, and how to work with the most common value types.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Type</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Type"><a href="/docs/reference/golang/api/values/record-id.md"><code>models.RecordID</code></a></td>
			<td scope="row" data-label="Description">A unique record identifier with table name and ID</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type"><a href="/docs/reference/golang/api/values/table.md"><code>models.Table</code></a></td>
			<td scope="row" data-label="Description">A table name</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type"><a href="/docs/reference/golang/api/values/uuid.md"><code>models.UUID</code></a></td>
			<td scope="row" data-label="Description">A UUID v4 or v7 value</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type"><a href="/docs/reference/golang/api/values/datetime.md"><code>models.CustomDateTime</code></a></td>
			<td scope="row" data-label="Description">A datetime value wrapping time.Time</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type"><a href="/docs/reference/golang/api/values/duration.md"><code>models.CustomDuration</code></a></td>
			<td scope="row" data-label="Description">A duration value wrapping time.Duration</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type"><a href="/docs/reference/golang/api/values/geometry.md"><code>models.GeometryPoint</code></a></td>
			<td scope="row" data-label="Description">A geographic point with longitude and latitude</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type"><a href="/docs/reference/golang/api/values/range.md"><code>models.Range</code></a></td>
			<td scope="row" data-label="Description">A range with inclusive or exclusive bounds</td>
		</tr>
	</tbody>
</table>

## Type mapping

The following table shows how SurrealDB types map to Go types when using the SDK.

| SurrealQL type | Go type | CBOR tag |
|---|---|---|
| `null` | `nil` | - |
| `none` | `models.CustomNil` / `models.None` | 6 |
| `bool` | `bool` | - |
| `int` | `int`, `int64`, etc. | - |
| `float` | `float64` | - |
| `decimal` | `models.DecimalString` | 10 |
| `string` | `string` | - |
| `bytes` | `[]byte` | - |
| `datetime` | `models.CustomDateTime` | 12 |
| `duration` | `models.CustomDuration` | 14 |
| `uuid` | `models.UUID` | 37 |
| `record` | `models.RecordID` | 8 |
| `array` | `[]any` or typed slice | - |
| `object` | `map[string]any` or struct | - |
| `geometry<point>` | `models.GeometryPoint` | 88 |
| `geometry<line>` | `models.GeometryLine` | 89 |
| `geometry<polygon>` | `models.GeometryPolygon` | 90 |
| `geometry<multipoint>` | `models.GeometryMultiPoint` | 91 |
| `geometry<multiline>` | `models.GeometryMultiLine` | 92 |
| `geometry<multipolygon>` | `models.GeometryMultiPolygon` | 93 |
| `geometry<collection>` | `models.GeometryCollection` | 94 |

## Working with record IDs

Use [`models.NewRecordID`](/docs/reference/golang/api/values/record-id.md) to construct a `RecordID`, or receive them from query results. The ID can be any CBOR-serializable value (string, integer, array, etc.).

```go
import "github.com/surrealdb/surrealdb.go/pkg/models"

id := models.NewRecordID("persons", "tobie")

numericID := models.NewRecordID("events", 42)
```

To parse a record ID from a string like `"persons:tobie"`, use `ParseRecordID`:

```go
id, err := models.ParseRecordID("persons:tobie")
```

When defining structs for database records, use `*models.RecordID` for the ID field:

```go
type Person struct {
	ID      *models.RecordID `json:"id,omitempty"`
	Name    string           `json:"name"`
	Surname string           `json:"surname"`
}
```

## Working with datetimes

[`CustomDateTime`](/docs/reference/golang/api/values/datetime.md) wraps Go's `time.Time` and handles CBOR encoding with nanosecond precision.

```go
now := models.CustomDateTime{Time: time.Now()}
```

You can use all standard `time.Time` methods directly on a `CustomDateTime` value, since it embeds `time.Time`.

## Working with durations

[`CustomDuration`](/docs/reference/golang/api/values/duration.md) wraps Go's `time.Duration` with SurrealDB-compatible formatting (e.g., `1d2h30m`).

```go
dur := models.CustomDuration{Duration: 2*time.Hour + 30*time.Minute}
fmt.Println(dur.String()) // "2h30m"
```

Use `CustomDurationString` when you need the string representation directly, and call `.ToCustomDuration()` to convert back.

## Working with geometry types

The SDK provides types for all SurrealDB geometry values. [`GeometryPoint`](/docs/reference/golang/api/values/geometry.md) is the most common:

```go
point := models.GeometryPoint{Longitude: -0.118, Latitude: 51.509}
```

Other geometry types compose `GeometryPoint`:

```go
line := models.GeometryLine{
	{Longitude: -0.118, Latitude: 51.509},
	{Longitude: -0.076, Latitude: 51.508},
}

polygon := models.GeometryPolygon{line}
```

## Using None

SurrealDB distinguishes between `null` (SQL NULL) and `none` (absence of a value). Use `models.None` to represent the absence of a value:

```go
surrealdb.Create[any](ctx, db, models.Table("test"), map[string]any{
	"field": models.None,
})
```

## Learn more

- [RecordID reference](/docs/reference/golang/api/values/record-id.md) for detailed constructors and methods
- [UUID reference](/docs/reference/golang/api/values/uuid.md) for UUID types
- [DateTime reference](/docs/reference/golang/api/values/datetime.md) for datetime handling
- [Duration reference](/docs/reference/golang/api/values/duration.md) for duration types and parsing
- [Geometry reference](/docs/reference/golang/api/values/geometry.md) for all geometry types
- [SurrealQL data model](/docs/reference/query-language/language-primitives/data-types.md) for the underlying type system

---

Source: https://surrealdb.com/docs/reference/golang/embedding

# Embedding

The surrealdb.c C FFI library contains Go bindings that can be used to access an embedded SurrealDB instance.

The Go bindings in `surrealdb.c` let a Go program run SurrealDB in-process instead of connecting to a server. This page covers building the C library, setting up CGO, and connecting to an embedded instance.

## Setup

1. Build surrealdb.c and set up CGO
2. Run the command `go get github.com/surrealdb/surrealdb.c.go`
3. Write code to connect to an embedded instance of SurrealDB.

> [!NOTE]
> This module links against a C static library (libsurrealdb_c.a) at compile time. You must build surrealdb.c and set CGO_LDFLAGS to its location before go build or go test will work. Without this, the Go linker cannot find the SurrealDB symbols and the build will fail. See docs/build.md for step-by-step instructions - or use the provided Makefile which handles everything automatically (make build).

The following code shows a simple example that opens up an embedded instance in memory, defines a table and then creates and selects a `person` record that matches the `Person` struct that the output deserializes into.

```go
package main

import (
    "context"
    "fmt"
    "log"

    surrealdb "github.com/surrealdb/surrealdb.c.go"
)

type Person struct {
    ID   surrealdb.RecordID[string] `cbor:"id,omitempty"`
    Name string                     `cbor:"name"`
    Age  int64                      `cbor:"age"`
}

func main() {
    ctx := context.Background()

    db, err := surrealdb.Open(ctx, "mem://")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    db.Use(ctx, "main", "main")
    db.Query(ctx, "DEFINE TABLE person SCHEMALESS", nil)
    db.Query(ctx, "CREATE $rid CONTENT $content", map[string]any{
        "rid":     surrealdb.NewRecordID("person", "alice"),
        "content": Person{Name: "Alice", Age: 30},
    })

    results, _ := surrealdb.Query[Person](ctx, db, "SELECT * FROM person", nil)
    for _, p := range results[0].Values() {
        fmt.Printf("%s: %s (age %d)\n", p.ID, p.Name, p.Age)
    }
}
```

## More information

For more information on the Go bindings used for an embedded SurrealDB instance, see [this page](https://github.com/surrealdb/surrealdb.c.go/tree/main/docs) for the surrealdb.c.go repo.

Pages of particular note are:

* [Storage backends](https://github.com/surrealdb/surrealdb.c.go/blob/main/docs/rocksdb.md): how to use other endpoints like `rocksdb://`.
* [Linking strategy](https://github.com/surrealdb/surrealdb.c.go/blob/main/docs/internals.md)

---

Source: https://surrealdb.com/docs/reference/golang/installation

# Installation

The SurrealDB Go SDK can be installed with a single go get command.

Install the SDK from [pkg.go.dev](https://pkg.go.dev/github.com/surrealdb/surrealdb.go) using `go get`:

```bash
go get github.com/surrealdb/surrealdb.go
```

Then import the SDK and its models package in your Go files:

```go
import (
	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)
```

The `surrealdb` package contains the client, query functions, and authentication methods. The `models` package contains value types such as [`RecordID`](/docs/reference/golang/api/values/record-id.md), [`Table`](/docs/reference/golang/api/values/table.md), and [`UUID`](/docs/reference/golang/api/values/uuid.md).

## Requirements

- Go `1.23` or later
- SurrealDB `v2.x` or `v3.x`

## Next steps

- [Getting started](/docs/languages/golang.md) to build your first application
- [Connecting to SurrealDB](/docs/reference/golang/concepts/connecting-to-surrealdb.md) for connection protocols and configuration

---

Source: https://surrealdb.com/docs/reference/java

# Java SDK

The official SurrealDB SDK for Java. Simple and advanced querying of a remote or embedded database.

The SurrealDB SDK for Java lets you connect to [SurrealDB](/docs) from any Java application. It supports connecting to remote instances over WebSocket or HTTP, and running embedded databases in-process. The SDK provides methods for querying with [SurrealQL](/docs/reference/query-language.md), managing data, [authentication](/docs/learn/security/authentication/users.md), live queries, and transactions. It uses JNI to call native Rust code for high performance.

> [!NOTE]
> The latest version of the SDK is `2.1.2`.
> The SDK works with SurrealDB versions `v2.0.0` and later, including the current release, `v3.3.4`.

## Getting started

- [Installation](/docs/reference/java/installation.md) - Install the SDK and add it to your project.

- [Getting started guide](/docs/languages/java.md) - Connect to SurrealDB and run your first queries.

## Learn

- [Concepts](/docs/reference/java/concepts/connecting-to-surrealdb.md) - Guides for connecting, authenticating, querying, and working with data.

- [API Reference](/docs/reference/java/api/core/surreal.md) - Complete reference for the SDK's methods, types, and errors.

## Concepts

- [Connecting to SurrealDB](/docs/reference/java/concepts/connecting-to-surrealdb.md) - open a connection over HTTP or WebSocket
- [Authentication](/docs/reference/java/concepts/authentication.md) - sign up, sign in, and authenticate with a token
- [Multiple sessions](/docs/reference/java/concepts/multiple-sessions.md) - run isolated sessions over a single connection
- [Executing queries](/docs/reference/java/concepts/executing-queries.md) - send SurrealQL and read the results back
- [Data manipulation](/docs/reference/java/concepts/data-manipulation.md) - create, select, update, upsert and delete records
- [Value types](/docs/reference/java/concepts/value-types.md) - how SurrealDB's types map onto native ones
- [Class converters](/docs/reference/java/concepts/class-converters.md) - map your own classes onto records
- [Transactions](/docs/reference/java/concepts/transactions.md) - group statements so they succeed or fail together
- [Live queries](/docs/reference/java/concepts/live-queries.md) - stream changes as they happen
- [Error handling](/docs/reference/java/concepts/error-handling.md) - what a failure looks like, and how to catch it
- [Embedded databases](/docs/reference/java/concepts/embedded-databases.md) - run SurrealDB in-process for tests and local use

## Contributing

To contribute to the SDK code, submit an Issue or Pull Request in the [surrealdb.java](https://github.com/surrealdb/surrealdb.java) repository. To contribute to this documentation, submit an Issue or Pull Request in the [docs.surrealdb.com](https://github.com/surrealdb/docs.surrealdb.com) repository.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.java)
- [JavaDoc](https://surrealdb.github.io/surrealdb.java/javadoc/)
- [Maven package](https://mvnrepository.com/artifact/com.surrealdb/surrealdb)

---

Source: https://surrealdb.com/docs/reference/java/api/core/live-stream

# LiveStream

The LiveStream class provides a blocking interface for receiving real-time notifications from live queries.

The `LiveStream` class provides a blocking interface for receiving real-time notifications from live queries. It implements `AutoCloseable`, so it can be used in a try-with-resources block. Live streams are created by calling [`.selectLive()`](/docs/reference/java/api/core/surreal.md#select-live) on a `Surreal` instance.

> [!NOTE]
> Live queries require a WebSocket connection (`ws://` or `wss://`).

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Methods

### `.next()` {#next}

Blocks until the next live query notification is available and returns it. Returns an empty `Optional` if the stream has been closed.

```java title="Method Syntax"
stream.next()
```

**Returns:** `Optional<LiveNotification>`

```java title="Example"
LiveStream stream = db.selectLive("person");
Optional<LiveNotification> notification = stream.next();
```

### `.close()` {#close}

Closes the live query subscription and releases associated resources. This is called automatically when using try-with-resources.

```java title="Method Syntax"
stream.close()
```

**Returns:** `void`

```java title="Example"
stream.close();
```

---

## `LiveNotification` {#live-notification}

The `LiveNotification` class represents a single real-time notification received from a live query. Each notification contains the action that triggered it, the affected record value, and the live query identifier.

---

## Methods

### `.getAction()` {#get-action}

Returns the type of action that triggered the notification.

```java title="Method Syntax"
notification.getAction()
```

**Returns:** `String` - one of `"CREATE"`, `"UPDATE"`, or `"DELETE"`

```java title="Example"
String action = notification.getAction();
```

### `.getValue()` {#get-value}

Returns the record value associated with the notification. For `CREATE` and `UPDATE` actions, this is the full record. For `DELETE` actions, this may be `null`.

```java title="Method Syntax"
notification.getValue()
```

**Returns:** `Value` (may be `null` for `DELETE` actions)

```java title="Example"
Value record = notification.getValue();
```

### `.getQueryId()` {#get-query-id}

Returns the UUID of the live query that produced this notification.

```java title="Method Syntax"
notification.getQueryId()
```

**Returns:** `String`

```java title="Example"
String queryId = notification.getQueryId();
```

---

## Complete example

```java title="Listening for live changes"
import com.surrealdb.Surreal;
import com.surrealdb.LiveStream;
import com.surrealdb.LiveNotification;
import com.surrealdb.signin.RootCredential;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");
    db.signin(new RootCredential("root", "root"));

    try (LiveStream stream = db.selectLive("person")) {
        while (true) {
            Optional<LiveNotification> notification = stream.next();
            if (notification.isEmpty()) break;

            LiveNotification n = notification.get();
            System.out.println(n.getAction() + ": " + n.getValue());
        }
    }
}
```

---

## See also

- [Surreal](/docs/reference/java/api/core/surreal.md) - Connection and method reference
- [Live queries](/docs/reference/java/concepts/live-queries.md) - Live query concepts and patterns
- [SurrealQL LIVE SELECT](/docs/reference/query-language/statements/live-select.md) - Live query syntax

---

Source: https://surrealdb.com/docs/reference/java/api/core/response

# Response

The Response class wraps the results of a SurrealQL query execution.

The `Response` class wraps the results returned by a SurrealQL query execution. A single query string can contain multiple statements, and the `Response` holds the result of each statement indexed by its zero-based position.

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Methods

### `.take(index)` {#take}

Extracts the result of a specific statement from the response by its zero-based index. The untyped variant returns a raw `Value`, while the typed variant deserializes the result into the specified Java class.

```java title="Method Syntax"
Value take(int num)
<T> List<T> take(Class<T> type, int num)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(optional)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The class to deserialise the result into. Omit for an untyped <code>Value</code> return.</td>
        </tr>
        <tr>
            <td><code>num</code> _(required)_</td>
            <td><code>int</code></td>
            <td>The zero-based index of the statement result to extract.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Value` (untyped) or `List<T>` (typed)

```java title="Example"
Response response = db.query("SELECT * FROM users; SELECT * FROM posts;");

Value users = response.take(0);
List<Post> posts = response.take(Post.class, 1);
```

### `.size()` {#size}

Returns the number of statement results contained in the response.

```java title="Method Syntax"
response.size()
```

**Returns:** `int`

```java title="Example"
Response response = db.query("SELECT * FROM users; SELECT * FROM posts;");
int count = response.size();
```

---

## Complete example

```java title="Working with multi-statement responses"
import com.surrealdb.Surreal;
import com.surrealdb.Response;
import com.surrealdb.signin.RootCredential;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");
    db.signin(new RootCredential("root", "root"));

    Response response = db.query(
        "CREATE person SET name = 'Alice'; SELECT * FROM person;"
    );

    int statementCount = response.size();

    Value created = response.take(0);
    List<Person> people = response.take(Person.class, 1);
}
```

---

## See also

- [Surreal](/docs/reference/java/api/core/surreal.md), Connection and method reference
- [Executing queries](/docs/reference/java/concepts/executing-queries.md), Query concepts and patterns
- [Value types](/docs/reference/java/concepts/value-types.md), Working with the Value class
- [SurrealQL](/docs/reference/query-language.md), Query language reference

---

Source: https://surrealdb.com/docs/reference/java/api/core/surreal

# Surreal

The Surreal class is the main entry point for connecting to and interacting with a SurrealDB instance from Java.

The `Surreal` class is the main entry point for the Java SDK. It provides methods for connecting to a SurrealDB instance, authenticating, querying, and managing data. The class implements `AutoCloseable`, so it can be used in a try-with-resources block to ensure the connection is closed automatically.

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Connection methods

### `Surreal()` {#constructor}

Creates a new `Surreal` instance. The instance is not connected to any server until `.connect()` is called.

```java title="Method Syntax"
Surreal db = new Surreal();
```

**Returns:** `Surreal`

```java title="Example"
Surreal db = new Surreal();
```

### `.connect(url)` {#connect}

Connects the instance to a SurrealDB server using the specified URL. The URL scheme determines the connection protocol. See the [start command](/docs/reference/cli/db/commands/start.md) documentation for server configuration options.

```java title="Method Syntax"
db.connect(url)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>url</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The connection URL. Supported schemes: <code>ws://</code>, <code>wss://</code>, <code>http://</code>, <code>https://</code>, <code>memory://</code>, <code>surrealkv://</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Surreal` (for method chaining)

```java title="Example"
db.connect("ws://localhost:8000");
```

### `.close()` {#close}

Closes the active connection and releases all associated resources. This is called automatically when using try-with-resources.

```java title="Method Syntax"
db.close()
```

**Returns:** `void`

```java title="Example"
db.close();
```

### `.useNs(namespace)` {#use-ns}

Switches the connection to a specific namespace.

```java title="Method Syntax"
db.useNs(ns)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ns</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The namespace to switch to.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Surreal` (for method chaining)

```java title="Example"
db.useNs("surrealdb").useDb("docs");
```

### `.useDb(database)` {#use-db}

Switches the connection to a specific database.

```java title="Method Syntax"
db.useDb(database)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>db</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The database to switch to.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Surreal` (for method chaining)

```java title="Example"
db.useDb("docs");
```

### `.useDefaults()` {#use-defaults}

Resets the namespace and database to the server defaults.

```java title="Method Syntax"
db.useDefaults()
```

**Returns:** `Surreal` (for method chaining)

```java title="Example"
db.useDefaults();
```

### `.getNamespace()` {#get-namespace}

Returns the namespace currently in use on this connection.

```java title="Method Syntax"
db.getNamespace()
```

**Returns:** `String`

```java title="Example"
String ns = db.getNamespace();
```

### `.getDatabase()` {#get-database}

Returns the database currently in use on this connection.

```java title="Method Syntax"
db.getDatabase()
```

**Returns:** `String`

```java title="Example"
String database = db.getDatabase();
```

### `.newSession()` {#new-session}

Creates a new isolated session that shares the underlying connection but maintains its own namespace, database, authentication state, and variables.

```java title="Method Syntax"
db.newSession()
```

**Returns:** `Surreal`

```java title="Example"
Surreal session = db.newSession();
session.useNs("other_ns").useDb("other_db");
```

---

## Authentication methods

### `.signin(credential)` {#signin}

Signs in to the database with the provided credentials. The credential type determines the authentication level: root, namespace, database, or record access.

```java title="Method Syntax"
db.signin(credential)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>credential</code> _(required)_</td>
            <td><code>Credential</code></td>
            <td>The credentials to sign in with. Use <code>RootCredential</code>, <code>NamespaceCredential</code>, <code>DatabaseCredential</code>, or <code>RecordCredential</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Token`

```java title="Example"
Token token = db.signin(new RootCredential("root", "root"));
```

### `.signup(credential)` {#signup}

Signs up a new record user using a record access method defined with [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md).

```java title="Method Syntax"
db.signup(credential)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>credential</code> _(required)_</td>
            <td><code>RecordCredential</code></td>
            <td>The record access credentials including namespace, database, access method, and any additional fields required by the access definition.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Token`

```java title="Example"
Token token = db.signup(new RecordCredential(
    "surrealdb", "docs", "user_access",
    Map.of("email", "user@example.com", "password", "s3cret")
));
```

### `.authenticate(token)` {#authenticate}

Authenticates the current connection using an existing JWT token.

```java title="Method Syntax"
db.authenticate(token)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>token</code> _(required)_</td>
            <td><code>String</code></td>
            <td>A valid JWT token string.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Surreal` (for method chaining)

```java title="Example"
db.authenticate("eyJhbGciOiJIUzI1NiIs...");
```

### `.invalidate()` {#invalidate}

Invalidates the current authentication, removing the associated session token.

```java title="Method Syntax"
db.invalidate()
```

**Returns:** `Surreal` (for method chaining)

```java title="Example"
db.invalidate();
```

---

## Query methods

### `.query(sql)` {#query}

Executes one or more SurrealQL statements and returns a `Response` containing the results of each statement.

```java title="Method Syntax"
db.query(sql)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>sql</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The SurrealQL query string to execute.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Response`](/docs/reference/java/api/core/response.md)

```java title="Example"
Response response = db.query("SELECT * FROM users");
List<User> users = response.take(User.class, 0);
```

### `.queryBind(sql, params)` {#query-bind}

Executes a parameterised SurrealQL query. Parameters are safely injected into the query, preventing SurrealQL injection.

```java title="Method Syntax"
db.queryBind(sql, params)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>sql</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The SurrealQL query string with parameter placeholders.</td>
        </tr>
        <tr>
            <td><code>params</code> _(required)_</td>
            <td><code>Map&lt;String, ?&gt;</code></td>
            <td>A map of parameter names to values.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Response`](/docs/reference/java/api/core/response.md)

```java title="Example"
Response response = db.queryBind(
    "SELECT * FROM users WHERE age > $min_age",
    Map.of("min_age", 18)
);
```

### `.run(name, args)` {#run}

Runs a server-side SurrealDB function defined with [`DEFINE FUNCTION`](/docs/reference/query-language/statements/define/function.md).

```java title="Method Syntax"
db.run(name, args)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>name</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The function name (e.g. <code>"fn::calculate_total"</code>).</td>
        </tr>
        <tr>
            <td><code>args</code> _(optional)_</td>
            <td><code>Object...</code></td>
            <td>Arguments to pass to the function.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Value`

```java title="Example"
Value result = db.run("fn::calculate_total", 100, 0.2);
```

---

## Data methods

> [!NOTE]
> Most data methods have multiple overloads. The typed variants accepting `Class<T>` are shown below. Untyped variants returning `Value` or `Iterator<Value>` are also available.

### `.create(type, target, content)` {#create}

Creates one or more records. When called with a table name, SurrealDB generates random IDs. When called with a `RecordId`, the record is created with that specific ID.

```java title="Method Syntax"
<T> List<T> create(Class<T> type, String target, T... contents)
<T> T create(Class<T> type, RecordId recordId, T content)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(required)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The class to deserialise results into.</td>
        </tr>
        <tr>
            <td><code>target</code> _(required)_</td>
            <td><code>String</code> or <code>RecordId</code></td>
            <td>The table name or specific record ID.</td>
        </tr>
        <tr>
            <td><code>contents</code> _(required)_</td>
            <td><code>T</code> or <code>T...</code></td>
            <td>The record content(s) to create.</td>
        </tr>
    </tbody>
</table>

**Returns:** `List<T>` (table target) or `T` (record ID target)

```java title="Example"
Person alice = new Person();
alice.name = "Alice";
alice.age = 30;

List<Person> created = db.create(Person.class, "person", alice);

Person specific = db.create(Person.class, new RecordId("person", "tobie"), alice);
```

### `.select(type, target)` {#select}

Selects records from a table or retrieves specific records by ID.

```java title="Method Syntax"
<T> Iterator<T> select(Class<T> type, String target)
<T> Optional<T> select(Class<T> type, RecordId recordId)
<T> List<T> select(Class<T> type, RecordId... recordIds)
<T> List<T> select(Class<T> type, RecordIdRange range)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(required)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The class to deserialise results into.</td>
        </tr>
        <tr>
            <td><code>target</code> _(required)_</td>
            <td><code>String</code>, <code>RecordId</code>, <code>RecordId...</code>, or <code>RecordIdRange</code></td>
            <td>The table name, a single record ID, multiple record IDs, or a record ID range.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Iterator<T>` (table), `Optional<T>` (single ID), `List<T>` (multiple IDs or range)

```java title="Example"
Iterator<Person> all = db.select(Person.class, "person");

Optional<Person> one = db.select(Person.class, new RecordId("person", "tobie"));

List<Person> range = db.select(Person.class,
    new RecordIdRange("person", Id.from("a"), Id.from("m")));
```

### `.selectSync(type, target)` {#select-sync}

Thread-safe variant of [`.select()`](#select) for table-level queries. Returns a synchronized iterator safe for use across multiple threads.

```java title="Method Syntax"
<T> Iterator<T> selectSync(Class<T> type, String target)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(required)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The class to deserialise results into.</td>
        </tr>
        <tr>
            <td><code>target</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table name to select from.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Iterator<T>` (synchronized)

```java title="Example"
Iterator<Person> all = db.selectSync(Person.class, "person");
```

### `.insert(type, target, content)` {#insert}

Inserts one or more records into a table.

```java title="Method Syntax"
<T> List<T> insert(Class<T> type, String target, T... contents)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(required)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The class to deserialise results into.</td>
        </tr>
        <tr>
            <td><code>target</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table to insert into.</td>
        </tr>
        <tr>
            <td><code>contents</code> _(required)_</td>
            <td><code>T...</code></td>
            <td>The record content(s) to insert.</td>
        </tr>
    </tbody>
</table>

**Returns:** `List<T>`

```java title="Example"
Person alice = new Person();
alice.name = "Alice";

List<Person> inserted = db.insert(Person.class, "person", alice);
```

### `.update(type, target, upType, content)` {#update}

Updates existing records. Use `UpType.CONTENT` to replace the entire record, `UpType.MERGE` to merge fields into the existing record, or `UpType.PATCH` to apply a JSON Patch.

```java title="Method Syntax"
<T> T update(Class<T> type, RecordId recordId, UpType upType, T content)
<T> Iterator<T> update(Class<T> type, String target, UpType upType, T content)
<T> Value update(RecordIdRange range, UpType upType, T content)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(required)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The class to deserialise results into.</td>
        </tr>
        <tr>
            <td><code>target</code> _(required)_</td>
            <td><code>RecordId</code>, <code>RecordIdRange</code>, or <code>String</code></td>
            <td>The record ID, record ID range, or table name to update.</td>
        </tr>
        <tr>
            <td><code>upType</code> _(required)_</td>
            <td><code>UpType</code></td>
            <td>The update strategy: <code>UpType.CONTENT</code> (replace), <code>UpType.MERGE</code> (merge), or <code>UpType.PATCH</code> (JSON Patch).</td>
        </tr>
        <tr>
            <td><code>content</code> _(required)_</td>
            <td><code>T</code></td>
            <td>The update content.</td>
        </tr>
    </tbody>
</table>

**Returns:** `T` (single record) or `Iterator<T>` (table)

```java title="Example"
Person updated = new Person();
updated.name = "Alice Smith";
updated.age = 31;

Person result = db.update(Person.class, new RecordId("person", "alice"), UpType.CONTENT, updated);
```

### `.updateSync(type, target, upType, content)` {#update-sync}

Thread-safe variant of [`.update()`](#update) for table-level updates. Returns a synchronized iterator safe for use across multiple threads.

```java title="Method Syntax"
<T> Iterator<T> updateSync(Class<T> type, String target, UpType upType, T content)
```

**Returns:** `Iterator<T>` (synchronized)

```java title="Example"
Iterator<Person> results = db.updateSync(Person.class, "person", UpType.MERGE, updates);
```

### `.upsert(type, target, upType, content)` {#upsert}

Updates an existing record or creates a new one if it does not exist. Accepts the same parameters as [`.update()`](#update).

```java title="Method Syntax"
<T> T upsert(Class<T> type, RecordId recordId, UpType upType, T content)
<T> Iterator<T> upsert(Class<T> type, String target, UpType upType, T content)
<T> Value upsert(RecordIdRange range, UpType upType, T content)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(required)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The class to deserialise results into.</td>
        </tr>
        <tr>
            <td><code>target</code> _(required)_</td>
            <td><code>RecordId</code>, <code>RecordIdRange</code>, or <code>String</code></td>
            <td>The record ID, record ID range, or table name to upsert.</td>
        </tr>
        <tr>
            <td><code>upType</code> _(required)_</td>
            <td><code>UpType</code></td>
            <td>The update strategy: <code>UpType.CONTENT</code> (replace), <code>UpType.MERGE</code> (merge), or <code>UpType.PATCH</code> (JSON Patch).</td>
        </tr>
        <tr>
            <td><code>content</code> _(required)_</td>
            <td><code>T</code></td>
            <td>The record content.</td>
        </tr>
    </tbody>
</table>

**Returns:** `T` (single record) or `Iterator<T>` (table)

```java title="Example"
Person person = new Person();
person.name = "Alice";
person.age = 30;

Person result = db.upsert(Person.class, new RecordId("person", "alice"), UpType.CONTENT, person);
```

### `.upsertSync(type, target, upType, content)` {#upsert-sync}

Thread-safe variant of [`.upsert()`](#upsert) for table-level upserts. Returns a synchronized iterator safe for use across multiple threads.

```java title="Method Syntax"
<T> Iterator<T> upsertSync(Class<T> type, String target, UpType upType, T content)
```

**Returns:** `Iterator<T>` (synchronized)

```java title="Example"
Iterator<Person> results = db.upsertSync(Person.class, "person", UpType.CONTENT, person);
```

### `.delete(target)` {#delete}

Deletes records from the database. Supports deleting a single record, multiple records by ID, a range of records, or all records in a table.

```java title="Method Syntax"
void delete(RecordId recordId)
void delete(RecordId... recordIds)
void delete(RecordIdRange range)
void delete(String target)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>target</code> _(required)_</td>
            <td><code>RecordId</code>, <code>RecordId...</code>, <code>RecordIdRange</code>, or <code>String</code></td>
            <td>A single record ID, multiple record IDs, a record ID range, or a table name.</td>
        </tr>
    </tbody>
</table>

**Returns:** `void`

```java title="Example"
db.delete(new RecordId("person", "tobie"));

db.delete(new RecordId("person", "alice"), new RecordId("person", "bob"));

db.delete(new RecordIdRange("person", Id.from("a"), Id.from("f")));

db.delete("temp_data");
```

### `.relate(from, table, to)` {#relate}

Creates a graph relation between two records.

```java title="Method Syntax"
Value relate(RecordId from, String table, RecordId to)
<T extends Relation> T relate(Class<T> type, RecordId from, String table, RecordId to)
<T> Value relate(RecordId from, String table, RecordId to, T content)
<R extends Relation, T> R relate(Class<R> type, RecordId from, String table, RecordId to, T content)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(optional)_</td>
            <td><code>Class&lt;T extends Relation&gt;</code></td>
            <td>The class to deserialise the relation into. Omit for untyped <code>Value</code> return.</td>
        </tr>
        <tr>
            <td><code>from</code> _(required)_</td>
            <td><code>RecordId</code></td>
            <td>The source record.</td>
        </tr>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The relation table name.</td>
        </tr>
        <tr>
            <td><code>to</code> _(required)_</td>
            <td><code>RecordId</code></td>
            <td>The target record.</td>
        </tr>
        <tr>
            <td><code>content</code> _(optional)_</td>
            <td><code>T</code></td>
            <td>Additional data to attach to the edge record.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Value` or `T`

```java title="Example"
Value relation = db.relate(
    new RecordId("person", "alice"),
    "likes",
    new RecordId("post", "post1")
);

Value withContent = db.relate(
    new RecordId("person", "alice"),
    "likes",
    new RecordId("post", "post1"),
    Map.of("timestamp", "2026-01-01T00:00:00Z")
);
```

### `.insertRelation(target, content)` {#insert-relation}

Inserts a relation record into a relation table with additional data.

```java title="Method Syntax"
<T extends InsertRelation> T insertRelation(Class<T> type, String target, T content)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(required)_</td>
            <td><code>Class&lt;T extends InsertRelation&gt;</code></td>
            <td>The relation class to deserialise into.</td>
        </tr>
        <tr>
            <td><code>target</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The relation table name.</td>
        </tr>
        <tr>
            <td><code>content</code> _(required)_</td>
            <td><code>T</code></td>
            <td>The relation content, including <code>in</code> and <code>out</code> fields.</td>
        </tr>
    </tbody>
</table>

**Returns:** `T`

```java title="Example"
Likes like = new Likes();
like.in = new RecordId("person", "alice");
like.out = new RecordId("post", "post1");
like.createdAt = "2025-01-01T00:00:00Z";

Likes result = db.insertRelation(Likes.class, "likes", like);
```

### `.insertRelations(target, contents)` {#insert-relations}

Inserts multiple relation records into a relation table using varargs.

```java title="Method Syntax"
<T extends InsertRelation> List<T> insertRelations(Class<T> type, String target, T... contents)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(required)_</td>
            <td><code>Class&lt;T extends InsertRelation&gt;</code></td>
            <td>The relation class to deserialise into.</td>
        </tr>
        <tr>
            <td><code>target</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The relation table name.</td>
        </tr>
        <tr>
            <td><code>contents</code> _(required)_</td>
            <td><code>T...</code></td>
            <td>The relation records to insert, each including <code>in</code> and <code>out</code> fields.</td>
        </tr>
    </tbody>
</table>

**Returns:** `List<T>`

```java title="Example"
Likes like1 = new Likes();
like1.in = new RecordId("person", "alice");
like1.out = new RecordId("post", "post1");

Likes like2 = new Likes();
like2.in = new RecordId("person", "alice");
like2.out = new RecordId("post", "post2");

List<Likes> results = db.insertRelations(Likes.class, "likes", like1, like2);
```

---

## Live query methods

> [!NOTE]
> Live queries require a WebSocket connection (`ws://` or `wss://`).

### `.selectLive(table)` {#select-live}

Starts a live query that receives real-time notifications when records in the specified table are created, updated, or deleted.

```java title="Method Syntax"
db.selectLive(table)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table to watch for changes.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`LiveStream`](/docs/reference/java/api/core/live-stream.md)

```java title="Example"
LiveStream stream = db.selectLive("person");
```

---

## Transaction methods

### `.beginTransaction()` {#begin-transaction}

Starts a new atomic transaction. All operations performed on the returned `Transaction` are grouped and only applied when committed.

```java title="Method Syntax"
db.beginTransaction()
```

**Returns:** [`Transaction`](/docs/reference/java/api/core/transaction.md)

```java title="Example"
Transaction tx = db.beginTransaction();
```

---

## Utility methods

### `.version()` {#version}

Returns the version string of the connected SurrealDB server.

```java title="Method Syntax"
db.version()
```

**Returns:** `String`

```java title="Example"
String version = db.version();
```

### `.health()` {#health}

Checks the health of the connected SurrealDB server.

```java title="Method Syntax"
db.health()
```

**Returns:** `boolean`

```java title="Example"
boolean healthy = db.health();
```

### `.exportSql(path)` {#export-sql}

Exports the current database to a file at the specified path.

```java title="Method Syntax"
db.exportSql(path)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>path</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The file path to export the database to.</td>
        </tr>
    </tbody>
</table>

**Returns:** `boolean`

```java title="Example"
boolean success = db.exportSql("/tmp/backup.surql");
```

### `.importSql(path)` {#import-sql}

Imports a database from a file at the specified path.

```java title="Method Syntax"
db.importSql(path)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>path</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The file path to import the database from.</td>
        </tr>
    </tbody>
</table>

**Returns:** `boolean`

```java title="Example"
boolean success = db.importSql("/tmp/backup.surql");
```

---

## See also

- [Transaction](/docs/reference/java/api/core/transaction.md) - Transaction reference
- [Response](/docs/reference/java/api/core/response.md) - Query response reference
- [LiveStream](/docs/reference/java/api/core/live-stream.md) - Live query reference
- [Connecting to SurrealDB](/docs/reference/java/concepts/connecting-to-surrealdb.md) - Connection protocols and patterns
- [Authentication](/docs/reference/java/concepts/authentication.md) - Authentication concepts
- [SurrealQL](/docs/reference/query-language.md) - Query language reference
- [DEFINE USER](/docs/reference/query-language/statements/define/user.md) - System user configuration
- [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md) - Record access method configuration

---

Source: https://surrealdb.com/docs/reference/java/api/core/transaction

# Transaction

The Transaction class provides methods for executing queries within an atomic transaction.

The `Transaction` class wraps a set of operations into an atomic unit. Changes made within a transaction are only applied when committed, and can be rolled back by cancelling. Transactions are created by calling [`.beginTransaction()`](/docs/reference/java/api/core/surreal.md#begin-transaction) on a `Surreal` instance.

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Methods

> [!NOTE]
> The `Transaction` class only supports `.query()` with raw SurrealQL strings. Parameterised queries via `.queryBind()` are not available inside transactions. To pass dynamic values, interpolate them directly in the SurrealQL string or use SurrealQL parameters defined earlier in the transaction.

### `.query(sql)` {#query}

Executes a SurrealQL query within the transaction. The query results are not visible outside the transaction until it is committed.

```java title="Method Syntax"
tx.query(sql)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>sql</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The SurrealQL query string to execute within the transaction.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Response`](/docs/reference/java/api/core/response.md)

```java title="Example"
Transaction tx = db.beginTransaction();
Response response = tx.query("CREATE person SET name = 'Alice'");
tx.commit();
```

### `.commit()` {#commit}

Commits the transaction, applying all changes made within it to the database. After committing, the transaction object should not be reused.

```java title="Method Syntax"
tx.commit()
```

**Returns:** `void`

```java title="Example"
Transaction tx = db.beginTransaction();
tx.query("CREATE person SET name = 'Alice'");
tx.query("CREATE person SET name = 'Bob'");
tx.commit();
```

### `.cancel()` {#cancel}

Cancels the transaction, discarding all changes made within it. No data is written to the database. After cancelling, the transaction object should not be reused.

```java title="Method Syntax"
tx.cancel()
```

**Returns:** `void`

```java title="Example"
Transaction tx = db.beginTransaction();
tx.query("DELETE person");
tx.cancel();
```

---

## Complete example

```java title="Atomic transfer"
import com.surrealdb.Surreal;
import com.surrealdb.Transaction;
import com.surrealdb.signin.RootCredential;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("bank").useDb("ledger");
    db.signin(new RootCredential("root", "root"));

    Transaction tx = db.beginTransaction();
    try {
        tx.query("UPDATE accounts:alice SET balance = balance - 200");
        tx.query("UPDATE accounts:bob SET balance = balance + 200");
        tx.commit();
    } catch (Exception e) {
        tx.cancel();
        throw e;
    }
}
```

---

## See also

- [Surreal](/docs/reference/java/api/core/surreal.md) - Connection and method reference
- [Response](/docs/reference/java/api/core/response.md) - Query response reference
- [Transactions](/docs/reference/java/concepts/transactions.md) - Transaction concepts and patterns
- [SurrealQL BEGIN](/docs/reference/query-language/statements/begin.md) - Server-side transaction syntax

---

Source: https://surrealdb.com/docs/reference/java/api/errors

# Errors

The Java SDK provides a structured exception hierarchy for handling errors from SurrealDB.

The Java SDK uses a hierarchy of exceptions rooted at `SurrealException`. Server-returned errors are represented by `ServerException` and its subclasses, which provide structured access to error details, kinds, and cause chains.

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Exception hierarchy

- `SurrealException` (extends `RuntimeException`)
  - `ServerException`
    - `NotFoundException`
    - `NotAllowedException`
    - `QueryException`
    - `AlreadyExistsException`
    - `ValidationException`
    - `ConfigurationException`
    - `SerializationException`
    - `InternalException`
    - `ThrownException`

---

## `SurrealException` {#surreal-exception}

Base exception for all SDK errors. Extends `RuntimeException`.

All exceptions thrown by the Java SDK are subclasses of `SurrealException`, making it possible to catch all SDK-related errors with a single catch block.

```java title="Example"
try {
    db.query("SELECT * FROM users");
} catch (SurrealException e) {
    System.err.println("SDK error: " + e.getMessage());
}
```

---

## `ServerException` {#server-exception}

Base class for all server-returned errors. Extends `SurrealException`.

`ServerException` provides structured access to the error kind, details, and cause chain returned by the server. All specific server error types extend this class.

### `.getKind()` {#get-kind}

Returns the error kind as a string.

```java title="Method Syntax"
exception.getKind()
```

**Returns:** `String`

```java title="Example"
try {
    db.select(Person.class, new RecordId("person", "missing"));
} catch (ServerException e) {
    String kind = e.getKind();
}
```

### `.getKindEnum()` {#get-kind-enum}

Returns the error kind as an [`ErrorKind`](#error-kind) enum value.

```java title="Method Syntax"
exception.getKindEnum()
```

**Returns:** `ErrorKind`

```java title="Example"
try {
    db.query("INVALID QUERY");
} catch (ServerException e) {
    ErrorKind kind = e.getKindEnum();
}
```

### `.getDetails()` {#get-details}

Returns structured error details provided by the server.

```java title="Method Syntax"
exception.getDetails()
```

**Returns:** `Object`

### `.getServerCause()` {#get-server-cause}

Returns the typed server cause if the error was caused by another server error.

```java title="Method Syntax"
exception.getServerCause()
```

**Returns:** `ServerException`

### `.hasKind(kind)` {#has-kind}

Checks if the error or any error in its cause chain matches a specific kind.

```java title="Method Syntax"
exception.hasKind(kind)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>kind</code> _(required)_</td>
            <td><code>String</code> or <code>ErrorKind</code></td>
            <td>The error kind to check for.</td>
        </tr>
    </tbody>
</table>

**Returns:** `boolean`

```java title="Example"
try {
    db.query("SELECT * FROM protected_table");
} catch (ServerException e) {
    if (e.hasKind(ErrorKind.NOT_ALLOWED)) {
        System.err.println("Permission denied");
    }
}
```

### `.findCause(kind)` {#find-cause}

Finds the first error in the cause chain that matches a specific kind.

```java title="Method Syntax"
exception.findCause(kind)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>kind</code> _(required)_</td>
            <td><code>String</code> or <code>ErrorKind</code></td>
            <td>The error kind to search for.</td>
        </tr>
    </tbody>
</table>

**Returns:** `ServerException`

```java title="Example"
try {
    db.query("CREATE person SET name = 'Alice'");
} catch (ServerException e) {
    ServerException cause = e.findCause(ErrorKind.ALREADY_EXISTS);
    if (cause != null) {
        System.err.println("Duplicate: " + cause.getMessage());
    }
}
```

---

## `ErrorKind` {#error-kind}

Enum representing error categories returned by the server.

| Value | Description |
|---|---|
| `VALIDATION` | Data validation failed |
| `CONFIGURATION` | Configuration error |
| `THROWN` | Explicitly thrown error from SurrealQL |
| `QUERY` | Query execution error |
| `SERIALIZATION` | Serialisation/deserialisation error |
| `NOT_ALLOWED` | Operation not permitted |
| `NOT_FOUND` | Resource not found |
| `ALREADY_EXISTS` | Resource already exists |
| `CONNECTION` | Connection error |
| `INTERNAL` | Internal server error |
| `UNKNOWN` | Unknown error kind |

### `ErrorKind.fromString(kind)` {#from-string}

Converts a string to an `ErrorKind` enum value. Returns `UNKNOWN` if the string does not match any known kind.

```java title="Method Syntax"
ErrorKind.fromString(kind)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>kind</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The error kind string to convert.</td>
        </tr>
    </tbody>
</table>

**Returns:** `ErrorKind`

```java title="Example"
ErrorKind kind = ErrorKind.fromString("NotFound");
```

---

## `NotFoundException` {#not-found-exception}

Thrown when a requested resource does not exist. Extends `ServerException`.

### Additional methods

| Method | Returns | Description |
|---|---|---|
| `.getTableName()` | `String` | The table that was queried |
| `.getRecordId()` | `String` | The record ID that was not found |
| `.getMethodName()` | `String` | The method that triggered the error |
| `.getNamespaceName()` | `String` | The namespace that was not found |
| `.getDatabaseName()` | `String` | The database that was not found |
| `.getSessionId()` | `String` | The session ID that was not found |

```java title="Example"
try {
    db.select(Person.class, new RecordId("person", "nonexistent"));
} catch (NotFoundException e) {
    String table = e.getTableName();
    String record = e.getRecordId();
}
```

---

## `NotAllowedException` {#not-allowed-exception}

Thrown when an operation is not permitted. Extends `ServerException`.

### Additional methods

| Method | Returns | Description |
|---|---|---|
| `.isTokenExpired()` | `boolean` | Whether the authentication token has expired |
| `.isInvalidAuth()` | `boolean` | Whether the authentication credentials are invalid |
| `.isScriptingBlocked()` | `boolean` | Whether scripting is disabled on the server |
| `.getMethodName()` | `String` | The method that was not allowed |
| `.getFunctionName()` | `String` | The function that was not allowed |
| `.getTargetName()` | `String` | The target resource that was not accessible |

```java title="Example"
try {
    db.query("SELECT * FROM protected_table");
} catch (NotAllowedException e) {
    if (e.isTokenExpired()) {
        db.signin(new RootCredential("root", "root"));
    }
}
```

---

## `QueryException` {#query-exception}

Thrown when a query fails to execute. Extends `ServerException`.

### Additional methods

| Method | Returns | Description |
|---|---|---|
| `.isNotExecuted()` | `boolean` | Whether the query was not executed |
| `.isTimedOut()` | `boolean` | Whether the query timed out |
| `.isCancelled()` | `boolean` | Whether the query was cancelled |
| `.getTimeout()` | `Map<String, Object>` | The timeout details if the query timed out |

```java title="Example"
try {
    db.query("SELECT * FROM large_table TIMEOUT 1s");
} catch (QueryException e) {
    if (e.isTimedOut()) {
        System.err.println("Query timed out: " + e.getTimeout());
    }
}
```

---

## `AlreadyExistsException` {#already-exists-exception}

Thrown when attempting to create a resource that already exists. Extends `ServerException`.

### Additional methods

| Method | Returns | Description |
|---|---|---|
| `.getRecordId()` | `String` | The record ID that already exists |
| `.getTableName()` | `String` | The table containing the duplicate |
| `.getSessionId()` | `String` | The session ID that already exists |
| `.getNamespaceName()` | `String` | The namespace that already exists |
| `.getDatabaseName()` | `String` | The database that already exists |

```java title="Example"
try {
    db.create(Person.class, new RecordId("person", "alice"), person);
} catch (AlreadyExistsException e) {
    String recordId = e.getRecordId();
}
```

---

## `ValidationException` {#validation-exception}

Thrown when data validation fails. Extends `ServerException`. No additional methods.

---

## `ConfigurationException` {#configuration-exception}

Thrown when there is a configuration error. Extends `ServerException`. No additional methods.

---

## `SerializationException` {#serialization-exception}

Thrown when serialisation or deserialisation fails. Extends `ServerException`. No additional methods.

---

## `InternalException` {#internal-exception}

Thrown for internal server errors. Extends `ServerException`. No additional methods.

---

## `ThrownException` {#thrown-exception}

Thrown when a SurrealQL [`THROW`](/docs/reference/query-language/statements/throw.md) statement is executed. Extends `ServerException`. No additional methods.

```java title="Example"
try {
    db.query("THROW 'custom error message'");
} catch (ThrownException e) {
    System.err.println("SurrealQL threw: " + e.getMessage());
}
```

---

## See also

- [Surreal](/docs/reference/java/api/core/surreal.md) - Connection and method reference
- [Error handling](/docs/reference/java/concepts/error-handling.md) - Error handling concepts and patterns
- [SurrealQL THROW](/docs/reference/query-language/statements/throw.md) - Throwing custom errors from queries

---

Source: https://surrealdb.com/docs/reference/java/api/types

# Java types

The Java SDK provides credential classes, enums, and helper types for authentication and data operations.

The SDK provides several supporting types for authentication, update operations, and relation modelling. These types are used as parameters to methods on the [`Surreal`](/docs/reference/java/api/core/surreal.md) class.

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Authentication types

### `Credential` {#credential}

Interface. Base type for all credentials passed to [`signin()`](/docs/reference/java/api/core/surreal.md#signin) and [`signup()`](/docs/reference/java/api/core/surreal.md#signup).

### `Signin` {#signin}

Interface. Extends `Credential`. Marker interface for sign-in credentials.

---

### `RootCredential` {#root-credential}

Implements `Signin`. Authenticates as a root user.

```java title="Constructor"
RootCredential(String username, String password)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>username</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The root username.</td>
        </tr>
        <tr>
            <td><code>password</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The root password.</td>
        </tr>
    </tbody>
</table>

#### Methods

| Method | Returns | Description |
|---|---|---|
| `.getUsername()` | `String` | The root username |
| `.getPassword()` | `String` | The root password |

```java title="Example"
db.signin(new RootCredential("root", "root"));
```

---

### `NamespaceCredential` {#namespace-credential}

Extends `RootCredential`. Authenticates as a namespace user.

```java title="Constructor"
NamespaceCredential(String username, String password, String namespace)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>username</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The namespace username.</td>
        </tr>
        <tr>
            <td><code>password</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The namespace password.</td>
        </tr>
        <tr>
            <td><code>namespace</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The namespace to authenticate against.</td>
        </tr>
    </tbody>
</table>

#### Additional method

| Method | Returns | Description |
|---|---|---|
| `.getNamespace()` | `String` | The target namespace |

```java title="Example"
db.signin(new NamespaceCredential("ns_user", "ns_pass", "surrealdb"));
```

---

### `DatabaseCredential` {#database-credential}

Extends `NamespaceCredential`. Authenticates as a database user.

```java title="Constructor"
DatabaseCredential(String username, String password, String namespace, String database)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>username</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The database username.</td>
        </tr>
        <tr>
            <td><code>password</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The database password.</td>
        </tr>
        <tr>
            <td><code>namespace</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The namespace containing the database.</td>
        </tr>
        <tr>
            <td><code>database</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The database to authenticate against.</td>
        </tr>
    </tbody>
</table>

#### Additional method

| Method | Returns | Description |
|---|---|---|
| `.getDatabase()` | `String` | The target database |

```java title="Example"
db.signin(new DatabaseCredential("db_user", "db_pass", "surrealdb", "docs"));
```

---

### `RecordCredential` {#record-credential}

Implements `Credential`. Authenticates as a record user via an access method defined with [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md).

```java title="Constructors"
RecordCredential(String namespace, String database, String access, Object params)
RecordCredential(String access, Object params)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>namespace</code> _(optional)_</td>
            <td><code>String</code></td>
            <td>The namespace. Omit to use the session namespace.</td>
        </tr>
        <tr>
            <td><code>database</code> _(optional)_</td>
            <td><code>String</code></td>
            <td>The database. Omit to use the session database.</td>
        </tr>
        <tr>
            <td><code>access</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The access method name.</td>
        </tr>
        <tr>
            <td><code>params</code> _(required)_</td>
            <td><code>Object</code></td>
            <td>Additional fields required by the access definition.</td>
        </tr>
    </tbody>
</table>

#### Methods

| Method | Returns | Description |
|---|---|---|
| `.getNamespace()` | `String` | The target namespace |
| `.getDatabase()` | `String` | The target database |
| `.getAccess()` | `String` | The access method name |
| `.getParams()` | `Object` | The additional parameters |

```java title="Example"
Token token = db.signup(new RecordCredential(
    "surrealdb", "docs", "user_access",
    Map.of("email", "user@example.com", "password", "s3cret")
));
```

---

### `BearerCredential` {#bearer-credential}

Implements `Credential`. Authenticates with an existing token.

```java title="Constructor"
BearerCredential(String token)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>token</code> _(required)_</td>
            <td><code>String</code></td>
            <td>A valid JWT token string.</td>
        </tr>
    </tbody>
</table>

#### Method

| Method | Returns | Description |
|---|---|---|
| `.getToken()` | `String` | The bearer token |

```java title="Example"
db.signin(new BearerCredential("eyJhbGciOiJIUzI1NiIs..."));
```

---

### `Token` {#token}

Represents authentication tokens returned by [`signin()`](/docs/reference/java/api/core/surreal.md#signin) and [`signup()`](/docs/reference/java/api/core/surreal.md#signup).

```java title="Constructors"
Token(String access, String refresh)
Token(String token)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>access</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The access token (JWT).</td>
        </tr>
        <tr>
            <td><code>refresh</code> _(optional)_</td>
            <td><code>String</code></td>
            <td>The refresh token. May be <code>null</code>.</td>
        </tr>
        <tr>
            <td><code>token</code> _(required)_</td>
            <td><code>String</code></td>
            <td>A single token string (legacy constructor).</td>
        </tr>
    </tbody>
</table>

#### Methods

| Method | Returns | Description |
|---|---|---|
| `.getAccess()` | `String` | The access token (JWT) |
| `.getRefresh()` | `String` | The refresh token (may be `null`) |
| `.getToken()` | `String` | The access token |

```java title="Example"
Token token = db.signin(new RootCredential("root", "root"));
String jwt = token.getAccess();
String refresh = token.getRefresh();
```

---

## Operation types

### `UpType` {#up-type}

Enum for [`update()`](/docs/reference/java/api/core/surreal.md#update) and [`upsert()`](/docs/reference/java/api/core/surreal.md#upsert) operation types.

| Value | Description |
|---|---|
| `CONTENT` | Replaces the entire record content |
| `MERGE` | Merges fields with the existing record |
| `PATCH` | Applies partial changes |

```java title="Example"
db.update(Person.class, new RecordId("person", "alice"), UpType.MERGE, updates);
```

---

## Relation types

### `Relation` {#relation}

POJO base class for graph relations. Contains the standard relation fields. Used with [`relate()`](/docs/reference/java/api/core/surreal.md#relate).

#### Fields

| Field | Type | Description |
|---|---|---|
| `id` | `RecordId` | The relation record ID |
| `in` | `RecordId` | The source record |
| `out` | `RecordId` | The target record |

```java title="Example"
public class Likes extends Relation {
    public String createdAt;
}

Likes like = db.relate(Likes.class,
    new RecordId("person", "alice"),
    "likes",
    new RecordId("post", "post1")
);
```

---

### `InsertRelation` {#insert-relation}

POJO for inserting relations. Uses `Id` instead of `RecordId` for the `id` field. Used with [`insertRelation()`](/docs/reference/java/api/core/surreal.md#insert-relation).

#### Fields

| Field | Type | Description |
|---|---|---|
| `id` | `Id` | The relation ID |
| `in` | `RecordId` | The source record |
| `out` | `RecordId` | The target record |

```java title="Example"
public class Likes extends InsertRelation {
    public String createdAt;
}

Likes like = new Likes();
like.in = new RecordId("person", "alice");
like.out = new RecordId("post", "post1");
like.createdAt = "2025-01-01T00:00:00Z";

Likes result = db.insertRelation(Likes.class, "likes", like);
```

---

## Utility types

### `NsDb` {#ns-db}

Holds a namespace and database pair.

#### Methods

| Method | Returns | Description |
|---|---|---|
| `.getNamespace()` | `String` | The namespace |
| `.getDatabase()` | `String` | The database |

---

## See also

- [Surreal](/docs/reference/java/api/core/surreal.md) - Connection and method reference
- [Authentication](/docs/reference/java/concepts/authentication.md) - Authentication concepts and patterns
- [Data manipulation](/docs/reference/java/concepts/data-manipulation.md) - CRUD operation patterns
- [DEFINE USER](/docs/reference/query-language/statements/define/user.md) - System user definition for root, namespace, and database credentials
- [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md) - Access method definition for record-level authentication

---

Source: https://surrealdb.com/docs/reference/java/api/values/datetime

# Datetime

SurrealDB datetime values map to Java's ZonedDateTime class.

SurrealDB [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md) values map to Java's `java.time.ZonedDateTime`. The SDK handles conversion automatically when deserialising query results into [`Value`](/docs/reference/java/api/values/value.md) objects or POJOs.

---

## Value methods

### `.isDateTime()` {#is-datetime}

Checks if the value is a datetime.

```java title="Method Syntax"
value.isDateTime()
```

**Returns:** `boolean`

### `.getDateTime()` {#get-datetime}

Returns the datetime as a `ZonedDateTime`.

```java title="Method Syntax"
value.getDateTime()
```

**Returns:** `java.time.ZonedDateTime`

---

## POJO mapping

When using typed methods, datetime fields in your POJO should be declared as `ZonedDateTime` for reads unless you need a narrower type (`Instant`, `OffsetDateTime`, or `LocalDateTime`). When writing records, you can also use those types plus `java.util.Date` - see [Class converters](/docs/reference/java/concepts/class-converters.md#temporal-types).

```java
import java.time.ZonedDateTime;

public class Event {
    public RecordId id;
    public String title;
    public ZonedDateTime createdAt;

    public Event() {}
}

Optional<Event> event = db.select(Event.class, new RecordId("event", "conf"));
ZonedDateTime when = event.get().createdAt;
```

---

## Example

```java title="Working with datetime values"
import com.surrealdb.Surreal;
import com.surrealdb.Response;
import com.surrealdb.Value;
import com.surrealdb.signin.RootCredential;
import java.time.ZonedDateTime;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");
    db.signin(new RootCredential("root", "root"));

    Response response = db.query("RETURN time::now()");
    Value result = response.take(0);

    if (result.isDateTime()) {
        ZonedDateTime now = result.getDateTime();
    }
}
```

---

## See also

- [Value types](/docs/reference/java/concepts/value-types.md) - Type mapping overview
- [Value](/docs/reference/java/api/values/value.md) - The Value class reference
- [SurrealQL datetimes](/docs/reference/query-language/language-primitives/data-types/datetimes.md) - Datetime types in SurrealDB

---

Source: https://surrealdb.com/docs/reference/java/api/values/duration

# Duration

SurrealDB duration values map to Java's Duration class.

SurrealDB [`duration`](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) values map to Java's `java.time.Duration`. The SDK handles conversion automatically when deserialising query results into [`Value`](/docs/reference/java/api/values/value.md) objects or POJOs.

---

## Value methods

### `.isDuration()` {#is-duration}

Checks if the value is a duration.

```java title="Method Syntax"
value.isDuration()
```

**Returns:** `boolean`

### `.getDuration()` {#get-duration}

Returns the duration as a `java.time.Duration`.

```java title="Method Syntax"
value.getDuration()
```

**Returns:** `java.time.Duration`

---

## POJO mapping

When using typed methods, duration fields in your POJO should be declared as `Duration`.

```java
import java.time.Duration;

public class Task {
    public RecordId id;
    public String name;
    public Duration timeout;

    public Task() {}
}

Optional<Task> task = db.select(Task.class, new RecordId("task", "build"));
Duration timeout = task.get().timeout;
```

---

## Example

```java title="Working with duration values"
import com.surrealdb.Surreal;
import com.surrealdb.Response;
import com.surrealdb.Value;
import com.surrealdb.signin.RootCredential;
import java.time.Duration;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");
    db.signin(new RootCredential("root", "root"));

    Response response = db.query("RETURN 1h30m");
    Value result = response.take(0);

    if (result.isDuration()) {
        Duration d = result.getDuration();
    }
}
```

---

## See also

- [Value types](/docs/reference/java/concepts/value-types.md) - Type mapping overview
- [Value](/docs/reference/java/api/values/value.md) - The Value class reference
- [SurrealQL durations](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) - Duration types in SurrealDB

---

Source: https://surrealdb.com/docs/reference/java/api/values/file-ref

# FileRef

The FileRef class represents a reference to a file stored in SurrealDB.

The `FileRef` class represents a reference to a [file stored in SurrealDB](/docs/reference/query-language/language-primitives/data-types/files.md). A file reference consists of a storage bucket name and a unique file key within that bucket.

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Methods

### `.getBucket()` {#get-bucket}

Returns the name of the storage bucket containing the file.

```java title="Method Syntax"
fileRef.getBucket()
```

**Returns:** `String`

### `.getKey()` {#get-key}

Returns the unique key identifying the file within its bucket.

```java title="Method Syntax"
fileRef.getKey()
```

**Returns:** `String`

---

## Example

```java title="Working with file references"
import com.surrealdb.Surreal;
import com.surrealdb.Response;
import com.surrealdb.Value;
import com.surrealdb.FileRef;
import com.surrealdb.signin.RootCredential;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");
    db.signin(new RootCredential("root", "root"));

    Response response = db.query("SELECT avatar FROM user:tobie");
    Value result = response.take(0);

    if (result.isFile()) {
        FileRef file = result.getFile();
        String bucket = file.getBucket();
        String key = file.getKey();
    }
}
```

---

## See also

- [Value types](/docs/reference/java/concepts/value-types.md) - Type mapping overview
- [Value](/docs/reference/java/api/values/value.md) - The Value class reference
- [SurrealQL files](/docs/reference/query-language/language-primitives/data-types/files.md) - File storage in SurrealDB

---

Source: https://surrealdb.com/docs/reference/java/api/values/geometry

# Geometry

The Geometry class represents SurrealDB geometric data types.

The `Geometry` class represents [SurrealDB geometric data types](/docs/reference/query-language/language-primitives/data-types/geometries.md). It currently supports point geometry, mapping to Java's `Point2D.Double` from `java.awt.geom`.

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Methods

### `.isPoint()` {#is-point}

Checks if the geometry value is a point.

```java title="Method Syntax"
geometry.isPoint()
```

**Returns:** `boolean`

### `.getPoint()` {#get-point}

Returns the point coordinates as a `Point2D.Double`. The `x` coordinate represents longitude and `y` represents latitude.

```java title="Method Syntax"
geometry.getPoint()
```

**Returns:** `Point2D.Double` (`java.awt.geom`)

---

## Example

```java title="Working with geometry values"
import com.surrealdb.Surreal;
import com.surrealdb.Response;
import com.surrealdb.Value;
import com.surrealdb.Geometry;
import com.surrealdb.signin.RootCredential;
import java.awt.geom.Point2D;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");
    db.signin(new RootCredential("root", "root"));

    Response response = db.query(
        "RETURN <geometry> { type: 'Point', coordinates: [10.0, 20.0] }"
    );
    Value result = response.take(0);
    Geometry geo = result.getGeometry();

    if (geo.isPoint()) {
        Point2D.Double point = geo.getPoint();
    }
}
```

---

## See also

- [Value types](/docs/reference/java/concepts/value-types.md) - Type mapping overview
- [Value](/docs/reference/java/api/values/value.md) - The Value class reference
- [SurrealQL geometries](/docs/reference/query-language/language-primitives/data-types/geometries.md) - Geometry types in SurrealDB

---

Source: https://surrealdb.com/docs/reference/java/api/values/range

# Range

SurrealDB range values are accessed through Value methods for start and end bounds.

SurrealDB range values represent a bounded interval. In the Java SDK, ranges are accessed through [`Value`](/docs/reference/java/api/values/value.md) methods that return the start and end bounds.

> [!NOTE]
> For record ID ranges used in CRUD operations (select, update, delete), see [`RecordIdRange`](/docs/reference/java/api/values/record-id.md#record-id-range).

---

## Value methods

### `.isRange()` {#is-range}

Checks if the value is a range.

```java title="Method Syntax"
value.isRange()
```

**Returns:** `boolean`

### `.getRangeStart()` {#get-range-start}

Returns the start bound of the range, if present.

```java title="Method Syntax"
value.getRangeStart()
```

**Returns:** `Optional<`[`Value`](/docs/reference/java/api/values/value.md)`>`

### `.getRangeEnd()` {#get-range-end}

Returns the end bound of the range, if present.

```java title="Method Syntax"
value.getRangeEnd()
```

**Returns:** `Optional<`[`Value`](/docs/reference/java/api/values/value.md)`>`

---

## Example

```java title="Working with range values"
import com.surrealdb.Surreal;
import com.surrealdb.Response;
import com.surrealdb.Value;
import com.surrealdb.signin.RootCredential;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");
    db.signin(new RootCredential("root", "root"));

    Response response = db.query("RETURN 1..10");
    Value result = response.take(0);

    if (result.isRange()) {
        Optional<Value> start = result.getRangeStart();
        Optional<Value> end = result.getRangeEnd();
    }
}
```

---

## See also

- [Value types](/docs/reference/java/concepts/value-types.md) - Type mapping overview
- [Value](/docs/reference/java/api/values/value.md) - The Value class reference
- [RecordIdRange](/docs/reference/java/api/values/record-id.md#record-id-range) - Range-based CRUD operations

---

Source: https://surrealdb.com/docs/reference/java/api/values/record-id

# RecordId

The RecordId class represents a SurrealDB record identifier consisting of a table name and an ID value.

A `RecordId` uniquely identifies a record in SurrealDB. It consists of a table name and an ID value. The ID can be a `long`, `String`, `UUID`, or a composite key using `Array` or `Object`. See the [SurrealQL record ID documentation](/docs/reference/query-language/language-primitives/data-types/record-ids.md) for details on how record identifiers work in SurrealDB.

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Constructors

### `RecordId(String table, long id)` {#constructor-long}

Creates a record ID with a numeric identifier.

```java title="Method Syntax"
new RecordId(table, id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table name.</td>
        </tr>
        <tr>
            <td><code>id</code> _(required)_</td>
            <td><code>long</code></td>
            <td>The numeric record identifier.</td>
        </tr>
    </tbody>
</table>

```java title="Example"
RecordId id = new RecordId("person", 1);
```

### `RecordId(String table, String id)` {#constructor-string}

Creates a record ID with a string identifier.

```java title="Method Syntax"
new RecordId(table, id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table name.</td>
        </tr>
        <tr>
            <td><code>id</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The string record identifier.</td>
        </tr>
    </tbody>
</table>

```java title="Example"
RecordId id = new RecordId("person", "tobie");
```

### `RecordId(String table, UUID id)` {#constructor-uuid}

Creates a record ID with a UUID identifier.

```java title="Method Syntax"
new RecordId(table, id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table name.</td>
        </tr>
        <tr>
            <td><code>id</code> _(required)_</td>
            <td><code>UUID</code></td>
            <td>The UUID record identifier.</td>
        </tr>
    </tbody>
</table>

```java title="Example"
RecordId id = new RecordId("person", UUID.randomUUID());
```

### `RecordId(String table, Array id)` {#constructor-array}

Creates a record ID with a composite key using an array.

```java title="Method Syntax"
new RecordId(table, id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table name.</td>
        </tr>
        <tr>
            <td><code>id</code> _(required)_</td>
            <td><code>Array</code></td>
            <td>The composite key as an array.</td>
        </tr>
    </tbody>
</table>

### `RecordId(String table, Object id)` {#constructor-object}

Creates a record ID with a composite key using an object.

```java title="Method Syntax"
new RecordId(table, id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table name.</td>
        </tr>
        <tr>
            <td><code>id</code> _(required)_</td>
            <td><code>Object</code></td>
            <td>The composite key as an object.</td>
        </tr>
    </tbody>
</table>

---

## Methods

### `.getTable()` {#get-table}

Returns the table name of this record ID.

```java title="Method Syntax"
recordId.getTable()
```

**Returns:** `String`

```java title="Example"
RecordId id = new RecordId("person", "tobie");
String table = id.getTable();
```

### `.getId()` {#get-id}

Returns the identifier part of this record ID.

```java title="Method Syntax"
recordId.getId()
```

**Returns:** [`Id`](#id)

```java title="Example"
RecordId id = new RecordId("person", "tobie");
Id identifier = id.getId();
```

---

## `Id` {#id}

The `Id` class represents the identifier part of a `RecordId`. It wraps the underlying value and provides type checking and extraction methods.

### Static factory methods

#### `Id.from(long id)` {#from-long}

Creates an `Id` from a numeric value.

```java title="Method Syntax"
Id.from(id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>id</code> _(required)_</td>
            <td><code>long</code></td>
            <td>The numeric identifier.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Id`

#### `Id.from(String id)` {#from-string}

Creates an `Id` from a string value.

```java title="Method Syntax"
Id.from(id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>id</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The string identifier.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Id`

#### `Id.from(UUID id)` {#from-uuid}

Creates an `Id` from a UUID value.

```java title="Method Syntax"
Id.from(id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>id</code> _(required)_</td>
            <td><code>UUID</code></td>
            <td>The UUID identifier.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Id`

### Type checking methods

| Method | Returns `true` when |
|---|---|
| `isLong()` | The ID is a numeric value |
| `isString()` | The ID is a string value |
| `isUuid()` | The ID is a UUID value |
| `isArray()` | The ID is a composite array key |
| `isObject()` | The ID is a composite object key |

### Getter methods

| Method | Return Type | Description |
|---|---|---|
| `getLong()` | `long` | Returns the numeric ID value |
| `getString()` | `String` | Returns the string ID value |
| `getUuid()` | `UUID` | Returns the UUID ID value |
| `getArray()` | `Array` | Returns the composite array key |
| `getObject()` | `Object` | Returns the composite object key |

---

## `RecordIdRange` {#record-id-range}

The `RecordIdRange` class represents a range of record IDs within a table. It can be used with methods like `select` to retrieve a subset of records.

### Constructor

#### `RecordIdRange(String table, Id start, Id end)` {#range-constructor}

Creates a range of record IDs. Pass `null` for `start` or `end` to leave that bound open.

```java title="Method Syntax"
new RecordIdRange(table, start, end)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table name.</td>
        </tr>
        <tr>
            <td><code>start</code> _(optional)_</td>
            <td><code>Id</code></td>
            <td>The start of the range. Pass <code>null</code> for an unbounded start.</td>
        </tr>
        <tr>
            <td><code>end</code> _(optional)_</td>
            <td><code>Id</code></td>
            <td>The end of the range. Pass <code>null</code> for an unbounded end.</td>
        </tr>
    </tbody>
</table>

### Methods

#### `.getTable()` {#range-get-table}

Returns the table name of this range.

```java title="Method Syntax"
range.getTable()
```

**Returns:** `String`

#### `.getStart()` {#range-get-start}

Returns the start bound of the range, or `null` if unbounded.

```java title="Method Syntax"
range.getStart()
```

**Returns:** `Id` (nullable)

#### `.getEnd()` {#range-get-end}

Returns the end bound of the range, or `null` if unbounded.

```java title="Method Syntax"
range.getEnd()
```

**Returns:** `Id` (nullable)

### Example

```java title="Selecting a range of records"
RecordIdRange range = new RecordIdRange("users", Id.from(1), Id.from(100));
List<Value> results = db.select(range);
```

---

## See also

- [Value](/docs/reference/java/api/values/value.md) - The Value class reference
- [Surreal](/docs/reference/java/api/core/surreal.md) - Connection and method reference
- [Data manipulation](/docs/reference/java/concepts/data-manipulation.md) - Working with records
- [SurrealQL record IDs](/docs/reference/query-language/language-primitives/data-types/record-ids.md) - Record identifier formats and types

---

Source: https://surrealdb.com/docs/reference/java/api/values/table

# Table

SurrealDB table values map to Java strings via Value methods.

SurrealDB table values represent table names and are returned as `String` values in the Java SDK. The [`Value`](/docs/reference/java/api/values/value.md) class provides methods to check for and extract table values.

---

## Value methods

### `.isTable()` {#is-table}

Checks if the value is a table name.

```java title="Method Syntax"
value.isTable()
```

**Returns:** `boolean`

### `.getTable()` {#get-table}

Returns the table name as a `String`.

```java title="Method Syntax"
value.getTable()
```

**Returns:** `String`

---

## Example

```java title="Working with table values"
import com.surrealdb.Surreal;
import com.surrealdb.Response;
import com.surrealdb.Value;
import com.surrealdb.signin.RootCredential;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");
    db.signin(new RootCredential("root", "root"));

    Response response = db.query("RETURN <table> 'person'");
    Value result = response.take(0);

    if (result.isTable()) {
        String tableName = result.getTable();
    }
}
```

---

## See also

- [Value types](/docs/reference/java/concepts/value-types.md) - Type mapping overview
- [Value](/docs/reference/java/api/values/value.md) - The Value class reference
- [SurrealQL tables](/docs/reference/query-language/statements/define/table.md) - Table definitions in SurrealDB

---

Source: https://surrealdb.com/docs/reference/java/api/values/value

# Value

The Value class represents any SurrealDB value and provides type checking and conversion methods.

The `Value` class is the untyped representation of any SurrealDB value. It provides methods to check the underlying type and extract the value as a native Java type or SDK class. You can also convert a `Value` to a Java POJO using `.get(Class<T>)`.

**Source:** [surrealdb.java](https://github.com/surrealdb/surrealdb.java)

---

## Type checking methods {#type-checking}

Each method returns `true` when the `Value` holds the corresponding SurrealDB type.

| Method | Returns `true` when |
|---|---|
| `isNone()` | Value is `NONE` |
| `isNull()` | Value is `NULL` |
| `isBoolean()` | Value is a boolean |
| `isDouble()` | Value is a float/double |
| `isLong()` | Value is an integer/long |
| `isBigDecimal()` | Value is a decimal |
| `isString()` | Value is a string |
| `isUuid()` | Value is a UUID |
| `isArray()` | Value is an array |
| `isObject()` | Value is an object |
| `isGeometry()` | Value is a geometry |
| `isDateTime()` | Value is a datetime |
| `isDuration()` | Value is a duration |
| `isBytes()` | Value is binary data |
| `isRecordId()` | Value is a record ID |
| `isFile()` | Value is a file reference |
| `isRange()` | Value is a range |
| `isTable()` | Value is a table name |

---

## Getter methods {#getters}

Each getter extracts the underlying value. Call the corresponding type check method first to avoid unexpected results.

| Method | Return Type |
|---|---|
| `getBoolean()` | `boolean` |
| `getDouble()` | `double` |
| `getLong()` | `long` |
| `getBigDecimal()` | `BigDecimal` |
| `getString()` | `String` |
| `getUuid()` | `UUID` |
| `getArray()` | [`Array`](#array) |
| `getObject()` | [`Object`](#object) |
| `getGeometry()` | [`Geometry`](/docs/reference/java/api/values/geometry.md) |
| `getDateTime()` | `ZonedDateTime` |
| `getDuration()` | `Duration` |
| `getBytes()` | `byte[]` |
| `getRecordId()` | [`RecordId`](/docs/reference/java/api/values/record-id.md) |
| `getFile()` | [`FileRef`](/docs/reference/java/api/values/file-ref.md) |
| `getRangeStart()` | `Optional<Value>` |
| `getRangeEnd()` | `Optional<Value>` |
| `getTable()` | `String` |

---

## POJO conversion {#pojo-conversion}

### `.get(type)` {#get}

Converts the value to a Java POJO. The target class must have a public no-argument constructor. Fields are matched by name between the SurrealDB object and the Java class.

```java title="Method Syntax"
<T> T get(Class<T> type)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>type</code> _(required)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The target class to deserialise into.</td>
        </tr>
    </tbody>
</table>

**Returns:** `T`

```java title="Example"
public class Person {
    public RecordId id;
    public String name;
    public long age;
}

Response response = db.query("SELECT * FROM person:tobie");
Value value = response.take(0);
Person person = value.get(Person.class);
```

---

## `Array` {#array}

The `Array` class represents a SurrealDB array value. It implements `Iterable<Value>` and provides both untyped and typed iteration.

### Methods

#### `.get(idx)` {#array-get}

Returns the value at the specified index.

```java title="Method Syntax"
array.get(idx)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>idx</code> _(required)_</td>
            <td><code>int</code></td>
            <td>The zero-based index of the element.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Value`

#### `.len()` {#array-len}

Returns the number of elements in the array.

```java title="Method Syntax"
array.len()
```

**Returns:** `int`

#### `.iterator()` {#array-iterator}

Returns an iterator over the array elements as `Value` instances.

```java title="Method Syntax"
array.iterator()
```

**Returns:** `Iterator<Value>`

#### `.iterator(clazz)` {#array-typed-iterator}

Returns a typed iterator that deserializes each element into the specified class.

```java title="Method Syntax"
array.iterator(clazz)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>clazz</code> _(required)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The class to deserialise each element into.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Iterator<T>`

#### `.synchronizedIterator()` {#array-sync-iterator}

Returns a thread-safe iterator over the array elements.

```java title="Method Syntax"
array.synchronizedIterator()
```

**Returns:** `Iterator<Value>`

#### `.synchronizedIterator(clazz)` {#array-sync-typed-iterator}

Returns a thread-safe typed iterator that deserializes each element into the specified class.

```java title="Method Syntax"
array.synchronizedIterator(clazz)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>clazz</code> _(required)_</td>
            <td><code>Class&lt;T&gt;</code></td>
            <td>The class to deserialise each element into.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Iterator<T>`

```java title="Example"
Response response = db.query("SELECT * FROM person");
Value result = response.take(0);
Array array = result.getArray();

for (Value item : array) {
    String name = item.getObject().get("name").getString();
}

Iterator<Person> people = array.iterator(Person.class);
while (people.hasNext()) {
    Person person = people.next();
}
```

---

## `Object` {#object}

The `Object` class represents a SurrealDB object value. It implements `Iterable<Entry>` and provides key-based access to its fields.

### Methods

#### `.get(key)` {#object-get}

Returns the value associated with the specified key.

```java title="Method Syntax"
object.get(key)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>key</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The field name to look up.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Value`

#### `.len()` {#object-len}

Returns the number of key-value pairs in the object.

```java title="Method Syntax"
object.len()
```

**Returns:** `int`

#### `.iterator()` {#object-iterator}

Returns an iterator over the object's key-value pairs as `Entry` instances.

```java title="Method Syntax"
object.iterator()
```

**Returns:** `Iterator<Entry>`

#### `.synchronizedIterator()` {#object-sync-iterator}

Returns a thread-safe iterator over the object's key-value pairs.

```java title="Method Syntax"
object.synchronizedIterator()
```

**Returns:** `Iterator<Entry>`

```java title="Example"
Response response = db.query("SELECT * FROM person:tobie");
Value result = response.take(0);
Object obj = result.getObject();

Value name = obj.get("name");
int fieldCount = obj.len();

for (Entry entry : obj) {
    String key = entry.getKey();
    Value value = entry.getValue();
}
```

---

## `Entry` {#entry}

The `Entry` class represents a key-value pair in a SurrealDB object.

### Methods

#### `.getKey()` {#entry-key}

Returns the field name of this entry.

```java title="Method Syntax"
entry.getKey()
```

**Returns:** `String`

#### `.getValue()` {#entry-value}

Returns the value of this entry.

```java title="Method Syntax"
entry.getValue()
```

**Returns:** `Value`

---

## See also

- [Value types](/docs/reference/java/concepts/value-types.md) - Type mapping overview
- [RecordId](/docs/reference/java/api/values/record-id.md) - Record identifiers
- [SurrealQL data model](/docs/reference/query-language/language-primitives/data-types.md) - SurrealDB data types and structures

---

Source: https://surrealdb.com/docs/reference/java/concepts/authentication

# Authentication

The Java SDK provides methods for signing in, signing up, and managing authentication tokens.

The Java SDK supports signing in as a root, namespace, database, or [record-level user](/docs/learn/security/authentication/users.md#record-users). After signing in, the connection is [authenticated](/docs/learn/security/authentication/users.md) for all subsequent operations until the session is invalidated or the connection is closed.

You can configure authentication in your SurrealDB database using the [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md) or [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md) statements.

## API References

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#signin"><code>db.signin(credential)</code></a></td>
			<td scope="row" data-label="Description">Authenticates with the provided credentials</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#signup"><code>db.signup(credential)</code></a></td>
			<td scope="row" data-label="Description">Signs up a new record user</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#authenticate"><code>db.authenticate(token)</code></a></td>
			<td scope="row" data-label="Description">Authenticates with a JWT token</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#invalidate"><code>db.invalidate()</code></a></td>
			<td scope="row" data-label="Description">Invalidates the current authentication</td>
		</tr>
	</tbody>
</table>

## Signing in as a system user

[System users](/docs/learn/security/authentication/users.md#system-users) are defined with the [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md) statement and have access at the root, namespace, or database level. Use the corresponding [credential class](/docs/reference/java/api/types.md) to sign in.

```java
import com.surrealdb.signin.RootCredential;
import com.surrealdb.signin.NamespaceCredential;
import com.surrealdb.signin.DatabaseCredential;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");

    // Root user - full access to the entire instance
    db.signin(new RootCredential("root", "root"));

    // Namespace user - access to all databases in the namespace
    db.signin(new NamespaceCredential("tobie", "123456", "surrealdb"));

    // Database user - access to a single database
    db.signin(new DatabaseCredential("tobie", "123456", "surrealdb", "docs"));
}
```

## Signing in as a record user

Record users authenticate against a [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md) method defined on a database. Use [`RecordCredential`](/docs/reference/java/api/types.md#record-credential) with the access method name and any parameters required by the access definition.

```java
import com.surrealdb.signin.RecordCredential;

Map<String, Object> params = Map.of(
    "email", "info@surrealdb.com",
    "password", "123456"
);

Token token = db.signin(new RecordCredential(
    "surrealdb", "docs", "account", params
));
```

## Signing up a record user

The [`.signup()`](/docs/reference/java/api/core/surreal.md#signup) method registers a new record user through a record access method and returns a [`Token`](/docs/reference/java/api/types.md#token). Signup is only available for record-level access.

```java
import com.surrealdb.signin.RecordCredential;

Map<String, Object> params = Map.of(
    "email", "newuser@surrealdb.com",
    "password", "s3cureP@ss"
);

Token token = db.signup(new RecordCredential(
    "surrealdb", "docs", "account", params
));
```

## Using authentication tokens

The [`.signin()`](/docs/reference/java/api/core/surreal.md#signin) and [`.signup()`](/docs/reference/java/api/core/surreal.md#signup) methods return a [`Token`](/docs/reference/java/api/types.md#token) object. Use `.getAccess()` to retrieve the JWT [access token](/docs/learn/security/authentication/users.md#token) and `.getRefresh()` to retrieve the optional refresh token. You can store these tokens and use them later to re-authenticate without credentials.

```java
Token token = db.signin(new RootCredential("root", "root"));

String accessToken = token.getAccess();
String refreshToken = token.getRefresh();

// Later, re-authenticate with the stored token
db.authenticate(accessToken);
```

## Authenticating with a bearer token

If you have a bearer key - for example, one defined with a bearer access method - use [`BearerCredential`](/docs/reference/java/api/types.md#bearer-credential) to authenticate.

```java
import com.surrealdb.signin.BearerCredential;

db.signin(new BearerCredential("eyJhbGciOiJIUzI1NiIs..."));
```

## Invalidating authentication

The [`.invalidate()`](/docs/reference/java/api/core/surreal.md#invalidate) method clears the authentication state for the current connection. After invalidation, subsequent operations execute as an unauthenticated user.

```java
db.invalidate();
```

## Learn more

- [Surreal API reference](/docs/reference/java/api/core/surreal.md) for method signatures
- [Java Types reference](/docs/reference/java/api/types.md) for credential class details
- [Connecting to SurrealDB](/docs/reference/java/concepts/connecting-to-surrealdb.md) for connection setup
- [DEFINE USER](/docs/reference/query-language/statements/define/user.md) for configuring system users
- [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md) for configuring record access
- [Security best practices](/docs/learn/security/best-practices/security-best-practices.md) for token management
- [SurrealDB authentication overview](/docs/learn/security/authentication/users.md) for system users, record users, and token concepts

---

Source: https://surrealdb.com/docs/reference/java/concepts/class-converters

# Class converters

The Java SDK automatically converts between Java classes and SurrealDB values, letting you use POJOs for type-safe database operations.

The Java SDK includes a built-in class conversion system that automatically maps between Java classes and SurrealDB values. When you pass a `Class<T>` to SDK methods, the converter serializes Java objects into SurrealDB-compatible data on the way in and deserializes SurrealDB responses back into typed Java objects on the way out. This removes the need to manually extract fields from raw [`Value`](/docs/reference/java/api/values/value.md) objects and gives you compile-time type safety across your data layer.

## How conversion works

Class conversion happens in two directions:

- **Serialisation** - When you pass a Java object to a method like [`.create()`](/docs/reference/java/api/core/surreal.md#create), [`.insert()`](/docs/reference/java/api/core/surreal.md#insert), or [`.update()`](/docs/reference/java/api/core/surreal.md#update), the SDK reads the object's public fields and converts them into a SurrealDB object. Field names become object keys, and field values are converted to the corresponding SurrealDB types.

- **Deserialisation** - When you call a typed method like `db.select(Person.class, ...)` or use `Value.get(Person.class)`, the SDK creates a new instance of your class and populates its public fields from the SurrealDB object, matching by field name.

```java
public class Person {
    public RecordId id;
    public String name;
    public int age;

    public Person() {}
}

Person person = new Person();
person.name = "Tobie";
person.age = 33;

// Serialization: Person → SurrealDB object
db.create(new RecordId("person", "tobie"), person);

// Deserialization: SurrealDB object → Person
Optional<Person> result = db.select(Person.class,
    new RecordId("person", "tobie"));
```

## POJO requirements

For a Java class to work with the converter, it must satisfy two rules:

1. **Public no-argument constructor** - The SDK needs to instantiate the class during deserialisation.
2. **Public fields** - Fields are matched by name to SurrealDB object keys. Private fields, getters, and setters are not used by the converter.

```java
public class Product {
    public RecordId id;
    public String name;
    public double price;
    public boolean active;

    public Product() {}
}
```

Fields that exist in the Java class but not in the SurrealDB object are left at their Java default value (`null` for objects, `0` for numbers, `false` for booleans). Fields in the SurrealDB object that have no matching Java field are silently ignored.

## Field type mapping

POJO fields and bound values are mapped to SurrealDB types based on their declared Java type:

| Java Field/Value Type                                   | SurrealDB Type                                                                                                | Notes                                    |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| `String`                                                | `string`                                                                                                      |                                          |
| `long` / `Long`                                         | `int`                                                                                                         |                                          |
| `int` / `Integer`                                       | `int`                                                                                                         | Narrowed from SurrealDB's 64-bit integer |
| `double` / `Double`                                     | `float`                                                                                                       |                                          |
| `float` / `Float`                                       | `float`                                                                                                       | Narrowed from SurrealDB's 64-bit float   |
| `boolean` / `Boolean`                                   | `bool`                                                                                                        |                                          |
| `BigDecimal`                                            | `decimal`                                                                                                     | `java.math`                              |
| `UUID`                                                  | `uuid`                                                                                                        | `java.util`                              |
| `byte[]`                                                | `bytes`                                                                                                       |                                          |
| `Instant`                                               | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `ZonedDateTime`                                         | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `OffsetDateTime`                                        | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `LocalDateTime`                                         | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `java.util.Date`                                        | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `Duration`                                              | [`duration`](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) | `java.time`                              |
| [`RecordId`](/docs/reference/java/api/values/record-id.md) | [`record`](/docs/reference/query-language/language-primitives/data-types/record-ids.md)                          | SDK class                                |
| [`Geometry`](/docs/reference/java/api/values/geometry.md)  | [`geometry`](/docs/reference/query-language/language-primitives/data-types/geometries.md)                        | SDK class                                |
| [`FileRef`](/docs/reference/java/api/values/file-ref.md)   | [`file`](/docs/reference/query-language/language-primitives/data-types/files.md)                                 | SDK class                                |

See [Value types](/docs/reference/java/concepts/value-types.md) for the complete type mapping reference.

## Nested objects

When a POJO field is itself a class with public fields and a no-argument constructor, the converter recurses into it. This lets you model nested SurrealDB objects with nested Java classes.

```java
public class Address {
    public String street;
    public String city;
    public String country;

    public Address() {}
}

public class Person {
    public RecordId id;
    public String name;
    public Address address;

    public Person() {}
}

Person person = new Person();
person.name = "Tobie";
person.address = new Address();
person.address.street = "123 Main St";
person.address.city = "London";
person.address.country = "UK";

db.create(new RecordId("person", "tobie"), person);
```

The resulting SurrealDB record contains a nested object:

```surql
{
    id: person:tobie,
    name: "Tobie",
    address: {
        street: "123 Main St",
        city: "London",
        country: "UK"
    }
}
```

## Temporal types

SurrealDB [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md) values represent an absolute point in time. When serialising Java objects, the SDK accepts `Instant`, `ZonedDateTime`, `OffsetDateTime`, `LocalDateTime`, and `java.util.Date` fields. When deserialising SurrealDB `datetime` values back into POJOs, the same types are supported; `ZonedDateTime` is the usual choice when you need the stored instant with its offset.

> Note: `LocalDateTime` does not contain a time zone or offset. The SDK interprets `LocalDateTime` values as UTC during serialisation. If the value represents a user's local wall-clock time in a specific region, prefer `ZonedDateTime` or `OffsetDateTime` so the intended instant is explicit.

[`duration`](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) fields map to `java.time.Duration` in your POJOs.

```java
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.Duration;
import java.util.Date;

public class TaskWrite {
    public RecordId id;
    public String title;
    public Instant scheduledAt;
    public OffsetDateTime reviewedAt;
    public LocalDateTime localDeadline;
    public Date legacyCreatedAt;
    public Duration timeout;

    public TaskWrite() {}
}

TaskWrite task = new TaskWrite();
task.scheduledAt = Instant.parse("2026-06-06T08:00:00Z");
task.reviewedAt = OffsetDateTime.parse("2026-06-06T10:00:00+02:00");
task.localDeadline = LocalDateTime.parse("2026-06-06T10:00:00");
task.legacyCreatedAt = Date.from(task.scheduledAt);
task.timeout = Duration.ofMinutes(30);

db.create(new RecordId("task", "build"), task);
```

Use `ZonedDateTime` fields when you want the full stored instant (recommended for most read paths). `Instant`, `OffsetDateTime`, and `LocalDateTime` also work on deserialisation:

```java
import java.time.ZonedDateTime;
import java.time.Duration;

public class TaskRead {
    public RecordId id;
    public String title;
    public ZonedDateTime scheduledAt;
    public ZonedDateTime reviewedAt;
    public ZonedDateTime localDeadline;
    public ZonedDateTime legacyCreatedAt;
    public Duration timeout;

    public TaskRead() {}
}

Optional<TaskRead> task = db.select(TaskRead.class,
    new RecordId("task", "build"));
ZonedDateTime when = task.get().scheduledAt;
Duration howLong = task.get().timeout;
```

## Relation classes

Graph edges created with [`.relate()`](/docs/reference/java/api/core/surreal.md#relate) or [`.insertRelation()`](/docs/reference/java/api/core/surreal.md#insert-relation) use specialized base classes that include the standard relation fields (`id`, `in`, `out`).

### Using `Relation`

Extend [`Relation`](/docs/reference/java/api/types.md#relation) when reading or creating edges with `.relate()`. The base class provides `id`, `in`, and `out` as `RecordId` fields.

```java
public class Likes extends Relation {
    public String createdAt;
}

Likes like = db.relate(
    Likes.class,
    new RecordId("person", "alice"),
    "likes",
    new RecordId("post", "post1")
);
```

### Using `InsertRelation`

Extend [`InsertRelation`](/docs/reference/java/api/types.md#insert-relation) when inserting edges with `.insertRelation()`. The base class provides `id` as an [`Id`](/docs/reference/java/api/values/record-id.md#id) and `in` / `out` as `RecordId` fields.

```java
public class Follows extends InsertRelation {
    public ZonedDateTime since;

    public Follows() {}
}

Follows follow = new Follows();
follow.in = new RecordId("person", "alice");
follow.out = new RecordId("person", "bob");
follow.since = ZonedDateTime.now();

db.insertRelation(Follows.class, "follows", follow);
```

## Where conversion is available

Typed conversion is available across most SDK methods. Any method that accepts a `Class<T>` parameter uses the converter:

<table>
  <thead>
    <tr>
      <th scope="col">Method</th>
      <th scope="col">Typed variant</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#select">
          <code>db.select(target)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.select(Class, target)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#create">
          <code>db.create(target, content)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.create(Class, target, content)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#insert">
          <code>db.insert(target, content)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.insert(Class, target, content)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#update">
          <code>db.update(target, upType, content)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.update(Class, target, upType, content)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#upsert">
          <code>db.upsert(target, upType, content)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.upsert(Class, target, upType, content)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#relate">
          <code>db.relate(from, table, to)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.relate(Class, from, table, to)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/response.md#take">
          <code>response.take(index)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>response.take(Class, index)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/values/value.md#get">
          <code>value.get(Class)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        Direct POJO conversion from a <code>Value</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/values/value.md#array-typed-iterator">
          <code>array.iterator(Class)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        Typed iteration over array elements
      </td>
    </tr>
  </tbody>
</table>

## Handling conversion errors

When the SDK cannot convert a value to the target class - for example, because a field type is incompatible or the class is missing a no-argument constructor - it throws a [`SerializationException`](/docs/reference/java/api/errors.md#serialization-exception). You can catch this specifically or handle it as part of the general [`SurrealException`](/docs/reference/java/api/errors.md) hierarchy.

```java
try {
        Optional<Person> person = db.select(Person.class,
        new RecordId("person", "tobie"));
} catch (SerializationException e) {
    System.err.println("Conversion failed: " + e.getMessage());
}
```

See [Error handling](/docs/reference/java/concepts/error-handling.md) for more on the exception hierarchy.

## Learn more

- [Value types](/docs/reference/java/concepts/value-types.md) for the complete SurrealDB-to-Java type mapping
- [Data manipulation](/docs/reference/java/concepts/data-manipulation.md) for using converted types with CRUD operations
- [Value API reference](/docs/reference/java/api/values/value.md) for the `Value.get(Class)` method
- [Response API reference](/docs/reference/java/api/core/response.md) for typed response extraction
- [Java Types reference](/docs/reference/java/api/types.md) for `Relation`, `InsertRelation`, and other SDK types
- [Error handling](/docs/reference/java/concepts/error-handling.md) for serialisation error details

---

Source: https://surrealdb.com/docs/reference/java/concepts/connecting-to-surrealdb

# Connecting to SurrealDB

The Java SDK supports WebSocket, HTTP, and embedded connections to SurrealDB instances.

The first step towards interacting with [SurrealDB](/docs) is to create a new connection to a database instance. This involves initialising a new [`Surreal`](/docs/reference/java/api/core/surreal.md) instance, connecting it to an endpoint, and selecting a namespace and database. The SDK supports remote connections over WebSocket and HTTP, as well as embedded in-process databases.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#constructor"><code>new Surreal()</code></a></td>
			<td scope="row" data-label="Description">Creates a new Surreal instance</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#connect"><code>db.connect(url)</code></a></td>
			<td scope="row" data-label="Description">Connects to a SurrealDB instance</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#close"><code>db.close()</code></a></td>
			<td scope="row" data-label="Description">Closes the active connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#use-ns"><code>db.useNs(namespace)</code></a></td>
			<td scope="row" data-label="Description">Switches to a specific namespace</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#use-db"><code>db.useDb(database)</code></a></td>
			<td scope="row" data-label="Description">Switches to a specific database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#use-defaults"><code>db.useDefaults()</code></a></td>
			<td scope="row" data-label="Description">Uses the default namespace and database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#version"><code>db.version()</code></a></td>
			<td scope="row" data-label="Description">Returns the server version</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#health"><code>db.health()</code></a></td>
			<td scope="row" data-label="Description">Checks the server health</td>
		</tr>
	</tbody>
</table>

## Opening a connection

Create a new [`Surreal`](/docs/reference/java/api/core/surreal.md) instance and call [`.connect()`](/docs/reference/java/api/core/surreal.md#connect) with a connection string pointing to your SurrealDB instance. The `Surreal` class implements `AutoCloseable`, so you can use it in a try-with-resources block to ensure the connection is closed automatically.

```java
Surreal db = new Surreal();
db.connect("ws://localhost:8000");
```

## Connection string protocols

The connection string determines the protocol and transport used to communicate with SurrealDB. For more on server configuration, see the [start command](/docs/reference/cli/db/commands/start.md) documentation.

| Protocol | Description |
|---|---|
| `ws://` | Plain WebSocket connection |
| `wss://` | Secure WebSocket connection (TLS) |
| `http://` | Plain HTTP connection |
| `https://` | Secure HTTP connection (TLS) |
| `memory://` | In-memory embedded database |
| `surrealkv://` | Disk-based embedded database |

The `memory://` and `surrealkv://` protocols run SurrealDB in-process using JNI, which eliminates network overhead. See [Embedded databases](/docs/reference/java/concepts/embedded-databases.md) for details on configuring embedded connections.

## Feature support by protocol

Not all features are available on every protocol. The table below summarizes what is supported for each connection type.

| Feature | WebSocket | HTTP | Embedded |
|---|---|---|---|
| Authentication | Yes | Yes | Yes |
| Queries | Yes | Yes | Yes |
| CRUD operations | Yes | Yes | Yes |
| Live queries | Yes | No | No |
| Transactions | Yes | No | Yes |
| Multiple sessions | Yes | No | Yes |
| Export / Import | Yes | Yes | Yes |

## Selecting a namespace and database

After connecting, select a [namespace](/docs/reference/query-language/statements/define/namespace.md) and [database](/docs/reference/query-language/statements/define/database.md) using [`.useNs()`](/docs/reference/java/api/core/surreal.md#use-ns) and [`.useDb()`](/docs/reference/java/api/core/surreal.md#use-db). These methods return the `Surreal` instance, so they can be chained.

```java
db.useNs("surrealdb").useDb("docs");
```

To reset the namespace and database to the server defaults, call [`.useDefaults()`](/docs/reference/java/api/core/surreal.md#use-defaults).

```java
db.useDefaults();
```

## Using try-with-resources

Since [`Surreal`](/docs/reference/java/api/core/surreal.md) implements `AutoCloseable`, Java's try-with-resources statement ensures that [`.close()`](/docs/reference/java/api/core/surreal.md#close) is called when the block exits, even if an exception is thrown. This is the recommended pattern for managing connections.

```java
import com.surrealdb.Surreal;
import com.surrealdb.signin.RootCredential;

public class Example {

    public static void main(String[] args) {
        try (Surreal db = new Surreal()) {
            db.connect("ws://localhost:8000");
            db.useNs("surrealdb").useDb("docs");
            db.signin(new RootCredential("root", "root"));

            String version = db.version();
            System.out.println("Connected to SurrealDB " + version);
        }
    }

}
```

## Effect of connection protocol on token and session duration

The connection protocol affects how authentication tokens and sessions behave.

- **WebSocket** connections are stateful and long-lived. After the initial authentication, the session persists for the lifetime of the connection. The session duration defaults to `NONE`, meaning it never expires unless explicitly configured.
- **HTTP** connections are stateless. Each request must include a valid token, and the server creates a short-lived session for the duration of that request. The token duration defaults to 1 hour.

You can configure token and session durations using the `DURATION` clause on [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access.md) or [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md) statements. See the [security best practices](/docs/learn/security/best-practices/security-best-practices.md#expiration) documentation for guidance on choosing appropriate durations.

## Closing a connection

The [`.close()`](/docs/reference/java/api/core/surreal.md#close) method releases all resources associated with the connection. If you are not using try-with-resources, call `.close()` explicitly when you are done with the connection.

```java
db.close();
```

## Learn more

- [Surreal API reference](/docs/reference/java/api/core/surreal.md) for complete method signatures
- [Authentication](/docs/reference/java/concepts/authentication.md) for signing in and managing sessions
- [Embedded databases](/docs/reference/java/concepts/embedded-databases.md) for running SurrealDB in-process
- [Error handling](/docs/reference/java/concepts/error-handling.md) for handling connection errors
- [SurrealDB authentication](/docs/learn/security/authentication/users.md) for an overview of authentication concepts
- [DEFINE NAMESPACE](/docs/reference/query-language/statements/define/namespace.md) and [DEFINE DATABASE](/docs/reference/query-language/statements/define/database.md) for namespace and database configuration

---

Source: https://surrealdb.com/docs/reference/java/concepts/data-manipulation

# Data manipulation

The Java SDK provides type-safe methods for creating, selecting, updating, upserting, and deleting records.

The Java SDK provides dedicated methods for common CRUD operations on records and tables. These methods offer a structured alternative to writing raw [SurrealQL](/docs/reference/query-language.md), with built-in type safety through Java generics and POJO deserialisation.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#create"><code>db.create(target, content)</code></a></td>
			<td scope="row" data-label="Description">Creates one or more records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#select"><code>db.select(target)</code></a></td>
			<td scope="row" data-label="Description">Selects records from a table or by ID</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#insert"><code>db.insert(target, content)</code></a></td>
			<td scope="row" data-label="Description">Inserts one or more records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#update"><code>db.update(target, upType, content)</code></a></td>
			<td scope="row" data-label="Description">Updates records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#upsert"><code>db.upsert(target, upType, content)</code></a></td>
			<td scope="row" data-label="Description">Updates or creates records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#delete"><code>db.delete(target)</code></a></td>
			<td scope="row" data-label="Description">Deletes records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#relate"><code>db.relate(from, table, to)</code></a></td>
			<td scope="row" data-label="Description">Creates a graph relation</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#insert-relation"><code>db.insertRelation(target, content)</code></a></td>
			<td scope="row" data-label="Description">Inserts a relation record</td>
		</tr>
	</tbody>
</table>

## Defining model classes

Data manipulation methods can work with POJOs (Plain Old Java Objects) for type-safe access. Model classes need a public no-argument constructor and fields that map to SurrealDB object keys. Use a [`RecordId`](/docs/reference/java/api/values/record-id.md) field named `id` to hold the record identifier.

```java
public class Person {
    public RecordId id;
    public String name;
    public int age;

    public Person() {}
}
```

## Creating records

The [`.create()`](/docs/reference/java/api/core/surreal.md#create) method creates new records. When called with a table name, SurrealDB generates a random ID and returns a list. When called with a [`RecordId`](/docs/reference/java/api/values/record-id.md), the record is created with that specific ID and a single result is returned.

```java
Person alice = new Person();
alice.name = "Alice";
alice.age = 30;

// Create with a generated ID - returns List<Value>
List<Value> created = db.create("person", alice);

// Create with a specific ID - returns Value
Value tobie = db.create(new RecordId("person", "tobie"), alice);

// Typed variant - returns List<Person>
List<Person> typed = db.create(Person.class, "person", alice);
```

## Selecting records

The [`.select()`](/docs/reference/java/api/core/surreal.md#select) method reads records from the database. When called with a table name, it returns an `Iterator`. When called with a `RecordId`, it returns an `Optional`.

```java
// Select all records from a table
Iterator<Value> all = db.select("person");

// Select a specific record
Optional<Value> one = db.select(new RecordId("person", "tobie"));

// Typed variants
Iterator<Person> allTyped = db.select(Person.class, "person");
Optional<Person> oneTyped = db.select(Person.class, new RecordId("person", "tobie"));
```

Use `selectSync()` instead of `select()` when you need thread-safe iteration over the results.

## Inserting records

The [`.insert()`](/docs/reference/java/api/core/surreal.md#insert) method inserts one or more records into a table using varargs. This is more efficient than calling `.create()` in a loop for bulk operations.

```java
Person alice = new Person();
alice.name = "Alice";
alice.age = 30;

Person bob = new Person();
bob.name = "Bob";
bob.age = 25;

List<Value> inserted = db.insert("person", alice, bob);
```

Use [`.insertRelation()`](/docs/reference/java/api/core/surreal.md#insert-relation) to insert graph edge records. See [Creating graph relations](#creating-graph-relations) for details.

## Updating records

The [`.update()`](/docs/reference/java/api/core/surreal.md#update) method modifies existing records. You specify the update strategy using the [`UpType`](/docs/reference/java/api/types.md#up-type) enum:

| UpType | Behaviour |
|---|---|
| `UpType.CONTENT` | Replaces the entire record with the new content |
| `UpType.MERGE` | Merges new fields into the existing record |
| `UpType.PATCH` | Applies a JSON Patch to the record |

```java
Person updated = new Person();
updated.name = "Alice Smith";
updated.age = 31;

// Replace the entire record
db.update(new RecordId("person", "alice"), UpType.CONTENT, updated);

// Merge fields into the existing record
db.update(new RecordId("person", "alice"), UpType.MERGE, Map.of("age", 31));

// Update all records in a table
db.update("person", UpType.MERGE, Map.of("active", true));

// Typed variant
Person result = db.update(
    Person.class, new RecordId("person", "alice"), UpType.CONTENT, updated
);
```

## Upserting records

The [`.upsert()`](/docs/reference/java/api/core/surreal.md#upsert) method works like `.update()` but creates the record if it does not already exist. It accepts the same [`UpType`](/docs/reference/java/api/types.md#up-type) strategies.

```java
Person person = new Person();
person.name = "Charlie";
person.age = 28;

db.upsert(new RecordId("person", "charlie"), UpType.CONTENT, person);
```

## Deleting records

The [`.delete()`](/docs/reference/java/api/core/surreal.md#delete) method removes records from the database. You can delete a single record by [`RecordId`](/docs/reference/java/api/values/record-id.md), multiple records by passing several `RecordId` values, a range of records with [`RecordIdRange`](/docs/reference/java/api/values/record-id.md#record-id-range), or all records in a table by passing the table name.

```java
// Delete a single record
db.delete(new RecordId("person", "tobie"));

// Delete multiple specific records
db.delete(new RecordId("person", "alice"), new RecordId("person", "bob"));

// Delete a range of records
db.delete(new RecordIdRange("person", Id.from("a"), Id.from("f")));

// Delete all records in a table
db.delete("person");
```

## Creating graph relations

The [`.relate()`](/docs/reference/java/api/core/surreal.md#relate) method creates edges between records in SurrealDB's [graph model](/docs/reference/query-language/statements/relate.md). You specify the source record, the edge table, and the target record. Optionally, you can attach content to the edge.

```java
db.relate(
    new RecordId("person", "tobie"),
    "likes",
    new RecordId("post", 1)
);

// With content on the edge
db.relate(
    new RecordId("person", "tobie"),
    "likes",
    new RecordId("post", 1),
    Map.of("timestamp", "2026-02-27T12:00:00Z")
);
```

You can also use [`.insertRelation()`](/docs/reference/java/api/core/surreal.md#insert-relation) to insert relation records with `in` and `out` fields, similar to how `.insert()` works for regular records.

```java
public class Likes extends InsertRelation {
    public String timestamp;

    public Likes() {}
}

Likes like = new Likes();
like.in = new RecordId("person", "tobie");
like.out = new RecordId("post", 1);
like.timestamp = "2026-02-27T12:00:00Z";

db.insertRelation(Likes.class, "likes", like);
```

## Learn more

- [Surreal API reference](/docs/reference/java/api/core/surreal.md) for complete method signatures
- [Value types](/docs/reference/java/concepts/value-types.md) for type mappings and the Value class
- [Executing queries](/docs/reference/java/concepts/executing-queries.md) for custom SurrealQL queries
- [RecordId reference](/docs/reference/java/api/values/record-id.md) for record identifier details
- [SurrealQL SELECT](/docs/reference/query-language/statements/select.md), [CREATE](/docs/reference/query-language/statements/create.md), [UPDATE](/docs/reference/query-language/statements/update.md), [DELETE](/docs/reference/query-language/statements/delete.md) for the underlying query statements
- [SurrealQL RELATE](/docs/reference/query-language/statements/relate.md) for graph relation syntax
- [Record IDs](/docs/reference/query-language/language-primitives/data-types/record-ids.md) for SurrealDB record identifier formats

---

Source: https://surrealdb.com/docs/reference/java/concepts/embedded-databases

# Embedded databases

The Java SDK can run SurrealDB as an embedded in-process database for testing and standalone applications.

The Java SDK can run SurrealDB as an embedded in-process database, eliminating the need for a separate server. Embedded databases use JNI to run the SurrealDB engine directly within your application, which removes network overhead and simplifies deployment for testing, prototyping, and standalone applications.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#connect"><code>db.connect(url)</code></a></td>
			<td scope="row" data-label="Description">Connects using an embedded protocol</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#export-sql"><code>db.exportSql(path)</code></a></td>
			<td scope="row" data-label="Description">Exports the database to a file</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#import-sql"><code>db.importSql(path)</code></a></td>
			<td scope="row" data-label="Description">Imports data from a file</td>
		</tr>
	</tbody>
</table>

## Running an in-memory database

Use the `memory://` scheme to start an in-memory embedded database. All data is stored in memory and is lost when the connection closes. This is ideal for unit tests and rapid prototyping where persistence is not required.

```java
try (Surreal db = new Surreal()) {
    db.connect("memory://");
    db.useNs("main").useDb("main");
}
```

## Running a disk-based database

Use the `surrealkv://` scheme with a file path to start a disk-based embedded database. Data is persisted to the specified directory and survives application restarts.

```java
try (Surreal db = new Surreal()) {
    db.connect("surrealkv://path/to/database");
    db.useNs("app").useDb("main");
}
```

## Exporting and importing data

The [`.exportSql()`](/docs/reference/java/api/core/surreal.md#export-sql) method writes the current database contents to a SurrealQL file. The [`.importSql()`](/docs/reference/java/api/core/surreal.md#import-sql) method reads a SurrealQL file and applies it to the database.

```java
try (Surreal db = new Surreal()) {
    db.connect("surrealkv://path/to/database");
    db.useNs("app").useDb("main");

    db.exportSql("backup.surql");
    db.importSql("backup.surql");
}
```

## When to use embedded databases

Embedded databases are well suited for scenarios where running a separate SurrealDB server is unnecessary or impractical:

- **Testing** - use `memory://` for fast, isolated tests that start with a clean database on every run.
- **Desktop and mobile applications** - use `surrealkv://` to bundle a persistent database directly within the application.
- **CLI tools** - embed a database to store local state or configuration without requiring users to install SurrealDB.
- **Prototyping** - iterate quickly without managing a server process.

## Learn more

- [Surreal API reference](/docs/reference/java/api/core/surreal.md) for complete method signatures
- [Connecting to SurrealDB](/docs/reference/java/concepts/connecting-to-surrealdb.md) for all connection protocols
- [Deployment models](/docs/manage/self-hosted/deployment-models.md) for production server deployment
- [SurrealDB CLI start](/docs/reference/cli/db/commands/start.md) for server configuration and storage backends
- [SurrealDB CLI import](/docs/reference/cli/db/commands/import.md) and [export](/docs/reference/cli/db/commands/export.md) for command-line data management

---

Source: https://surrealdb.com/docs/reference/java/concepts/error-handling

# Error handling

The Java SDK provides a structured exception hierarchy for handling errors from the database and SDK.

The Java SDK provides a structured exception hierarchy for handling errors from the database and SDK. All exceptions extend [`SurrealException`](/docs/reference/java/api/errors.md#surreal-exception), which is an unchecked exception. Server-returned errors are represented as [`ServerException`](/docs/reference/java/api/errors.md#server-exception) subclasses with typed error details.

The error kinds, wire codes and structured shape behind these are documented once in [Errors](/docs/reference/rest-api/errors.md), which applies to every SDK and protocol.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Error class</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/java/api/errors.md#surreal-exception"><code>SurrealException</code></a></td>
			<td scope="row" data-label="Description">Base exception for all SDK errors</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/java/api/errors.md#server-exception"><code>ServerException</code></a></td>
			<td scope="row" data-label="Description">Base for server-returned errors</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/java/api/errors.md#not-found-exception"><code>NotFoundException</code></a></td>
			<td scope="row" data-label="Description">Thrown when a resource is not found</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/java/api/errors.md#not-allowed-exception"><code>NotAllowedException</code></a></td>
			<td scope="row" data-label="Description">Thrown when an operation is not permitted</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/java/api/errors.md#query-exception"><code>QueryException</code></a></td>
			<td scope="row" data-label="Description">Thrown when a query fails</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/java/api/errors.md#already-exists-exception"><code>AlreadyExistsException</code></a></td>
			<td scope="row" data-label="Description">Thrown when a resource already exists</td>
		</tr>
	</tbody>
</table>

## Exception hierarchy

[`SurrealException`](/docs/reference/java/api/errors.md#surreal-exception) is the base class for all exceptions thrown by the SDK. It extends `RuntimeException`, so exceptions are unchecked. [`ServerException`](/docs/reference/java/api/errors.md#server-exception) extends `SurrealException` and represents errors returned by the SurrealDB server. Specific exception types extend `ServerException` for common error categories.

- [`SurrealException`](/docs/reference/java/api/errors.md#surreal-exception) - base for all SDK errors
  - [`ServerException`](/docs/reference/java/api/errors.md#server-exception) - base for server-returned errors
    - [`NotFoundException`](/docs/reference/java/api/errors.md#not-found-exception) - resource not found
    - [`NotAllowedException`](/docs/reference/java/api/errors.md#not-allowed-exception) - operation not permitted
    - [`QueryException`](/docs/reference/java/api/errors.md#query-exception) - query execution failed
    - [`AlreadyExistsException`](/docs/reference/java/api/errors.md#already-exists-exception) - resource already exists

## Catching server errors

All server errors are [`ServerException`](/docs/reference/java/api/errors.md#server-exception) subclasses. Catch specific exceptions first for targeted handling, then fall back to `ServerException` for unexpected server errors.

```java
try {
    db.query("SELECT * FROM protected_table");
} catch (NotAllowedException e) {
    System.err.println("Permission denied: " + e.getMessage());
} catch (QueryException e) {
    System.err.println("Query failed: " + e.getMessage());
} catch (ServerException e) {
    System.err.println("Server error: " + e.getMessage());
}
```

## Inspecting error details

[`ServerException`](/docs/reference/java/api/errors.md#server-exception) provides methods for inspecting the error returned by the server.

- `.getKind()` - returns the error kind as a `String`
- `.getKindEnum()` - returns the error kind as an [`ErrorKind`](/docs/reference/java/api/errors.md#error-kind) enum value
- `.getDetails()` - returns additional error details

The [`ErrorKind`](/docs/reference/java/api/errors.md#error-kind) enum includes: `VALIDATION`, `CONFIGURATION`, `THROWN`, `QUERY`, `SERIALIZATION`, `NOT_ALLOWED`, `NOT_FOUND`, `ALREADY_EXISTS`, `CONNECTION`, `INTERNAL`, and `UNKNOWN`.

```java
try {
    db.query("INVALID QUERY");
} catch (ServerException e) {
    ErrorKind kind = e.getKindEnum();
    String details = e.getDetails();
    System.err.println(kind + ": " + details);
}
```

## Traversing error chains

Server errors can have nested causes. [`ServerException`](/docs/reference/java/api/errors.md#server-exception) provides methods for walking the cause chain to find a specific error type.

- `.getServerCause()` - returns the underlying `ServerException` cause, if any
- `.hasKind(kind)` - checks whether this error or any cause matches the given `ErrorKind`
- `.findCause(kind)` - searches the cause chain and returns the first `ServerException` matching the given `ErrorKind`

```java
try {
    db.query("SELECT * FROM users");
} catch (ServerException e) {
    if (e.hasKind(ErrorKind.NOT_ALLOWED)) {
        ServerException cause = e.findCause(ErrorKind.NOT_ALLOWED);
        System.err.println("Permission error: " + cause.getDetails());
    }
}
```

## Handling specific error types

Specific exception subclasses expose additional context about the error.

[`NotFoundException`](/docs/reference/java/api/errors.md#not-found-exception) provides `.getTableName()` and `.getRecordId()` to identify the missing resource.

```java
try {
    Optional<Value> user = db.select(new RecordId("users", "nonexistent"));
} catch (NotFoundException e) {
    System.err.println("Table: " + e.getTableName());
    System.err.println("Record: " + e.getRecordId());
}
```

[`NotAllowedException`](/docs/reference/java/api/errors.md#not-allowed-exception) provides `.isTokenExpired()` and `.isInvalidAuth()` to distinguish authentication failures.

```java
try {
    db.query("SELECT * FROM protected");
} catch (NotAllowedException e) {
    if (e.isTokenExpired()) {
        System.err.println("Token expired, re-authenticate");
    } else if (e.isInvalidAuth()) {
        System.err.println("Invalid credentials");
    }
}
```

[`QueryException`](/docs/reference/java/api/errors.md#query-exception) provides `.isTimedOut()` and `.isCancelled()` to identify query lifecycle issues.

```java
try {
    db.query("SELECT * FROM large_table");
} catch (QueryException e) {
    if (e.isTimedOut()) {
        System.err.println("Query timed out");
    } else if (e.isCancelled()) {
        System.err.println("Query was cancelled");
    }
}
```

## Learn more

- [Errors API reference](/docs/reference/java/api/errors.md) for complete exception class documentation
- [Connecting to SurrealDB](/docs/reference/java/concepts/connecting-to-surrealdb.md) for connection error scenarios
- [Authentication](/docs/reference/java/concepts/authentication.md) for authentication error scenarios
- [SurrealQL THROW](/docs/reference/query-language/statements/throw.md) for throwing custom errors from queries

---

Source: https://surrealdb.com/docs/reference/java/concepts/executing-queries

# Executing queries

The Java SDK provides methods for executing SurrealQL queries with optional parameter binding.

The Java SDK provides methods for executing raw [SurrealQL](/docs/reference/query-language.md) queries against SurrealDB. You can run single or multi-statement queries, bind [parameters](/docs/reference/query-language/language-primitives/parameters.md) for safe variable injection, and call server-side [functions](/docs/reference/query-language/statements/define/function.md).

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#query"><code>db.query(sql)</code></a></td>
			<td scope="row" data-label="Description">Executes a SurrealQL query</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#query-bind"><code>db.queryBind(sql, params)</code></a></td>
			<td scope="row" data-label="Description">Executes a parameterised query</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#run"><code>db.run(name, args)</code></a></td>
			<td scope="row" data-label="Description">Runs a SurrealDB function</td>
		</tr>
	</tbody>
</table>

## Running a query

The [`.query()`](/docs/reference/java/api/core/surreal.md#query) method executes one or more SurrealQL statements and returns a [`Response`](/docs/reference/java/api/core/response.md). Use [`.take(int)`](/docs/reference/java/api/core/response.md#take) to extract the result of a specific statement by its zero-based index, or `.take(Class, int)` to deserialise the result into a typed Java object.

```java
Response response = db.query("SELECT * FROM users; SELECT * FROM posts;");

Value users = response.take(0);
List<Post> posts = response.take(Post.class, 1);
```

## Using query parameters

The [`.queryBind()`](/docs/reference/java/api/core/surreal.md#query-bind) method accepts a `Map<String, ?>` of parameters that are safely injected into the query. Parameterised queries prevent SurrealQL injection and ensure values are properly escaped.

```java
Map<String, Object> params = Map.of("min_age", 18);

Response response = db.queryBind(
    "SELECT * FROM users WHERE age > $min_age",
    params
);

List<User> users = response.take(User.class, 0);
```

Always prefer [`.queryBind()`](/docs/reference/java/api/core/surreal.md#query-bind) over string concatenation when incorporating user-provided values into queries.

## Working with query responses

The [`Response`](/docs/reference/java/api/core/response.md) object contains the results of all statements in the query. Use [`.take(int)`](/docs/reference/java/api/core/response.md#take) to get a raw [`Value`](/docs/reference/java/api/values/value.md), or `.take(Class, int)` to deserialise into a POJO. The `.size()` method returns the number of statement results.

```java
Response response = db.query(
    "CREATE users SET name = 'Alice'; SELECT * FROM users;"
);

int statementCount = response.size();

Value created = response.take(0);
List<User> users = response.take(User.class, 1);
```

See [Value types](/docs/reference/java/concepts/value-types.md) for details on working with the [`Value`](/docs/reference/java/api/values/value.md) class and type conversions.

## Running SurrealDB functions

The [`.run()`](/docs/reference/java/api/core/surreal.md#run) method calls a server-side function defined with [`DEFINE FUNCTION`](/docs/reference/query-language/statements/define/function.md). Pass the function name and any arguments.

```java
Value result = db.run("fn::calculate_total", 100, 0.2);
```

## Learn more

- [Surreal API reference](/docs/reference/java/api/core/surreal.md) for method signatures
- [Response API reference](/docs/reference/java/api/core/response.md) for response handling
- [Value types](/docs/reference/java/concepts/value-types.md) for working with query results
- [SurrealQL](/docs/reference/query-language.md) for query language reference
- [SurrealQL parameters](/docs/reference/query-language/language-primitives/parameters.md) for parameter syntax in queries
- [DEFINE FUNCTION](/docs/reference/query-language/statements/define/function.md) for defining server-side functions

---

Source: https://surrealdb.com/docs/reference/java/concepts/live-queries

# Live queries

The Java SDK supports real-time live queries that stream changes from the database to your application.

The Java SDK supports real-time [live queries](/docs/reference/query-language/statements/live-select.md) that stream changes from the database to your application. When records in a table are created, updated, or deleted, the SDK delivers notifications through a [`LiveStream`](/docs/reference/java/api/core/live-stream.md) that you can consume in a loop or process asynchronously.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#select-live"><code>db.selectLive(table)</code></a></td>
			<td scope="row" data-label="Description">Starts a live query on a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/live-stream.md#next"><code>stream.next()</code></a></td>
			<td scope="row" data-label="Description">Returns the next notification</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/live-stream.md#close"><code>stream.close()</code></a></td>
			<td scope="row" data-label="Description">Closes the live query</td>
		</tr>
	</tbody>
</table>

## Starting a live query

The [`.selectLive()`](/docs/reference/java/api/core/surreal.md#select-live) method subscribes to changes on a table and returns a [`LiveStream`](/docs/reference/java/api/core/live-stream.md). Live queries require a WebSocket connection (`ws://` or `wss://`) because the server pushes notifications over the persistent connection.

```java
try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("surrealdb").useDb("docs");
    db.signin(new RootCredential("root", "root"));

    LiveStream stream = db.selectLive("users");
}
```

## Receiving notifications

Call [`.next()`](/docs/reference/java/api/core/live-stream.md#next) on the `LiveStream` to block until the next notification arrives. It returns an `Optional<LiveNotification>` - the optional is empty if the stream has been closed.

Each [`LiveNotification`](/docs/reference/java/api/core/live-stream.md#live-notification) provides:
- `.getAction()` - the type of change: `CREATE`, `UPDATE`, or `DELETE`
- `.getValue()` - the record data as a [`Value`](/docs/reference/java/api/values/value.md)
- `.getQueryId()` - the unique identifier of the live query

```java
LiveStream stream = db.selectLive("users");

while (true) {
    Optional<LiveNotification> notification = stream.next();
    if (notification.isEmpty()) break;

    LiveNotification n = notification.get();
    System.out.println(n.getAction() + ": " + n.getValue());
}
```

## Closing a live query

Call [`.close()`](/docs/reference/java/api/core/live-stream.md#close) to unsubscribe from the live query and release the server-side subscription. `LiveStream` implements `AutoCloseable`, so you can use try-with-resources to ensure the stream is closed automatically.

```java
try (LiveStream stream = db.selectLive("users")) {
    Optional<LiveNotification> notification = stream.next();
    notification.ifPresent(n ->
        System.out.println(n.getAction() + ": " + n.getValue())
    );
}
```

## Learn more

- [Surreal API reference](/docs/reference/java/api/core/surreal.md#select-live) for the selectLive method
- [LiveStream API reference](/docs/reference/java/api/core/live-stream.md) for stream and notification details
- [Connecting to SurrealDB](/docs/reference/java/concepts/connecting-to-surrealdb.md) for WebSocket connection setup
- [SurrealQL LIVE SELECT](/docs/reference/query-language/statements/live-select.md) for the underlying query syntax
- [DEFINE TABLE ... CHANGEFEED](/docs/reference/query-language/statements/define/table.md) for configuring changefeeds on tables

---

Source: https://surrealdb.com/docs/reference/java/concepts/multiple-sessions

# Multiple sessions

The Java SDK supports creating multiple isolated sessions over a single connection.

The Java SDK supports creating multiple isolated sessions over a single connection. Each session maintains its own namespace, database, and authentication state while sharing the underlying transport, which avoids the overhead of establishing multiple connections.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#new-session"><code>db.newSession()</code></a></td>
			<td scope="row" data-label="Description">Creates a new isolated session</td>
		</tr>
	</tbody>
</table>

## Creating a new session

The [`.newSession()`](/docs/reference/java/api/core/surreal.md#new-session) method returns a new [`Surreal`](/docs/reference/java/api/core/surreal.md) instance that shares the underlying connection but has its own independent state. The new session starts without a selected namespace, database, or authentication - you must configure these separately.

```java
Surreal session = db.newSession();
session.useNs("surrealdb").useDb("docs");
session.signin(new RootCredential("root", "root"));
```

## Session isolation

Each session independently manages its own namespace, database selection, and authentication. Changes to one session do not affect others. This means you can sign in with different credentials, select different databases, or invalidate authentication on one session without impacting the rest.

```java
try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");

    Surreal session1 = db.newSession();
    session1.useNs("tenant_a").useDb("main");
    session1.signin(new RootCredential("root", "root"));

    Surreal session2 = db.newSession();
    session2.useNs("tenant_b").useDb("main");
    session2.signin(new RootCredential("root", "root"));
}
```

In this example, `session1` and `session2` operate on different namespaces over the same WebSocket connection. Queries on `session1` only see data in `tenant_a`, while queries on `session2` only see data in `tenant_b`.

## When to use multiple sessions

Multiple sessions are useful when your application needs to interact with SurrealDB using different contexts over a single connection:

- **Multi-tenant applications** - isolate each tenant's data by using separate sessions with different namespaces or databases.
- **Background tasks** - run background operations with elevated or restricted credentials without affecting the main application session.
- **Testing** - create isolated sessions to test different permission levels or database states without opening additional connections.

## Learn more

- [Surreal API reference](/docs/reference/java/api/core/surreal.md#new-session) for the newSession method
- [Connecting to SurrealDB](/docs/reference/java/concepts/connecting-to-surrealdb.md) for connection setup
- [Authentication](/docs/reference/java/concepts/authentication.md) for per-session authentication
- [DEFINE NAMESPACE](/docs/reference/query-language/statements/define/namespace.md) for namespace isolation in multi-tenant setups

---

Source: https://surrealdb.com/docs/reference/java/concepts/transactions

# Transactions

The Java SDK supports client-side transactions for grouping multiple queries into an atomic unit.

The Java SDK supports client-side [transactions](/docs/reference/query-language/statements/begin.md) for grouping multiple queries into an atomic unit. All queries within a [`Transaction`](/docs/reference/java/api/core/transaction.md) are isolated from other operations and are either applied together on commit or discarded entirely on cancel.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/surreal.md#begin-transaction"><code>db.beginTransaction()</code></a></td>
			<td scope="row" data-label="Description">Starts a new transaction</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/transaction.md#query"><code>tx.query(sql)</code></a></td>
			<td scope="row" data-label="Description">Executes a query within the transaction</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/transaction.md#commit"><code>tx.commit()</code></a></td>
			<td scope="row" data-label="Description">Commits the transaction</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/java/api/core/transaction.md#cancel"><code>tx.cancel()</code></a></td>
			<td scope="row" data-label="Description">Cancels (rolls back) the transaction</td>
		</tr>
	</tbody>
</table>

## Starting a transaction

The [`.beginTransaction()`](/docs/reference/java/api/core/surreal.md#begin-transaction) method returns a [`Transaction`](/docs/reference/java/api/core/transaction.md) object. All queries executed through this object are isolated until the transaction is committed or cancelled.

```java
Transaction tx = db.beginTransaction();
```

## Executing queries in a transaction

The [`tx.query()`](/docs/reference/java/api/core/transaction.md#query) method works like `db.query()` but executes within the transaction scope. Each call returns a [`Response`](/docs/reference/java/api/core/response.md) that you can inspect immediately, but the underlying changes are not visible outside the transaction until it is committed.

> [!NOTE]
> Parameterised queries via `.queryBind()` are not available inside transactions. To pass dynamic values, use SurrealQL `LET` statements or inline the values in the query string.

```java
Transaction tx = db.beginTransaction();
tx.query("CREATE account:one SET balance = 100");
tx.query("CREATE account:two SET balance = 0");
```

## Committing a transaction

Call [`.commit()`](/docs/reference/java/api/core/transaction.md#commit) to apply all changes atomically. Once committed, the changes become visible to other connections and queries.

```java
tx.commit();
```

## Cancelling a transaction

Call [`.cancel()`](/docs/reference/java/api/core/transaction.md#cancel) to discard all changes made within the transaction. Use a try-catch pattern to ensure the transaction is rolled back if any query fails.

```java
Transaction tx = db.beginTransaction();
try {
    tx.query("CREATE account:one SET balance = 100");
    tx.query("CREATE account:two SET balance = 0");
    tx.query("UPDATE account:one SET balance -= 50");
    tx.query("UPDATE account:two SET balance += 50");
    tx.commit();
} catch (SurrealException e) {
    tx.cancel();
    throw e;
}
```

## Learn more

- [Transaction API reference](/docs/reference/java/api/core/transaction.md) for complete method signatures
- [Executing queries](/docs/reference/java/concepts/executing-queries.md) for query execution outside transactions
- [Error handling](/docs/reference/java/concepts/error-handling.md) for handling transaction errors
- [SurrealQL BEGIN](/docs/reference/query-language/statements/begin.md) for server-side transaction syntax
- [SurrealQL COMMIT](/docs/reference/query-language/statements/commit.md) and [CANCEL](/docs/reference/query-language/statements/cancel.md) for transaction control

---

Source: https://surrealdb.com/docs/reference/java/concepts/value-types

# Value types

The Java SDK maps SurrealDB data types to native Java types and provides custom classes for complex values.

The Java SDK maps SurrealDB data types to native Java types where possible and provides custom classes for types that have no direct Java equivalent. You can work with results as untyped [`Value`](/docs/reference/java/api/values/value.md) objects or pass a `Class<T>` to SDK methods for automatic deserialisation into POJOs.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Class</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/java/api/values/value.md"><code>Value</code></a></td>
			<td scope="row" data-label="Description">Represents any SurrealDB value</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/java/api/values/record-id.md"><code>RecordId</code></a></td>
			<td scope="row" data-label="Description">Represents a record identifier</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/java/api/values/geometry.md"><code>Geometry</code></a></td>
			<td scope="row" data-label="Description">Represents geometric data</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/java/api/values/file-ref.md"><code>FileRef</code></a></td>
			<td scope="row" data-label="Description">Represents a file reference</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/java/api/values/datetime.md"><code>Datetime</code></a></td>
			<td scope="row" data-label="Description">Maps to <code>java.time.ZonedDateTime</code></td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/java/api/values/duration.md"><code>Duration</code></a></td>
			<td scope="row" data-label="Description">Maps to <code>java.time.Duration</code></td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/java/api/values/table.md"><code>Table</code></a></td>
			<td scope="row" data-label="Description">Table name value, maps to <code>String</code></td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/java/api/values/range.md"><code>Range</code></a></td>
			<td scope="row" data-label="Description">Range value with start and end bounds</td>
		</tr>
	</tbody>
</table>

## Type mapping

SurrealDB types map to Java types as follows:

| SurrealDB Type | Java Type | Notes |
|---|---|---|
| `string` | `String` | |
| `int` | `long` | |
| `float` | `double` | |
| `bool` | `boolean` | |
| `null` | `null` | |
| `none` | `Value.isNone()` | Check via [`Value`](/docs/reference/java/api/values/value.md) |
| [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md) | `ZonedDateTime` | `java.time` |
| [`duration`](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) | `Duration` | `java.time` |
| `decimal` | `BigDecimal` | `java.math` |
| `uuid` | `UUID` | `java.util` |
| `bytes` | `byte[]` | |
| `array` | [`Array`](/docs/reference/java/api/values/value.md#array) | SDK custom class |
| `object` | [`Object`](/docs/reference/java/api/values/value.md#object) | SDK custom class |
| [`record`](/docs/reference/query-language/language-primitives/data-types/record-ids.md) | [`RecordId`](/docs/reference/java/api/values/record-id.md) | SDK custom class |
| [`geometry`](/docs/reference/query-language/language-primitives/data-types/geometries.md) | [`Geometry`](/docs/reference/java/api/values/geometry.md) | SDK custom class |
| [`file`](/docs/reference/query-language/language-primitives/data-types/files.md) | [`FileRef`](/docs/reference/java/api/values/file-ref.md) | SDK custom class |
| `table` | `String` | Via `Value.getTable()` |
| `range` | [`Value`](/docs/reference/java/api/values/value.md) | Via `Value.getRangeStart()` / `Value.getRangeEnd()` |

## Working with the Value class

[`Value`](/docs/reference/java/api/values/value.md) is the untyped representation of any SurrealDB value. It provides type-checking methods and getters for extracting the underlying Java value.

```java
Value value = response.take(0);

if (value.isLong()) {
    long count = value.getLong();
}

if (value.isString()) {
    String name = value.getString();
}

if (value.isArray()) {
    Array items = value.getArray();
}
```

To convert a `Value` directly into a POJO, use `.get(Class)`.

```java
Person person = value.get(Person.class);
```

## Using POJOs for type-safe access

Instead of working with raw [`Value`](/docs/reference/java/api/values/value.md) objects, you can pass a `Class<T>` to most SDK methods to get automatic deserialisation. POJOs need a public no-argument constructor, and their fields map directly to SurrealDB object keys.

```java
public class Person {
    public RecordId id;
    public String name;
    public int age;

    public Person() {}
}

List<Person> people = db.create(Person.class, "person", newPerson);
Optional<Person> tobie = db.select(Person.class, new RecordId("person", "tobie"));
```

## Constructing record identifiers

[`RecordId`](/docs/reference/java/api/values/record-id.md) represents a SurrealDB record identifier consisting of a table name and an ID value. The ID can be a `long`, `String`, `UUID`, `Array`, or `Object`.

```java
RecordId numericId = new RecordId("person", 1L);
RecordId stringId = new RecordId("person", "tobie");
RecordId uuidId = new RecordId("person", UUID.randomUUID());

Array compositeKey = new Array(2026, "Q1");
RecordId arrayId = new RecordId("report", compositeKey);

Object objectKey = new Object();
objectKey.put("region", "eu");
objectKey.put("year", 2026);
RecordId objectId = new RecordId("report", objectKey);
```

## Iterating arrays and objects

The SDK's `Array` class implements `Iterable<Value>`, and the `Object` class implements `Iterable<Entry>`. You can iterate over them using standard Java for-each loops.

```java
Array items = value.getArray();
for (Value item : items) {
    System.out.println(item.getString());
}

Object obj = value.getObject();
for (Entry entry : obj) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}
```

When iterating from multiple threads, use `synchronizedIterator()` to get a thread-safe iterator.

```java
Iterator<Value> safeIterator = items.synchronizedIterator();
```

## Learn more

- [Value API reference](/docs/reference/java/api/values/value.md) for complete Value class documentation
- [RecordId API reference](/docs/reference/java/api/values/record-id.md) for record identifier details
- [Geometry API reference](/docs/reference/java/api/values/geometry.md) for geometric data types
- [FileRef API reference](/docs/reference/java/api/values/file-ref.md) for file references
- [Datetime API reference](/docs/reference/java/api/values/datetime.md) for datetime handling
- [Duration API reference](/docs/reference/java/api/values/duration.md) for duration handling
- [Table API reference](/docs/reference/java/api/values/table.md) for table name values
- [Range API reference](/docs/reference/java/api/values/range.md) for range values
- [Data manipulation](/docs/reference/java/concepts/data-manipulation.md) for using types with CRUD operations
- [SurrealQL data model](/docs/reference/query-language/language-primitives/data-types.md) for the full list of SurrealDB data types

---

Source: https://surrealdb.com/docs/reference/java/installation

# Installation

The SurrealDB SDK for Java is available on Maven Central and can be installed using Gradle or Maven.

The SurrealDB SDK for Java is available on the [Maven Central](https://mvnrepository.com/artifact/com.surrealdb/surrealdb) repository. You can add it to your project using Gradle or Maven.

> [!NOTE]
> The SDK requires Java JDK version `8` or later.

## Install the SDK

Install the [SurrealDB SDK](https://mvnrepository.com/artifact/com.surrealdb/surrealdb) from Maven Central using [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/).

**Gradle (Groovy)**

```groovy
ext {
    surrealdbVersion = "2.1.1"
}

dependencies {
    implementation "com.surrealdb:surrealdb:${surrealdbVersion}"
}
```

**Gradle (Kotlin)**

```kotlin
val surrealdbVersion by extra("2.1.1")

dependencies {
    implementation("com.surrealdb:surrealdb:${surrealdbVersion}")
}
```

**Maven**

```xml
<dependency>
    <groupId>com.surrealdb</groupId>
    <artifactId>surrealdb</artifactId>
    <version>2.1.1</version>
</dependency>
```

## Import the SDK

After installing, you can access the SDK by importing from the `com.surrealdb` package.

```java
import com.surrealdb.Surreal;
```

## Next steps

- [Getting started](/docs/languages/java.md) for a complete working example
- [Connecting to SurrealDB](/docs/reference/java/concepts/connecting-to-surrealdb.md) for connection options and protocols
- [Authentication](/docs/reference/java/concepts/authentication.md) for signing in and managing credentials

---

Source: https://surrealdb.com/docs/reference/javascript

# JavaScript SDK

The official SurrealDB SDK for JavaScript. Simple and advanced querying of a remote or embedded database.

The SurrealDB SDK for JavaScript and TypeScript lets you easily connect to SurrealDB from any environment: frontend, backend, serverless, mobile, or embedded within your app. It supports connecting to remote or embedded databases, running queries, managing data and authentication, and subscribing to real-time updates with live queries.

> [!NOTE]
> The latest version of the SDK is `2.1.1`.
> The SDK works with SurrealDB versions `v2.0.0` and later, including the current release, `v3.3.4`.

## Getting started

- [Installation](/docs/reference/javascript/installation.md) - Install the SDK and add it to your project.

- [Getting started guide](/docs/languages/javascript.md) - Connect to SurrealDB and run your first queries.

## Learn

- [Concepts](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) - Guides for connecting, authenticating, querying, and working with data.

- [API Reference](/docs/reference/javascript/api/core/surreal.md) - Complete reference for the SDK's methods, types, and errors.

## Concepts

- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) - open a connection over HTTP or WebSocket
- [Authentication](/docs/reference/javascript/concepts/authentication.md) - sign up, sign in, and authenticate with a token
- [Multiple sessions](/docs/reference/javascript/concepts/multiple-sessions.md) - run isolated sessions over a single connection
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) - send SurrealQL and read the results back
- [Bound queries](/docs/reference/javascript/concepts/bound-queries.md) - interpolate values into a query safely
- [Value types](/docs/reference/javascript/concepts/value-types.md) - how SurrealDB's types map onto native ones
- [Codecs](/docs/reference/javascript/concepts/codecs.md) - serialise and deserialise SurrealDB values
- [Transactions](/docs/reference/javascript/concepts/transactions.md) - group statements so they succeed or fail together
- [Live queries](/docs/reference/javascript/concepts/live-queries.md) - stream changes as they happen
- [Invoking APIs](/docs/reference/javascript/concepts/invoking-apis.md) - call API endpoints defined in the database
- [Utilities](/docs/reference/javascript/concepts/utilities.md) - compare, convert and escape values
- [Error handling](/docs/reference/javascript/concepts/error-handling.md) - what a failure looks like, and how to catch it
- [Diagnostics](/docs/reference/javascript/concepts/diagnostics.md) - inspect protocol-level traffic
- [Embedded engines](/docs/reference/javascript/concepts/embedded-engines.md) - run the database in the browser through WebAssembly, or on the server through the Node.js engine

## Language and engines

- [JavaScript](/docs/reference/javascript/installation.md) (available)

- [Node.js](/docs/reference/javascript/engines/node.md) (available)

- [WebAssembly](/docs/reference/javascript/engines/wasm.md) (available)

## Frameworks

- [React](/docs/reference/javascript/frameworks/react.md) (available)

- [Solid.js](/docs/reference/javascript/frameworks/solidjs.md) (available)

- [Vue.js](/docs/reference/javascript/frameworks/vuejs.md) (available)

- [Expo](/docs/reference/javascript/frameworks/expo.md) (available)

- [React Native](/docs/reference/javascript/frameworks/react-native.md) (available)

- [Next.js](/docs/build/integrations/vercel/nextjs.md) (available)

- **Angular** (coming soon)

- **Svelte** (coming soon)

## Example projects

You can find example repositories that demonstrate how to integrate SurrealDB in a number of different environments:

- [Surreal Stickies (React)](https://github.com/surrealdb/examples/tree/main/notes-v2) - A simple note-taking application built with SurrealDB, React, and Vite.

- [Surreal Stickies (SvelteKit)](https://github.com/surrealdb/examples/tree/main/notes-kit) - A simple note-taking application built with SurrealDB, SvelteKit, and Vite.

- [Surreal Presence (React)](https://github.com/Odonno/surrealdb-presence-demo) - A demo project on how to create a realtime presence web application using SurrealDB Live Queries.

- [TypeScript Starter](https://github.com/surrealdb/examples/tree/main/ts-bun-starter) - A simple TypeScript starter project using Bun.

## Contributing

To contribute to the SDK code, submit an Issue or Pull Request in the [surrealdb.js](https://github.com/surrealdb/surrealdb.js) repository. To contribute to this documentation, submit an Issue or Pull Request in the [docs.surrealdb.com](https://github.com/surrealdb/docs.surrealdb.com) repository.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.js)
- [NPM package](https://npmjs.com/package/surrealdb)
- [JSR package](https://jsr.io/@surrealdb/surrealdb)

---

Source: https://surrealdb.com/docs/reference/javascript/api

# API reference

Complete reference for the JavaScript SDK's classes, query builders, data types, and errors.

This section documents every public class, type, and helper in the JavaScript SDK. For task-oriented guidance, see the [concepts](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) pages.

## Sections

- [**Core classes**](/docs/reference/javascript/api/core.md) - Connecting, sessions, transactions, and query execution.
- [**Query builders**](/docs/reference/javascript/api/queries.md) - The chainable builders returned by each query method.
- [**Data types**](/docs/reference/javascript/api/values.md) - Classes for SurrealDB-specific values such as `RecordId` and `Duration`.
- [**Utilities**](/docs/reference/javascript/api/utilities/surql.md) - Helpers for building parameterised queries and comparing values.
- [**Errors**](/docs/reference/javascript/api/errors.md) - Error classes raised by the SDK.
- [**TypeScript types**](/docs/reference/javascript/api/types.md) - Type definitions and interfaces used throughout the SDK.

---

Source: https://surrealdb.com/docs/reference/javascript/api/core

# Core classes

The connection, session, transaction, and query execution classes that make up the SDK's core surface.

The core classes cover connecting to SurrealDB, scoping work to a session or a transaction, and executing queries. Every other part of the SDK is reached through one of these.

## Classes

- [**Surreal**](/docs/reference/javascript/api/core/surreal.md) - The main entry point for connecting to and interacting with a SurrealDB instance.
- [**SurrealSession**](/docs/reference/javascript/api/core/surreal-session.md) - Session-scoped context with authentication and query execution.
- [**SurrealQueryable**](/docs/reference/javascript/api/core/surreal-queryable.md) - The query execution methods shared by sessions and transactions.
- [**SurrealTransaction**](/docs/reference/javascript/api/core/surreal-transaction.md) - Atomic transaction support for executing multiple queries.
- [**SurrealApi**](/docs/reference/javascript/api/core/surreal-api.md) - Methods for invoking user-defined API endpoints.

## See also

- [Query builders](/docs/reference/javascript/api/queries.md) - The builder returned by each query method
- [Data types](/docs/reference/javascript/api/values.md) - Value classes used in queries and results
- [Errors](/docs/reference/javascript/api/errors.md) - Error classes raised by these methods

---

Source: https://surrealdb.com/docs/reference/javascript/api/core/surreal

# Surreal

The Surreal class is the main entry point for connecting to and interacting with a SurrealDB instance.

The `Surreal` class is the primary interface for connecting to a SurrealDB instance, managing connections, executing queries, and handling database sessions. It extends [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md) and inherits all session management and query execution capabilities.

By default, a `Surreal` instance operates with a default session scope, but you can create additional isolated sessions using the session management methods.

**Extends:** [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md) → [`SurrealQueryable`](/docs/reference/javascript/api/core/surreal-queryable.md)

**Source:** [api/surreal.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/api/surreal.ts)

## Constructor

### Syntax
```ts title="Constructor Syntax"
new Surreal(options?)
```

### Parameters

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#driveroptions">DriverOptions</a></code></td>
            <td>Driver-wide configuration options for customising engines, codecs, and implementations.</td>
        </tr>
    </tbody>
</table>

### Examples

```ts title="Basic Usage"
import { Surreal } from 'surrealdb';

const db = new Surreal();
```

```ts title="With Custom Options"
import { Surreal } from 'surrealdb';

const db = new Surreal({
    codecOptions: {
        useNativeDates: true // Use native Date objects instead of DateTime
    }
});
```

## Properties

### `status` {#status}

Returns the current connection status.

**Type:** [`ConnectionStatus`](/docs/reference/javascript/api/types/#connectionstatus)

**Values:** `"disconnected"` | `"connecting"` | `"reconnecting"` | `"connected"`

**Example:**
```ts
console.log(db.status); // "connected"
```

### `isConnected` {#isconnected}

Returns whether the connection is currently established. This is equivalent to checking if `status === "connected"`.

**Type:** `boolean`

**Example:**
```ts
if (db.isConnected) {
    console.log('Database is connected');
}
```

### `ready` {#ready}

A promise that resolves when the connection is established and ready, or rejects if a connection error occurs.

**Type:** `Promise<void>`

**Example:**
```ts
await db.ready;
console.log('Connection is ready');
```

### Inherited properties

The `Surreal` class inherits all properties from [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md), including:

- `namespace` - Current namespace
- `database` - Current database
- `accessToken` - Current access token
- `parameters` - Session parameters
- `session` - Session ID
- `isValid` - Session validity status

## Connection methods

### `.connect()` {#connect}

Connect to a local or remote SurrealDB instance.

> [!WARNING]
> Calling `connect()` will reset and dispose of any existing sessions created with `newSession()`.

```ts title="Method Syntax"
db.connect(url, opts?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>url</code> <label label="required" /></td>
            <td><code>string | URL</code></td>
            <td>The endpoint URL to connect to (e.g., <code>"ws://localhost:8000"</code>, <code>"http://localhost:8000/rpc"</code>).</td>
        </tr>
        <tr>
            <td><code>opts</code> <label label="optional" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#connectoptions">ConnectOptions</a></code></td>
            <td>Connection-specific options such as namespace, database, and authentication.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Promise<void>` - Resolves when connection is successful

#### Examples

```ts title="WebSocket Connection"
await db.connect('ws://localhost:8000');
```

```ts title="HTTP Connection"
await db.connect('http://localhost:8000/rpc');
```

```ts title="With Namespace and Database"
await db.connect('ws://localhost:8000', {
    namespace: 'my_namespace',
    database: 'my_database'
});
```

```ts title="With Authentication"
await db.connect('ws://localhost:8000', {
    namespace: 'my_namespace',
    database: 'my_database',
    authentication: {
        username: 'root',
        password: 'secret'
    }
});
```

```ts title="With Custom Reconnect Options"
await db.connect('ws://localhost:8000', {
    reconnect: {
        enabled: true,
        attempts: 10,
        retryDelay: 1000,
        retryDelayMax: 10000,
        retryDelayMultiplier: 2
    }
});
```

### `.close()` {#close}

Disconnect from the active SurrealDB instance.

```ts title="Method Syntax"
db.close()
```

#### Returns
`Promise<void>` - Resolves when disconnection is successful

#### Example
```ts
await db.close();
console.log('Connection closed');
```

### `.health()` {#health}

Check the health status of the connected SurrealDB instance.

```ts title="Method Syntax"
db.health()
```

#### Returns
`Promise<void>` - Resolves if the instance is healthy, rejects otherwise

#### Example
```ts
try {
    await db.health();
    console.log('Database is healthy');
} catch (error) {
    console.error('Health check failed:', error);
}
```

### `.version()` {#version}

Retrieve version information from the connected SurrealDB instance.

```ts title="Method Syntax"
db.version()
```

#### Returns
[`Promise<VersionInfo>`](/docs/reference/javascript/api/types/#versioninfo) - An object containing version information

#### Example
```ts
const info = await db.version();
console.log(info.version); // "surrealdb-2.1.0"
```

### `.isFeatureSupported()` {#isfeatureSupported}

Check whether a specific feature is available in the current connection.

```ts title="Method Syntax"
db.isFeatureSupported(feature)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>feature</code> <label label="required" /></td>
            <td><code>Feature</code></td>
            <td>A feature from the <code>Features</code> object (e.g. <code>Features.LiveQueries</code>, <code>Features.Api</code>).</td>
        </tr>
    </tbody>
</table>

#### Returns
`boolean` - `true` if the feature is supported, `false` otherwise

#### Example
```ts
import { Surreal, Features } from 'surrealdb';

if (db.isFeatureSupported(Features.LiveQueries)) {
    console.log('Live queries are supported');
}
```

## Session management methods

### `.sessions()` {#sessions}

List all active sessions on the current connection.

```ts title="Method Syntax"
db.sessions()
```

#### Returns
`Promise<string[]>` - An array of session IDs

#### Example
```ts
const sessionIds = await db.sessions();
console.log('Active sessions:', sessionIds);
```

### `.newSession()` {#newsession}

Create a new isolated session on the current connection. The new session will have its own namespace, database, variables, and authentication state, but will share the same connection.

Sessions are automatically restored when the connection reconnects. Call `reset()` on the returned session to destroy it.

```ts title="Method Syntax"
db.newSession()
```

#### Returns
`Promise<SurrealSession>` - A new [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md) instance

#### Example
```ts
// Create a new isolated session
const session = await db.newSession();

// Use different namespace/database in the new session
await session.use({ namespace: 'other_ns', database: 'other_db' });

// Query in the new session context
const results = await session.select('users');

// Destroy the session when done
await session.reset();
```

### `.closeSession()` {#closesession}

Close the primary session. This is equivalent to calling [`close()`](#close) on the connection.

```ts title="Method Syntax"
db.closeSession()
```

#### Returns
`Promise<void>` - Resolves when the session is closed

#### Example
```ts
await db.closeSession();
```

## Data management methods

### `.export()` {#export}

Export the database contents as a SQL string.

```ts title="Method Syntax"
db.export(options?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code>Partial&lt;<a href="/docs/reference/javascript/api/types/#sqlexportoptions">SqlExportOptions</a>&gt;</code></td>
            <td>Options to customise what gets exported.</td>
        </tr>
    </tbody>
</table>

#### Returns
`ExportPromise` - A promise that resolves to the exported database as a SQL string. Has a `.raw()` method that returns the raw `Response` object.

#### Examples

```ts title="Export Everything"
const sql = await db.export();
```

```ts title="Export Only Specific Tables"
const sql = await db.export({
    tables: ['users', 'posts'],
    records: true
});
```

```ts title="Export Schema Only (No Records)"
const sql = await db.export({
    records: false,
    tables: true,
    functions: true
});
```

```ts title="Export as Raw Response"
const response = await db.export().raw();
```

### `.import()` {#import}

Import database contents from a SQL string or a readable stream.

```ts title="Method Syntax"
db.import(input)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>input</code> <label label="required" /></td>
            <td><code>string | ReadableStream&lt;string&gt;</code></td>
            <td>The SQL string or readable stream to import into the database.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Promise<void>` - Resolves when the import is complete

#### Example
```ts
const sqlData = `
    DEFINE TABLE users SCHEMAFULL;
    DEFINE FIELD name ON users TYPE string;
    CREATE users:john SET name = 'John Doe';
`;

await db.import(sqlData);
console.log('Data imported successfully');
```

### `.exportModel()` {#exportmodel}

Export a SurrealML model from the database.

```ts title="Method Syntax"
db.exportModel(name, version)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>name</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The name of the SurrealML model to export.</td>
        </tr>
        <tr>
            <td><code>version</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The version of the model to export.</td>
        </tr>
    </tbody>
</table>

#### Returns
`ExportModelPromise` - A promise that resolves to the exported model data. Has a `.raw()` method that returns the raw `Response` object.

#### Example
```ts
const modelData = await db.exportModel('my_model', '1.0.0');
```

```ts title="Export as Raw Response"
const response = await db.exportModel('my_model', '1.0.0').raw();
```

## Events

The `Surreal` class implements the `EventPublisher` interface and emits various events during the connection lifecycle. Subscribe to events using the [`subscribe()`](#subscribe) method.

### `connecting` {#event-connecting}

Emitted when the connection attempt starts.

**Payload:** None

**Example:**
```ts
const unsubscribe = db.subscribe('connecting', () => {
    console.log('Connecting to database...');
});
```

### `connected` {#event-connected}

Emitted when the connection is successfully established.

**Payload:** `[version: string]` - The SurrealDB version string

**Example:**
```ts
db.subscribe('connected', (version) => {
    console.log('Connected to SurrealDB version:', version);
});
```

### `reconnecting` {#event-reconnecting}

Emitted when the connection is attempting to reconnect after being disconnected.

**Payload:** None

**Example:**
```ts
db.subscribe('reconnecting', () => {
    console.log('Attempting to reconnect...');
});
```

### `disconnected` {#event-disconnected}

Emitted when the connection is closed.

**Payload:** None

**Example:**
```ts
db.subscribe('disconnected', () => {
    console.log('Disconnected from database');
});
```

### `error` {#event-error}

Emitted when a connection error occurs.

**Payload:** `[error: Error]` - The error object

**Example:**
```ts
db.subscribe('error', (error) => {
    console.error('Connection error:', error.message);
});
```

### Inherited events

The `Surreal` class also inherits and re-emits events from [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md):

- `auth` - Emitted when authentication state changes
- `using` - Emitted when namespace/database changes

### `.subscribe()` {#subscribe}

Subscribe to connection and session events.

```ts title="Method Syntax"
db.subscribe(event, listener)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>event</code> <label label="required" /></td>
            <td><code>keyof <a href="/docs/reference/javascript/api/types/#surrealevents">SurrealEvents</a></code></td>
            <td>The event name to subscribe to.</td>
        </tr>
        <tr>
            <td><code>listener</code> <label label="required" /></td>
            <td><code>Function</code></td>
            <td>Callback function invoked when the event is emitted.</td>
        </tr>
    </tbody>
</table>

#### Returns
`() => void` - An unsubscribe function to remove the event listener

#### Example
```ts
const unsubscribe = db.subscribe('connected', (version) => {
    console.log('Connected:', version);
});

// Later, unsubscribe from the event
unsubscribe();
```

## Inherited methods

As `Surreal` extends [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md), it inherits all authentication and query methods:

### Authentication methods
- [`signup()`](/docs/reference/javascript/api/core/surreal-session.md#signup) - Sign up a new user
- [`signin()`](/docs/reference/javascript/api/core/surreal-session.md#signin) - Sign in with credentials
- [`authenticate()`](/docs/reference/javascript/api/core/surreal-session.md#authenticate) - Authenticate with a token
- [`invalidate()`](/docs/reference/javascript/api/core/surreal-session.md#invalidate) - Invalidate the session

### Session configuration methods
- [`use()`](/docs/reference/javascript/api/core/surreal-session.md#use) - Set namespace and database
- [`set()`](/docs/reference/javascript/api/core/surreal-session.md#set) - Set a session parameter
- [`unset()`](/docs/reference/javascript/api/core/surreal-session.md#unset) - Remove a session parameter
- [`reset()`](/docs/reference/javascript/api/core/surreal-session.md#reset) - Reset the session

### Query methods

As `Surreal` extends [`SurrealQueryable`](/docs/reference/javascript/api/core/surreal-queryable.md) (via `SurrealSession`), it also inherits all query execution methods:

- [`query()`](/docs/reference/javascript/api/core/surreal-queryable.md#query) - Execute raw SurrealQL
- [`select()`](/docs/reference/javascript/api/core/surreal-queryable.md#select) - Select records
- [`create()`](/docs/reference/javascript/api/core/surreal-queryable.md#create) - Create records
- [`insert()`](/docs/reference/javascript/api/core/surreal-queryable.md#insert) - Insert records
- [`update()`](/docs/reference/javascript/api/core/surreal-queryable.md#update) - Update records
- [`upsert()`](/docs/reference/javascript/api/core/surreal-queryable.md#upsert) - Upsert records
- [`delete()`](/docs/reference/javascript/api/core/surreal-queryable.md#delete) - Delete records
- [`relate()`](/docs/reference/javascript/api/core/surreal-queryable.md#relate) - Create graph relationships
- [`live()`](/docs/reference/javascript/api/core/surreal-queryable.md#live) - Subscribe to live queries
- [`run()`](/docs/reference/javascript/api/core/surreal-queryable.md#run) - Execute functions

### Transaction method
- [`beginTransaction()`](/docs/reference/javascript/api/core/surreal-transaction.md) - Start a transaction

## Type parameters

This class does not use generic type parameters.

## Complete example

```ts
import { Surreal } from 'surrealdb';

// Create and connect
const db = new Surreal({
    codecOptions: {
        useNativeDates: true
    }
});

// Subscribe to connection events
db.subscribe('connecting', () => console.log('Connecting...'));
db.subscribe('connected', (version) => console.log('Connected:', version));
db.subscribe('error', (error) => console.error('Error:', error));

// Connect to database
await db.connect('ws://localhost:8000', {
    namespace: 'my_namespace',
    database: 'my_database',
    authentication: {
        username: 'root',
        password: 'secret'
    }
});

// Check connection status
console.log('Connected:', db.isConnected); // true
console.log('Status:', db.status); // "connected"

// Get version info
const version = await db.version();
console.log('Version:', version.version);

// Execute queries (inherited from SurrealSession/SurrealQueryable)
const users = await db.select('users');
console.log('Users:', users);

// Create a new isolated session
const session = await db.newSession();
await session.use({ namespace: 'other_ns', database: 'other_db' });
const otherData = await session.select('data');

// Export database
const backup = await db.export({ records: true });
console.log('Backup size:', backup.length);

// Close connection
await db.close();
```

## See also

- [SurrealSession](/docs/reference/javascript/api/core/surreal-session.md) - Session management and authentication
- [SurrealQueryable](/docs/reference/javascript/api/core/surreal-queryable.md) - Query execution methods
- [SurrealTransaction](/docs/reference/javascript/api/core/surreal-transaction.md) - Transaction support
- [Node engine](/docs/reference/javascript/engines/node.md) and [WASM engine](/docs/reference/javascript/engines/wasm.md) - Engine-specific documentation
- [Data types](/docs/reference/javascript/api/values.md) - Working with data types

---

Source: https://surrealdb.com/docs/reference/javascript/api/core/surreal-api

# SurrealApi

The SurrealApi class provides methods for invoking user-defined API endpoints in SurrealDB.

The `SurrealApi` class exposes methods to interact with user-defined API endpoints in SurrealDB. It provides type-safe HTTP-style methods (GET, POST, PUT, DELETE, PATCH, TRACE) for invoking custom database APIs.

**Source:** [api/api.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/api/api.ts)

## Overview

SurrealApi allows you to access custom API endpoints defined in your SurrealDB database. The class supports type-safe API definitions, which type-check request and response bodies at compile time.

```ts
// Define your API paths with types
type MyPaths = {
    "/users": { get: [void, User[]] };
    "/users/:id": { get: [void, User] };
    "/users": { post: [CreateUserInput, User] };
};

// Access with type safety
const api = db.api<MyPaths>();
const users = await api.get("/users"); // Type: User[]
```

## Creating an API instance

API instances are created through the [`api`](/docs/reference/javascript/api/core/surreal-queryable.md#api) property on [`Surreal`](/docs/reference/javascript/api/core/surreal.md), [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md), or [`SurrealTransaction`](/docs/reference/javascript/api/core/surreal-transaction.md):

```ts
// Basic API access
const api = db.api();

// Type-safe API access
const api = db.api<MyPaths>();

// API with path prefix
const usersApi = db.api<MyPaths>("/users");
```

## Type definitions

### `PathDef` {#pathdef}

Defines the HTTP methods available for an API path:

```ts
type PathDef = Partial<Record<HttpMethod, MethodDef>>;
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "trace";
type MethodDef = [RequestBody, ResponseBody] | [];
```

### Example path definitions

```ts
type MyApiPaths = {
    // GET endpoint with no request body, returns User[]
    "/users": {
        get: [void, User[]];
        post: [CreateUserRequest, User];
    };
    
    // Dynamic path parameters
    [K: `/users/${string}`]: {
        get: [void, User];
        put: [UpdateUserRequest, User];
        delete: [void, void];
    };
    
    // POST endpoint with request/response bodies
    "/auth/login": {
        post: [{ email: string; password: string }, { token: string }];
    };
};
```

## Methods

### `.header()` {#header}

Configure a header for all requests sent by this API instance. This is useful for setting common headers like authentication tokens or content types.

```ts title="Method Syntax"
api.header(name, value)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>name</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The name of the header to configure.</td>
        </tr>
        <tr>
            <td><code>value</code> <label label="required" /></td>
            <td><code>string | null</code></td>
            <td>The value to set, or <code>null</code> to remove the header.</td>
        </tr>
    </tbody>
</table>

#### Returns
`void`

#### Examples

```ts title="Set Custom Header"
api.header('X-API-Key', 'my-secret-key');
```

```ts title="Remove Header"
api.header('X-API-Key', null);
```

```ts title="Set Authorization Header"
api.header('Authorization', `Bearer ${token}`);
```

### `.invoke()` {#invoke}

Invoke a user-defined API with a custom request object. This is the generic method used by all HTTP method-specific functions.

> [!NOTE: Tip]
> Prefer using method-specific functions ([`.get()`](#get), [`.post()`](#post), etc.) for better type safety.

```ts title="Method Syntax"
api.invoke<T>(path, request?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>path</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The API path to invoke.</td>
        </tr>
        <tr>
            <td><code>request</code> <label label="optional" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#apirequest">ApiRequest</a>&lt;T&gt;</code></td>
            <td>The request configuration object.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`ApiPromise<unknown>`](/docs/reference/javascript/api/queries/api-promise.md) - A promise for the API response

#### Example
```ts
const result = await api.invoke('/custom', {
    method: 'post',
    body: { data: 'value' },
    headers: { 'X-Custom': 'header' },
    query: { filter: 'active' }
});
```

### `.get()` {#get}

Invoke a user-defined GET API endpoint.

```ts title="Method Syntax"
api.get(path)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>path</code> <label label="required" /></td>
            <td><code>P extends ValidPaths&lt;TPaths, "get"&gt;</code></td>
            <td>The API path to invoke.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`ApiPromise<void, ResponseBody>`](/docs/reference/javascript/api/queries/api-promise.md) - A promise for the GET response

#### Examples

```ts title="Get All Users"
const users = await api.get("/users");
```

```ts title="Get Specific User"
const user = await api.get("/users/123");
```

### `.post()` {#post}

Invoke a user-defined POST API endpoint.

```ts title="Method Syntax"
api.post(path, body?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>path</code> <label label="required" /></td>
            <td><code>P extends ValidPaths&lt;TPaths, "post"&gt;</code></td>
            <td>The API path to invoke.</td>
        </tr>
        <tr>
            <td><code>body</code> <label label="optional" /></td>
            <td><code>RequestBody&lt;TPaths, P, "post"&gt;</code></td>
            <td>The request body to send.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`ApiPromise<RequestBody, ResponseBody>`](/docs/reference/javascript/api/queries/api-promise.md) - A promise for the POST response

#### Example
```ts
const newUser = await api.post("/users", {
    name: "John Doe",
    email: "john@example.com"
});
```

### `.put()` {#put}

Invoke a user-defined PUT API endpoint.

```ts title="Method Syntax"
api.put(path, body?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>path</code> <label label="required" /></td>
            <td><code>P extends ValidPaths&lt;TPaths, "put"&gt;</code></td>
            <td>The API path to invoke.</td>
        </tr>
        <tr>
            <td><code>body</code> <label label="optional" /></td>
            <td><code>RequestBody&lt;TPaths, P, "put"&gt;</code></td>
            <td>The request body to send.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`ApiPromise<RequestBody, ResponseBody>`](/docs/reference/javascript/api/queries/api-promise.md) - A promise for the PUT response

#### Example
```ts
const updated = await api.put("/users/123", {
    name: "John Smith",
    email: "john.smith@example.com"
});
```

### `.delete()` {#delete}

Invoke a user-defined DELETE API endpoint.

```ts title="Method Syntax"
api.delete(path, body?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>path</code> <label label="required" /></td>
            <td><code>P extends ValidPaths&lt;TPaths, "delete"&gt;</code></td>
            <td>The API path to invoke.</td>
        </tr>
        <tr>
            <td><code>body</code> <label label="optional" /></td>
            <td><code>RequestBody&lt;TPaths, P, "delete"&gt;</code></td>
            <td>Optional request body.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`ApiPromise<RequestBody, ResponseBody>`](/docs/reference/javascript/api/queries/api-promise.md) - A promise for the DELETE response

#### Example
```ts
await api.delete("/users/123");
```

### `.patch()` {#patch}

Invoke a user-defined PATCH API endpoint.

```ts title="Method Syntax"
api.patch(path, body?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>path</code> <label label="required" /></td>
            <td><code>P extends ValidPaths&lt;TPaths, "patch"&gt;</code></td>
            <td>The API path to invoke.</td>
        </tr>
        <tr>
            <td><code>body</code> <label label="optional" /></td>
            <td><code>RequestBody&lt;TPaths, P, "patch"&gt;</code></td>
            <td>The partial updates to apply.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`ApiPromise<RequestBody, ResponseBody>`](/docs/reference/javascript/api/queries/api-promise.md) - A promise for the PATCH response

#### Example
```ts
const updated = await api.patch("/users/123", {
    email: "newemail@example.com"
});
```

### `.trace()` {#trace}

Invoke a user-defined TRACE API endpoint.

```ts title="Method Syntax"
api.trace(path, body?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>path</code> <label label="required" /></td>
            <td><code>P extends ValidPaths&lt;TPaths, "trace"&gt;</code></td>
            <td>The API path to invoke.</td>
        </tr>
        <tr>
            <td><code>body</code> <label label="optional" /></td>
            <td><code>RequestBody&lt;TPaths, P, "trace"&gt;</code></td>
            <td>Optional request body.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`ApiPromise<RequestBody, ResponseBody>`](/docs/reference/javascript/api/queries/api-promise.md) - A promise for the TRACE response

## Complete examples

### Basic API usage

```ts
import { Surreal } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Get API instance
const api = db.api();

// Make API calls
const users = await api.get('/users');
const user = await api.get('/users/123');
const created = await api.post('/users', {
    name: 'New User',
    email: 'user@example.com'
});
```

### Type-safe API

```ts
// Define your API contract
type ApiPaths = {
    "/users": {
        get: [void, User[]];
        post: [CreateUserRequest, User];
    };
    [K: `/users/${string}`]: {
        get: [void, User];
        put: [UpdateUserRequest, User];
        delete: [void, void];
    };
    "/auth/login": {
        post: [LoginRequest, LoginResponse];
    };
};

// Create type-safe API instance
const api = db.api<ApiPaths>();

// All calls are type-checked
const users: User[] = await api.get("/users");
const user: User = await api.get("/users/123");
const newUser: User = await api.post("/users", {
    name: "Alice",
    email: "alice@example.com"
});
```

### Using headers

```ts
const api = db.api();

// Set authentication header
const token = await login();
api.header('Authorization', `Bearer ${token}`);

// All subsequent requests include the header
const protected Data = await api.get('/protected-endpoint');

// Remove header
api.header('Authorization', null);
```

### API with prefix

```ts
type UserPaths = {
    "/": { get: [void, User[]] };
    [K: `/${string}`]: {
        get: [void, User];
        put: [UpdateUserRequest, User];
        delete: [void, void];
    };
};

// Create API with prefix
const usersApi = db.api<UserPaths>("/users");

// Calls are prefixed automatically
const all Users = await usersApi.get("/");        // GET /users/
const user = await usersApi.get("/123");          // GET /users/123
const updated = await usersApi.put("/123", data); // PUT /users/123
```

### Error handling

```ts
const api = db.api();

try {
    const user = await api.get('/users/999');
} catch (error) {
    if (error instanceof ResponseError) {
        console.error('API error:', error.message);
        console.error('Status code:', error.code);
    } else {
        console.error('Unexpected error:', error);
    }
}
```

### With transaction

```ts
const txn = await db.beginTransaction();

try {
    // API calls within transaction
    const api = txn.api();
    const user = await api.post('/users', userData);
    const profile = await api.post('/profiles', {
        userId: user.id,
        ...profileData
    });
    
    await txn.commit();
} catch (error) {
    await txn.cancel();
    throw error;
}
```

## Best practices

### 1. Define API types

Always define types for your API paths, so that request and response bodies are type-checked:

```ts
// Good: Type-safe
type MyApi = {
    "/users": { get: [void, User[]] };
};
const api = db.api<MyApi>();

// Avoid: Untyped
const api = db.api();
```

### 2. Reuse API instances

Create and reuse API instances rather than creating new ones for each call:

```ts
// Good: Reuse instance
const api = db.api();
await api.get('/users');
await api.get('/posts');

// Avoid: Creating multiple instances
await db.api().get('/users');
await db.api().get('/posts');
```

### 3. Use prefixes for namespacing

Use path prefixes to organise related endpoints:

```ts
const usersApi = db.api("/users");
const postsApi = db.api("/posts");

await usersApi.get("/123");  // GET /users/123
await postsApi.get("/456");  // GET /posts/456
```

## See also

- [SurrealQueryable.api](/docs/reference/javascript/api/core/surreal-queryable.md#api) - Creating API instances
- [ApiPromise](/docs/reference/javascript/api/queries/api-promise.md) - API response handling
- [User-Defined APIs Guide](/docs/reference/query-language/statements/define/api.md) - Defining APIs in SurrealDB

---

Source: https://surrealdb.com/docs/reference/javascript/api/core/surreal-queryable

# SurrealQueryable

The SurrealQueryable class provides all query execution methods for interacting with SurrealDB.

The `SurrealQueryable` class is an abstract base class that provides all query execution methods for interacting with SurrealDB. It is the foundation for executing database operations and is extended by [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md) and [`SurrealTransaction`](/docs/reference/javascript/api/core/surreal-transaction.md).

**Extended by:** [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md), [`SurrealTransaction`](/docs/reference/javascript/api/core/surreal-transaction.md)

**Source:** [api/queryable.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/api/queryable.ts)

## Methods

### `.api()` {#api}

Create a [`SurrealApi`](/docs/reference/javascript/api/core/surreal-api.md) instance for invoking user-defined API endpoints. You can provide type definitions for type-safe API calls and an optional path prefix.

```ts title="Method Syntax"
db.api<TPaths>(prefix?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>prefix</code> <label label="optional" /></td>
            <td><code>string</code></td>
            <td>A path prefix to prepend to all API calls made through this instance.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`SurrealApi<TPaths>`](/docs/reference/javascript/api/core/surreal-api.md) - An API instance for invoking custom database APIs

#### Examples
```ts title="Basic API Access"
const api = db.api();
const result = await api.get('/users');
```

```ts title="Type-Safe API Access"
type MyPaths = {
    "/users": { get: [void, User[]] };
    [K: `/users/${number}`]: { get: [void, User] };
};

const api = db.api<MyPaths>();
const users = await api.get("/users"); // Type: User[]
```

```ts title="API with Prefix"
const usersApi = db.api<MyPaths>("/users");
const user = await usersApi.get("123"); // GET /users/123
```

## Query methods

### `.query()` {#query}

Execute raw SurrealQL statements against the database.

```ts title="Method Syntax"
db.query<R>(query, bindings?)
db.query<R>(boundQuery)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>query</code> <label label="required" /></td>
            <td><code>string | <a href="/docs/reference/javascript/api/utilities/bound-query.md">BoundQuery</a></code></td>
            <td>The SurrealQL query string or BoundQuery instance.</td>
        </tr>
        <tr>
            <td><code>bindings</code> <label label="optional" /></td>
            <td><code>Record&lt;string, unknown&gt;</code></td>
            <td>Variables to bind in the query (when using string query).</td>
        </tr>
    </tbody>
</table>

#### Type parameters
- `R extends unknown[]` - Array of result types for each query statement

#### Returns
[`Query<R>`](/docs/reference/javascript/api/queries/query.md) - A query instance that can be configured and executed

#### Examples

```ts title="Simple Query"
const result = await db.query('SELECT * FROM users').collect();
console.log(result); // [{ success: true, result: [...] }]
```

```ts title="Query with Bindings"
const result = await db.query(
    'SELECT * FROM users WHERE age > $age',
    { age: 18 }
).collect();
```

```ts title="Multiple Statements"
const results = await db.query<[User[], Post[]]>(`
    SELECT * FROM users;
    SELECT * FROM posts;
`).collect();

const [users, posts] = results.map(r => r.result);
```

```ts title="Using BoundQuery"
import { surql } from 'surrealdb';

const query = surql`SELECT * FROM users WHERE age > ${18}`;
const result = await db.query(query).collect();
```

### `.select()` {#select}

Select records from the database by record ID, record ID range, or table.

```ts title="Method Syntax"
db.select<T>(recordId)
db.select<T>(range)
db.select<T>(table)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>recordId</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#anyrecordid">AnyRecordId</a></code></td>
            <td>A specific record ID to select.</td>
        </tr>
        <tr>
            <td><code>range</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/record-id.md">RecordIdRange</a></code></td>
            <td>A range of record IDs to select.</td>
        </tr>
        <tr>
            <td><code>table</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/table.md">Table</a></code></td>
            <td>A table to select all records from.</td>
        </tr>
    </tbody>
</table>

#### Returns
- For `RecordId`: [`SelectPromise<T | undefined, T>`](/docs/reference/javascript/api/queries/select-promise.md) - A promise resolving to a single record or `undefined`
- For `Table` or `RecordIdRange`: [`SelectPromise<T[], T>`](/docs/reference/javascript/api/queries/select-promise.md) - A promise resolving to an array of records

#### Examples

```ts title="Select by Record ID"
const user = await db.select(new RecordId('users', 'john'));
```

```ts title="Select All from Table"
const users = await db.select(new Table('users'));
```

```ts title="Select with Configuration"
const users = await db.select(new Table('users'))
    .where('age > 18')
    .limit(10)
    .start(0);
```

### `.create()` {#create}

Create new records in the database.

```ts title="Method Syntax"
db.create<T>(recordId)
db.create<T>(table)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>recordId</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#anyrecordid">AnyRecordId</a></code></td>
            <td>The record ID for the new record.</td>
        </tr>
        <tr>
            <td><code>table</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/table.md">Table</a></code></td>
            <td>The table to create a record in (auto-generated ID).</td>
        </tr>
    </tbody>
</table>

#### Returns
[`CreatePromise<RecordResult<T>, T>`](/docs/reference/javascript/api/queries/create-promise.md) - A promise with chainable configuration methods

#### Examples

```ts title="Create with Specific ID"
const user = await db.create(new RecordId('users', 'john'))
    .content({ name: 'John Doe', email: 'john@example.com' });
```

```ts title="Create with Auto-Generated ID"
const user = await db.create(new Table('users'))
    .content({ name: 'Jane Doe', email: 'jane@example.com' });
```

### `.insert()` {#insert}

Insert one or multiple records into the database.

```ts title="Method Syntax"
db.insert<T>(data)
db.insert<T>(table, data)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> <label label="optional" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/table.md">Table</a></code></td>
            <td>The table to insert records into.</td>
        </tr>
        <tr>
            <td><code>data</code> <label label="required" /></td>
            <td><code>Values&lt;T&gt; | Values&lt;T&gt;[]</code></td>
            <td>One or more records to insert.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`InsertPromise<T[]>`](/docs/reference/javascript/api/queries/insert-promise.md) - A promise with chainable configuration methods

#### Examples

```ts title="Insert Single Record"
const user = await db.insert({
    id: new RecordId('users', 'alice'),
    name: 'Alice',
    email: 'alice@example.com'
});
```

```ts title="Insert Multiple Records"
const users = await db.insert([
    { id: new RecordId('users', 'bob'), name: 'Bob' },
    { id: new RecordId('users', 'carol'), name: 'Carol' }
]);
```

```ts title="Insert into Table"
const users = await db.insert(new Table('users'), [
    { name: 'Dave' },
    { name: 'Eve' }
]);
```

### `.update()` {#update}

Update existing records in the database.

```ts title="Method Syntax"
db.update<T>(recordId)
db.update<T>(range)
db.update<T>(table)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>recordId</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#anyrecordid">AnyRecordId</a></code></td>
            <td>A specific record ID to update.</td>
        </tr>
        <tr>
            <td><code>range</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/record-id.md">RecordIdRange</a></code></td>
            <td>A range of record IDs to update.</td>
        </tr>
        <tr>
            <td><code>table</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/table.md">Table</a></code></td>
            <td>A table to update all records in.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`UpdatePromise<T>`](/docs/reference/javascript/api/queries/update-promise.md) - A promise with chainable configuration methods

#### Examples

```ts title="Update with Content"
const user = await db.update(new RecordId('users', 'john'))
    .content({ name: 'John Smith', email: 'john@example.com' });
```

```ts title="Update with Merge"
const user = await db.update(new RecordId('users', 'john'))
    .merge({ email: 'newemail@example.com' });
```

```ts title="Update with Condition"
const users = await db.update(new Table('users'))
    .merge({ verified: true })
    .where('age > 18');
```

### `.upsert()` {#upsert}

Upsert records (insert if they don't exist, replace if they do).

> [!WARNING]
> This function replaces existing record data with the specified data.

```ts title="Method Syntax"
db.upsert<T>(recordId)
db.upsert<T>(range)
db.upsert<T>(table)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>recordId</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#anyrecordid">AnyRecordId</a></code></td>
            <td>A specific record ID to upsert.</td>
        </tr>
        <tr>
            <td><code>range</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/record-id.md">RecordIdRange</a></code></td>
            <td>A range of record IDs to upsert.</td>
        </tr>
        <tr>
            <td><code>table</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/table.md">Table</a></code></td>
            <td>A table to upsert all records in.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`UpsertPromise<T>`](/docs/reference/javascript/api/queries/upsert-promise.md) - A promise with chainable configuration methods

#### Example
```ts
const user = await db.upsert(new RecordId('users', 'john'))
    .content({ name: 'John Doe', email: 'john@example.com' });
```

### `.delete()` {#delete}

Delete records from the database.

```ts title="Method Syntax"
db.delete<T>(recordId)
db.delete<T>(range)
db.delete<T>(table)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>recordId</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#anyrecordid">AnyRecordId</a></code></td>
            <td>A specific record ID to delete.</td>
        </tr>
        <tr>
            <td><code>range</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/record-id.md">RecordIdRange</a></code></td>
            <td>A range of record IDs to delete.</td>
        </tr>
        <tr>
            <td><code>table</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/table.md">Table</a></code></td>
            <td>A table to delete all records from.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`DeletePromise<T>`](/docs/reference/javascript/api/queries/delete-promise.md) - A promise with chainable configuration methods

#### Examples

```ts title="Delete Single Record"
const deleted = await db.delete(new RecordId('users', 'john'));
```

```ts title="Delete All from Table"
const deleted = await db.delete(new Table('users'));
```

### `.relate()` {#relate}

Create graph relationships (edges) between records.

```ts title="Method Syntax"
db.relate<T>(from, edge, to, data?)
db.relate<T>(from[], edge, to[], data?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>from</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#anyrecordid">AnyRecordId</a> | AnyRecordId[]</code></td>
            <td>The source record(s) for the relationship.</td>
        </tr>
        <tr>
            <td><code>edge</code> <label label="required" /></td>
            <td><code>Table | <a href="/docs/reference/javascript/api/types/#anyrecordid">AnyRecordId</a></code></td>
            <td>The edge table or specific edge record ID.</td>
        </tr>
        <tr>
            <td><code>to</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#anyrecordid">AnyRecordId</a> | AnyRecordId[]</code></td>
            <td>The target record(s) for the relationship.</td>
        </tr>
        <tr>
            <td><code>data</code> <label label="optional" /></td>
            <td><code>Partial&lt;T&gt;</code></td>
            <td>Optional data to store on the edge record.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`RelatePromise<T>`](/docs/reference/javascript/api/queries/relate-promise.md) - A promise for the relationship operation

#### Examples

```ts title="Create Single Relationship"
const edge = await db.relate(
    new RecordId('users', 'john'),
    new Table('likes'),
    new RecordId('posts', '1'),
    { timestamp: new Date() }
);
```

```ts title="Create Multiple Relationships"
const edges = await db.relate(
    [new RecordId('users', 'john'), new RecordId('users', 'jane')],
    new Table('follows'),
    [new RecordId('users', 'alice'), new RecordId('users', 'bob')]
);
```

### `.live()` {#live}

Create a live query subscription to receive real-time updates when records change.

```ts title="Method Syntax"
db.live<T>(what)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>what</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#liveresource">LiveResource</a></code></td>
            <td>The table, record ID, or range to subscribe to.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`ManagedLivePromise<T>`](/docs/reference/javascript/api/queries/live-promise.md) - A managed live query subscription

#### Example
```ts
const subscription = await db.live(new Table('users'));

for await (const update of subscription) {
    console.log('Update:', update.action, update.result);
}
```

### `.liveOf()` {#liveof}

Subscribe to an existing live query using its ID.

> [!NOTE]
> This function is for use with live queries not managed by the driver.

```ts title="Method Syntax"
db.liveOf<T>(id)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>id</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/uuid.md">Uuid</a></code></td>
            <td>The UUID of the existing live query.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`UnmanagedLivePromise<T>`](/docs/reference/javascript/api/queries/live-promise.md) - An unmanaged live query subscription

#### Example
```ts
const liveQueryId = await db.query('LIVE SELECT * FROM users').collect();
const subscription = db.liveOf(liveQueryId);
```

### `.run()` {#run}

Execute a SurrealDB function or SurrealML model.

```ts title="Method Syntax"
db.run<T>(name, args?)
db.run<T>(name, version, args?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>name</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The full name of the function to run (e.g., <code>"fn::calculate"</code>).</td>
        </tr>
        <tr>
            <td><code>version</code> <label label="optional" /></td>
            <td><code>string</code></td>
            <td>The version of a SurrealML model to use.</td>
        </tr>
        <tr>
            <td><code>args</code> <label label="optional" /></td>
            <td><code>unknown[]</code></td>
            <td>Arguments to pass to the function.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`RunPromise<T>`](/docs/reference/javascript/api/queries/run-promise.md) - A promise for the function result

#### Examples

```ts title="Run Custom Function"
const result = await db.run('fn::calculate', [10, 20]);
```

```ts title="Run SurrealML Model"
const prediction = await db.run('ml::predict', '1.0.0', [inputData]);
```

### `.auth()` {#auth}

Get the currently authenticated record user by selecting the `$auth` parameter.

> [!NOTE]
> The user must have permission to select their own record, otherwise an empty result is returned.

```ts title="Method Syntax"
db.auth<T>()
```

#### Returns
`AuthPromise<T | undefined>` - A promise for the authenticated user record, or `undefined` if not authenticated

#### Example
```ts
const user = await db.auth();
console.log('Current user:', user);
```

## See also

- [SurrealSession](/docs/reference/javascript/api/core/surreal-session.md) - Session management extending this class
- [SurrealTransaction](/docs/reference/javascript/api/core/surreal-transaction.md) - Transaction support extending this class
- [Query builders](/docs/reference/javascript/api/queries/) - Detailed query builder documentation
- [SelectPromise](/docs/reference/javascript/api/queries/select-promise.md) - SELECT query configuration
- [CreatePromise](/docs/reference/javascript/api/queries/create-promise.md) - CREATE query configuration

---

Source: https://surrealdb.com/docs/reference/javascript/api/core/surreal-session

# SurrealSession

The SurrealSession class provides session-scoped context with authentication and query execution capabilities.

The `SurrealSession` class represents a scoped contextual session attached to a SurrealDB connection. It provides authentication, session configuration, and inherits all query execution methods from [`SurrealQueryable`](/docs/reference/javascript/api/core/surreal-queryable.md).

Sessions allow you to maintain isolated contexts with their own namespace, database, variables, and authentication state, while sharing the underlying connection.

**Extends:** [`SurrealQueryable`](/docs/reference/javascript/api/core/surreal-queryable.md)

**Extended by:** [`Surreal`](/docs/reference/javascript/api/core/surreal.md)

**Source:** [api/session.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/api/session.ts)

## Constructor

The constructor is typically not called directly. Use [`Surreal.newSession()`](/docs/reference/javascript/api/core/surreal.md#newsession) or [`forkSession()`](#forksession) to create new sessions.

## Properties

### `namespace` {#namespace}

Returns the currently selected namespace for this session.

**Type:** `string | undefined`

**Example:**
```ts
console.log(session.namespace); // "my_namespace"
```

### `database` {#database}

Returns the currently selected database for this session.

**Type:** `string | undefined`

**Example:**
```ts
console.log(session.database); // "my_database"
```

### `accessToken` {#accesstoken}

Returns the current authentication access token for this session.

**Type:** `string | undefined`

**Example:**
```ts
if (session.accessToken) {
    console.log('Session is authenticated');
}
```

### `parameters` {#parameters}

Returns all parameters currently defined on the session.

**Type:** `Record<string, unknown>`

**Example:**
```ts
console.log(session.parameters); // { user_id: '123', role: 'admin' }
```

### `session` {#session}

Returns the unique session ID. For the default session, `undefined` is returned.

**Type:** [`Uuid`](/docs/reference/javascript/api/values/uuid.md) `| undefined`

**Example:**
```ts
const sessionId = session.session;
console.log('Session ID:', sessionId);
```

### `isValid` {#isvalid}

Returns whether the session is valid and can be used. This is always `true` for the default session, but will be `false` for sessions that have been disposed via [`reset()`](#reset) or [`closeSession()`](#closesession).

**Type:** `boolean`

**Example:**
```ts
if (session.isValid) {
    await session.select('users');
} else {
    console.log('Session has been closed');
}
```

## Session management methods

### `.forkSession()` {#forksession}

Create a new session by cloning the current session. The new session inherits all properties from the parent session including namespace, database, variables, and authentication state.

Sessions are automatically restored when the connection reconnects. Call [`reset()`](#reset) on the created session to destroy it.

```ts title="Method Syntax"
session.forkSession()
```

#### Returns
[`Promise<SurrealSession>`](/docs/reference/javascript/api/core/surreal-session.md) - A new session instance

#### Example
```ts
// Create a forked session that inherits parent state
const childSession = await session.forkSession();

// The child inherits parent's namespace and database
console.log(childSession.namespace); // Same as parent
console.log(childSession.database); // Same as parent

// But can be changed independently
await childSession.use({ database: 'other_db' });

// Parent session remains unchanged
console.log(session.database); // Original database
console.log(childSession.database); // 'other_db'

// Clean up when done
await childSession.reset();
```

### `.closeSession()` {#closesession}

Closes the current session and disposes of it. After this method is called, the session cannot be used again, and [`isValid`](#isvalid) will return `false`.

```ts title="Method Syntax"
session.closeSession()
```

#### Returns
`Promise<void>` - Resolves when the session is closed

#### Example
```ts
await session.closeSession();
console.log(session.isValid); // false
```

## Transaction methods

### `.beginTransaction()` {#begintransaction}

Create a new transaction scoped to the current session. Transactions allow you to execute multiple queries atomically.

Call [`commit()`](/docs/reference/javascript/api/core/surreal-transaction.md#commit) on the transaction to apply changes, or [`cancel()`](/docs/reference/javascript/api/core/surreal-transaction.md#cancel) to discard them.

```ts title="Method Syntax"
session.beginTransaction()
```

#### Returns
`Promise<SurrealTransaction>` - A new [`SurrealTransaction`](/docs/reference/javascript/api/core/surreal-transaction.md) instance

#### Example
```ts
// Start a transaction
const txn = await session.beginTransaction();

try {
    // Execute queries within the transaction
    await txn.create(new RecordId('users', 'john'), {
        content: { name: 'John Doe', email: 'john@example.com' }
    });
    
    await txn.create(new RecordId('posts', '1'), {
                content: { author: new RecordId('users', 'john'),
            title: 'Hello' }
    });
    
    // Commit all changes atomically
    await txn.commit();
    console.log('Transaction committed successfully');
} catch (error) {
    // Roll back on error
    await txn.cancel();
    console.error('Transaction cancelled:', error);
}
```

## Session configuration methods

### `.use()` {#use}

Switch to the specified namespace and/or database for this session.

Leaving the namespace or database `undefined` will leave the current value unchanged, while passing `null` will unset the selected namespace or database.

```ts title="Method Syntax"
session.use(what)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>what</code> <label label="optional" /></td>
            <td><code>Nullable&lt;<a href="/docs/reference/javascript/api/types/#namespacedatabase">NamespaceDatabase</a>&gt;</code></td>
            <td>Object specifying namespace and/or database to switch to. If omitted, returns the current namespace and database.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`Promise<NamespaceDatabase>`](/docs/reference/javascript/api/types/#namespacedatabase) - The current or newly selected namespace and database

#### Examples

```ts title="Switch Both Namespace and Database"
await session.use({ 
    namespace: 'production', 
    database: 'main' 
});
```

```ts title="Switch Only Database"
await session.use({ 
    database: 'analytics' 
});
// Namespace remains unchanged
```

```ts title="Unset Database"
await session.use({ 
    database: null 
});
// Database is now undefined
```

### `.set()` {#set}

Define a variable for the current session. Variables can be used in SurrealQL queries with the `$` prefix.

```ts title="Method Syntax"
session.set(variable, value)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>variable</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The name of the variable (without the $ prefix).</td>
        </tr>
        <tr>
            <td><code>value</code> <label label="required" /></td>
            <td><code>unknown</code></td>
            <td>The value to assign to the variable.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Promise<void>` - Resolves when the variable is set

#### Example
```ts
// Set a variable
await session.set('user_id', '12345');

// Use it in a query
const result = await session.query(
    'SELECT * FROM posts WHERE author = $user_id'
).collect();
```

### `.unset()` {#unset}

Remove a variable from the current session.

```ts title="Method Syntax"
session.unset(variable)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>variable</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The name of the variable to remove (without the $ prefix).</td>
        </tr>
    </tbody>
</table>

#### Returns
`Promise<void>` - Resolves when the variable is removed

#### Example
```ts
await session.unset('user_id');
```

### `.reset()` {#reset}

Resets the current session to its initial state, clearing authentication state, variables, and selected namespace/database.

For non-default sessions, this also closes and disposes of the session.

```ts title="Method Syntax"
session.reset()
```

#### Returns
`Promise<void>` - Resolves when the session is reset

#### Example
```ts
// Reset session to clean state
await session.reset();

// Session is now cleared
console.log(session.namespace); // undefined
console.log(session.accessToken); // undefined
console.log(session.parameters); // {}
```

## Authentication methods

### `.signup()` {#signup}

Sign up a new record user to the SurrealDB instance.

> [!NOTE]
> When this method is called, the `authentication` property passed to [`connect()`](/docs/reference/javascript/api/core/surreal.md#connect) will be ignored. You are responsible for handling session invalidation by listening to the [`auth`](#event-auth) event.

```ts title="Method Syntax"
session.signup(auth)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>auth</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#accessrecordauth">AccessRecordAuth</a></code></td>
            <td>The authentication details including access method and record data.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`Promise<Tokens>`](/docs/reference/javascript/api/types/#tokens) - The authentication tokens (access and refresh tokens)

#### Example
```ts
const tokens = await session.signup({
    namespace: 'my_namespace',
    database: 'my_database',
    access: 'user_access',
    variables: {
        email: 'user@example.com',
        password: 'secure_password',
        name: 'John Doe'
    }
});

console.log('Access token:', tokens.access);
console.log('Refresh token:', tokens.refresh);
```

### `.signin()` {#signin}

Sign in to the SurrealDB instance with authentication credentials.

> [!NOTE]
> When this method is called, the `authentication` property passed to [`connect()`](/docs/reference/javascript/api/core/surreal.md#connect) will be ignored. You are responsible for handling session invalidation by listening to the [`auth`](#event-auth) event.

```ts title="Method Syntax"
session.signin(auth)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>auth</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#anyauth">AnyAuth</a></code></td>
            <td>Authentication details (system user, record user, or access method).</td>
        </tr>
    </tbody>
</table>

#### Returns
[`Promise<Tokens>`](/docs/reference/javascript/api/types/#tokens) - The authentication tokens

#### Examples

```ts title="System User Authentication"
const tokens = await session.signin({
    username: 'root',
    password: 'secret'
});
```

```ts title="Record User Authentication"
const tokens = await session.signin({
    namespace: 'my_namespace',
    database: 'my_database',
    access: 'user_access',
    variables: {
        email: 'user@example.com',
        password: 'secure_password'
    }
});
```

### `.authenticate()` {#authenticate}

Authenticate the session using an existing access token or access and refresh token combination.

When authenticating with a refresh token, a new refresh token will be issued and returned.

> [!NOTE]
> When this method is called, the `authentication` property passed to [`connect()`](/docs/reference/javascript/api/core/surreal.md#connect) will be ignored. You are responsible for handling session invalidation by listening to the [`auth`](#event-auth) event.

```ts title="Method Syntax"
session.authenticate(token)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>token</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/types/#token">Token</a> | <a href="/docs/reference/javascript/api/types/#tokens">Tokens</a></code></td>
            <td>The access token or tokens object with access and refresh tokens.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`Promise<Tokens>`](/docs/reference/javascript/api/types/#tokens) - The authentication tokens (may include new refresh token)

#### Examples

```ts title="Authenticate with Access Token"
await session.authenticate(accessToken);
```

```ts title="Authenticate with Refresh Token"
const newTokens = await session.authenticate({
    access: oldAccessToken,
    refresh: refreshToken
});

// Store new tokens
console.log('New access token:', newTokens.access);
console.log('New refresh token:', newTokens.refresh);
```

### `.invalidate()` {#invalidate}

Invalidate the authentication for the current session, signing the user out.

```ts title="Method Syntax"
session.invalidate()
```

#### Returns
`Promise<void>` - Resolves when authentication is invalidated

#### Example
```ts
await session.invalidate();
console.log('User signed out');
```

## Events

The `SurrealSession` class emits events that you can subscribe to for tracking session state changes.

### `auth` {#event-auth}

Emitted when the authentication state changes for this session.

**Payload:** `[tokens:` [`Tokens`](/docs/reference/javascript/api/types/#tokens) `| null]` - The new authentication tokens, or `null` if invalidated

**Example:**
```ts
session.subscribe('auth', (tokens) => {
    if (tokens) {
        console.log('Authenticated with token:', tokens.access);
    } else {
        console.log('Authentication invalidated');
    }
});
```

### `using` {#event-using}

Emitted when the namespace or database changes for this session.

**Payload:** `[using:` [`NamespaceDatabase`](/docs/reference/javascript/api/types/#namespacedatabase)`]` - Object containing the new namespace and database

**Example:**
```ts
session.subscribe('using', (using) => {
    console.log('Now using:', using.namespace, '/', using.database);
});
```

### `.subscribe()` {#subscribe}

Subscribe to session events.

```ts title="Method Syntax"
session.subscribe(event, listener)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>event</code> <label label="required" /></td>
            <td><code>keyof <a href="/docs/reference/javascript/api/types/#sessionevents">SessionEvents</a></code></td>
            <td>The event name to subscribe to (<code>"auth"</code> or <code>"using"</code>).</td>
        </tr>
        <tr>
            <td><code>listener</code> <label label="required" /></td>
            <td><code>Function</code></td>
            <td>Callback function invoked when the event is emitted.</td>
        </tr>
    </tbody>
</table>

#### Returns
`() => void` - An unsubscribe function

#### Example
```ts
const unsubscribe = session.subscribe('auth', (tokens) => {
    console.log('Auth changed:', tokens);
});

// Later, unsubscribe
unsubscribe();
```

## Inherited methods

As `SurrealSession` extends [`SurrealQueryable`](/docs/reference/javascript/api/core/surreal-queryable.md), it inherits all query execution methods:

- [`query()`](/docs/reference/javascript/api/core/surreal-queryable.md#query) - Execute raw SurrealQL
- [`select()`](/docs/reference/javascript/api/core/surreal-queryable.md#select) - Select records
- [`create()`](/docs/reference/javascript/api/core/surreal-queryable.md#create) - Create records
- [`insert()`](/docs/reference/javascript/api/core/surreal-queryable.md#insert) - Insert records
- [`update()`](/docs/reference/javascript/api/core/surreal-queryable.md#update) - Update records
- [`upsert()`](/docs/reference/javascript/api/core/surreal-queryable.md#upsert) - Upsert records
- [`delete()`](/docs/reference/javascript/api/core/surreal-queryable.md#delete) - Delete records
- [`relate()`](/docs/reference/javascript/api/core/surreal-queryable.md#relate) - Create graph relationships
- [`live()`](/docs/reference/javascript/api/core/surreal-queryable.md#live) - Subscribe to live queries
- [`liveOf()`](/docs/reference/javascript/api/core/surreal-queryable.md#liveof) - Subscribe to existing live queries
- [`run()`](/docs/reference/javascript/api/core/surreal-queryable.md#run) - Execute functions
- [`auth()`](/docs/reference/javascript/api/core/surreal-queryable.md#auth) - Get authenticated record user
- [`api()`](/docs/reference/javascript/api/core/surreal-queryable.md#api) - Access user-defined APIs

## Async disposal

### `[Symbol.asyncDispose]()` {#symbol-asyncdispose}

Supports the [async disposal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncDispose) protocol, allowing sessions to be used with `await using` for automatic cleanup.

```ts title="Method Syntax"
session[Symbol.asyncDispose]()
```

#### Returns
`Promise<void>` - Resolves when the session is disposed

#### Example
```ts
{
    await using session = await db.newSession();
    await session.use({ namespace: 'main', database: 'main' });
    const users = await session.select('users');
}
// Session is automatically disposed when leaving the block
```

## Complete example

```ts
import { Surreal } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Use the default session
await db.use({ namespace: 'main', database: 'main' });

// Sign in
await db.signin({
    username: 'root',
    password: 'secret'
});

// Create an isolated session
const session = await db.newSession();

// Configure the session
await session.use({ 
    namespace: 'main', 
    database: 'main' 
});

// Set session variables
await session.set('user_role', 'admin');

// Subscribe to session events
session.subscribe('auth', (tokens) => {
        console.log('Session auth changed:',
        tokens ? 'authenticated' : 'signed out');
});

session.subscribe('using', (using) => {
    console.log('Using:', using);
});

// Authenticate as a record user
const tokens = await session.signin({
    namespace: 'main',
    database: 'main',
    access: 'user_access',
    variables: {
        email: 'user@example.com',
        password: 'password'
    }
});

// Execute queries in the session context
const users = await session.select('users');

// Start a transaction
const txn = await session.beginTransaction();
await txn.create('logs:1',
    { content: { message: 'User logged in' } });
await txn.commit();

// Fork the session to create an isolated copy
const childSession = await session.forkSession();
await childSession.use({ database: 'analytics' });

// Clean up
await childSession.reset();
await session.closeSession();
```

## See also

- [Surreal](/docs/reference/javascript/api/core/surreal.md) - Main connection class
- [SurrealQueryable](/docs/reference/javascript/api/core/surreal-queryable.md) - Query execution methods
- [SurrealTransaction](/docs/reference/javascript/api/core/surreal-transaction.md) - Transaction support
- [Authentication Types](/docs/reference/javascript/api/types/#anyauth) - Authentication type definitions

---

Source: https://surrealdb.com/docs/reference/javascript/api/core/surreal-transaction

# SurrealTransaction

The SurrealTransaction class provides atomic transaction support for executing multiple queries.

The `SurrealTransaction` class provides transaction support for executing multiple queries atomically. When all desired queries have been executed, call [`commit()`](#commit) to apply the changes to the database, or [`cancel()`](#cancel) to discard them.

Transactions are created using the [`beginTransaction()`](/docs/reference/javascript/api/core/surreal-session.md#begintransaction) method on a [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md) instance.

**Extends:** [`SurrealQueryable`](/docs/reference/javascript/api/core/surreal-queryable.md)

**Source:** [api/transaction.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/api/transaction.ts)

## Overview

Transactions ensure that a group of operations are executed atomically - either all succeed or all fail. This is essential for maintaining data consistency when performing related operations.

```ts
const txn = await db.beginTransaction();

try {
    // All operations succeed or fail together
    await txn.create(recordId1).content(data1);
    await txn.create(recordId2).content(data2);
    await txn.commit(); // Apply all changes
} catch (error) {
    await txn.cancel(); // Discard all changes
}
```

## Constructor

The constructor is not called directly. Use [`SurrealSession.beginTransaction()`](/docs/reference/javascript/api/core/surreal-session.md#begintransaction) to create transactions.

## Transaction methods

### `.commit()` {#commit}

Commit the transaction to the database, applying all changes made within the transaction scope.

After committing, the transaction cannot be used again.

```ts title="Method Syntax"
txn.commit()
```

#### Returns
`Promise<void>` - Resolves when the transaction is committed

#### Example
```ts
const txn = await db.beginTransaction();

await txn.create(new RecordId('users', 'alice'))
    .content({ name: 'Alice', email: 'alice@example.com' });

await txn.create(new RecordId('users', 'bob'))
    .content({ name: 'Bob', email: 'bob@example.com' });

// Commit both creates atomically
await txn.commit();
console.log('Transaction committed successfully');
```

### `.cancel()` {#cancel}

Cancel and discard all changes made in the transaction.

After cancelling, the transaction cannot be used again.

```ts title="Method Syntax"
txn.cancel()
```

#### Returns
`Promise<void>` - Resolves when the transaction is cancelled

#### Example
```ts
const txn = await db.beginTransaction();

try {
    await txn.create(new RecordId('users', 'alice'))
        .content({ name: 'Alice' });
    
    // Something goes wrong
    throw new Error('Validation failed');
} catch (error) {
    // Discard all changes
    await txn.cancel();
    console.log('Transaction cancelled:', error.message);
}
```

## Inherited methods

As `SurrealTransaction` extends [`SurrealQueryable`](/docs/reference/javascript/api/core/surreal-queryable.md), it inherits all query execution methods. All queries executed on a transaction instance are part of the transaction scope:

### Query methods
- [`query()`](/docs/reference/javascript/api/core/surreal-queryable.md#query) - Execute raw SurrealQL
- [`select()`](/docs/reference/javascript/api/core/surreal-queryable.md#select) - Select records
- [`create()`](/docs/reference/javascript/api/core/surreal-queryable.md#create) - Create records
- [`insert()`](/docs/reference/javascript/api/core/surreal-queryable.md#insert) - Insert records
- [`update()`](/docs/reference/javascript/api/core/surreal-queryable.md#update) - Update records
- [`upsert()`](/docs/reference/javascript/api/core/surreal-queryable.md#upsert) - Upsert records
- [`delete()`](/docs/reference/javascript/api/core/surreal-queryable.md#delete) - Delete records
- [`relate()`](/docs/reference/javascript/api/core/surreal-queryable.md#relate) - Create graph relationships
- [`live()`](/docs/reference/javascript/api/core/surreal-queryable.md#live) - Subscribe to live queries
- [`liveOf()`](/docs/reference/javascript/api/core/surreal-queryable.md#liveof) - Subscribe to existing live queries
- [`run()`](/docs/reference/javascript/api/core/surreal-queryable.md#run) - Execute functions
- [`auth()`](/docs/reference/javascript/api/core/surreal-queryable.md#auth) - Get authenticated record user
- [`api()`](/docs/reference/javascript/api/core/surreal-queryable.md#api) - Access user-defined APIs

All of these operations are executed within the transaction context and will be committed or cancelled together.

## Complete examples

### Basic transaction

```ts
import { Surreal, RecordId } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');
await db.use({ namespace: 'main', database: 'main' });

// Start a transaction
const txn = await db.beginTransaction();

try {
    // Create a user
    const user = await txn.create(new RecordId('users', 'john'))
        .content({
            name: 'John Doe',
            email: 'john@example.com',
            balance: 1000
        });
    
    // Create a purchase
    const purchase = await txn.create(new RecordId('purchases', 'purchase1'))
        .content({
            user: new RecordId('users', 'john'),
            amount: 100,
            item: 'Widget'
        });
    
    // Update user balance
    await txn.update(new RecordId('users', 'john'))
        .merge({ balance: 900 });
    
    // Commit all changes atomically
    await txn.commit();
    console.log('Purchase completed successfully');
} catch (error) {
    // If anything fails, cancel the transaction
    await txn.cancel();
    console.error('Purchase failed:', error);
}
```

### Money transfer transaction

```ts
async function transferMoney(
    db: Surreal,
    fromUser: string,
    toUser: string,
    amount: number
) {
    const txn = await db.beginTransaction();
    
    try {
        // Get current balances
        const from = await txn.select(new RecordId('users', fromUser));
        const to = await txn.select(new RecordId('users', toUser));
        
        if (!from || !to) {
            throw new Error('User not found');
        }
        
        if (from.balance < amount) {
            throw new Error('Insufficient funds');
        }
        
        // Update both balances
        await txn.update(new RecordId('users', fromUser))
            .merge({ balance: from.balance - amount });
        
        await txn.update(new RecordId('users', toUser))
            .merge({ balance: to.balance + amount });
        
        // Create transaction record
        await txn.create(new RecordId('transactions', crypto.randomUUID()))
            .content({
                from: new RecordId('users', fromUser),
                to: new RecordId('users', toUser),
                amount,
                timestamp: new Date()
            });
        
        // Commit all changes
        await txn.commit();
        console.log(`Transferred ${amount} from ${fromUser} to ${toUser}`);
        return true;
    } catch (error) {
        await txn.cancel();
        console.error('Transfer failed:', error);
        return false;
    }
}

// Use the function
await transferMoney(db, 'alice', 'bob', 50);
```

### Complex transaction with graph relationships

```ts
async function createUserWithFollows(
    db: Surreal,
    userData: { name: string; email: string },
    followUserIds: string[]
) {
    const txn = await db.beginTransaction();
    
    try {
        // Create the user
        const userId = crypto.randomUUID();
        const user = await txn.create(new RecordId('users', userId))
            .content(userData);
        
        // Create follow relationships
        for (const followId of followUserIds) {
            await txn.relate(
                new RecordId('users', userId),
                new Table('follows'),
                new RecordId('users', followId),
                { followedAt: new Date() }
            );
        }
        
        // Create initial activity log
        await txn.create(new RecordId('activity', crypto.randomUUID()))
            .content({
                user: new RecordId('users', userId),
                action: 'user_created',
                timestamp: new Date()
            });
        
        // Commit everything
        await txn.commit();
        console.log('User and relationships created successfully');
        return user;
    } catch (error) {
        await txn.cancel();
        console.error('Failed to create user:', error);
        throw error;
    }
}
```

### Transaction with error handling

```ts
async function atomicBulkOperation(db: Surreal, records: any[]) {
    const txn = await db.beginTransaction();
    const results: any[] = [];
    const errors: any[] = [];
    
    try {
        for (const record of records) {
            try {
                const result = await txn.create(new Table('items'))
                    .content(record);
                results.push(result);
            } catch (error) {
                errors.push({ record, error });
            }
        }
        
        // Only commit if all succeeded
        if (errors.length === 0) {
            await txn.commit();
            console.log(`Successfully created ${results.length} records`);
            return { success: true, results };
        } else {
            await txn.cancel();
            console.log(`Failed with ${errors.length} errors, rolled back`);
            return { success: false, errors };
        }
    } catch (error) {
        await txn.cancel();
        console.error('Transaction failed:', error);
        return { success: false, errors: [error] };
    }
}
```

## Best practices

### 1. Always handle errors

Always use try-catch blocks to ensure transactions are properly cancelled on errors:

```ts
const txn = await db.beginTransaction();
try {
    // Operations
    await txn.commit();
} catch (error) {
    await txn.cancel(); // Important!
    throw error;
}
```

### 2. Keep transactions short

Execute transactions quickly to avoid locking resources:

```ts
// Good: Short transaction
const txn = await db.beginTransaction();
await txn.update(recordId).merge(data);
await txn.commit();

// Avoid: Long-running operations in transactions
const txn = await db.beginTransaction();
await expensiveExternalApiCall(); // Bad!
await txn.update(recordId).merge(data);
await txn.commit();
```

### 3. Don't reuse transactions

Once a transaction is committed or cancelled, create a new one for subsequent operations:

```ts
const txn1 = await db.beginTransaction();
await txn1.create(record1);
await txn1.commit();

// Create a new transaction for next operation
const txn2 = await db.beginTransaction();
await txn2.create(record2);
await txn2.commit();
```

### 4. Validate before transaction

Perform validation before starting a transaction when possible:

```ts
// Validate first
if (!isValidEmail(email)) {
    throw new Error('Invalid email');
}

// Then transact
const txn = await db.beginTransaction();
// ... operations
await txn.commit();
```

## See also

- [SurrealSession.beginTransaction()](/docs/reference/javascript/api/core/surreal-session.md#begintransaction) - Creating transactions
- [SurrealQueryable](/docs/reference/javascript/api/core/surreal-queryable.md) - Available query methods
- [Query builders](/docs/reference/javascript/api/queries/) - Query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/errors

# Errors

Error classes for handling different types of failures in the SDK.

The SDK defines specific error classes for different failure scenarios. All error classes extend the base `SurrealError` class, allowing you to catch and handle specific error types.

## Base error

### `SurrealError` {#surrealerror}

Base class for all SDK errors.

**Extends:** `Error`

**Example:**
```ts
try {
    await db.select(new RecordId('users', 'john'));
} catch (error) {
    if (error instanceof SurrealError) {
        console.error('SDK error:', error.message);
    }
}
```

## Connection errors

### `ConnectionUnavailableError` {#connectionunavailableerror}

Thrown when attempting an operation without an active connection.

**Message:** `"You must be connected to a SurrealDB instance before performing this operation"`

**Example:**
```ts
const db = new Surreal();

try {
    await db.select(new Table('users')); // Not connected
} catch (error) {
    if (error instanceof ConnectionUnavailableError) {
        console.error('Not connected to database');
        await db.connect('ws://localhost:8000');
    }
}
```

---

### `HttpConnectionError` {#httpconnectionerror}

Thrown when an HTTP connection fails.

**Properties:**
- `status` (number) - HTTP status code
- `statusText` (string) - HTTP status text
- `buffer` (ArrayBuffer) - Response buffer

**Example:**
```ts
try {
    await db.connect('http://localhost:8000/rpc');
} catch (error) {
    if (error instanceof HttpConnectionError) {
        console.error(`HTTP ${error.status}: ${error.statusText}`);
    }
}
```

---

### `CallTerminatedError` {#callterminatederror}

Thrown when a call is terminated because the connection was closed.

**Message:** `"The call has been terminated because the connection was closed"`

**Example:**
```ts
try {
    const promise = db.query('SELECT SLEEP(10s)');
    await db.close(); // Close connection while query running
    await promise; // Will throw CallTerminatedError
} catch (error) {
    if (error instanceof CallTerminatedError) {
        console.error('Connection closed during operation');
    }
}
```

---

### `UnexpectedConnectionError` {#unexpectedconnectionerror}

Thrown when an unexpected connection error occurs.

**Properties:**
- `cause` (unknown) - The underlying error cause

**Example:**
```ts
try {
    await db.connect('ws://invalid:8000');
} catch (error) {
    if (error instanceof UnexpectedConnectionError) {
        console.error('Connection error:', error.cause);
    }
}
```

---

### `UnsupportedEngineError` {#unsupportedengineerror}

Thrown when attempting to use an unsupported or unconfigured engine.

**Properties:**
- `engine` (string) - The unsupported engine name

**Example:**
```ts
try {
    await db.connect('custom://localhost:8000');
} catch (error) {
    if (error instanceof UnsupportedEngineError) {
        console.error(`Engine "${error.engine}" is not supported`);
    }
}
```

## Reconnection errors

### `ReconnectExhaustionError` {#reconnectexhaustionerror}

Thrown when reconnect attempts have been exhausted.

**Message:** `"The reconnect attempts have been exhausted"`

**Example:**
```ts
db.subscribe('error', (error) => {
    if (error instanceof ReconnectExhaustionError) {
        console.error('Failed to reconnect after multiple attempts');
    }
});
```

---

### `ReconnectIterationError` {#reconnectiterationerror}

Thrown when a reconnect iterator fails to iterate.

**Message:** `"The reconnect iterator failed to iterate"`

## Server errors

Server errors represent structured errors returned by the SurrealDB server. They form a class hierarchy rooted at `ServerError`, which replaces the former `ResponseError` class.

### `ErrorKind` {#errorkind}

Known error kinds returned by the SurrealDB server. Use these constants for matching against `ServerError.kind`.

```ts
const ErrorKind = {
    Validation: "Validation",
    Configuration: "Configuration",
    Thrown: "Thrown",
    Query: "Query",
    Serialization: "Serialization",
    NotAllowed: "NotAllowed",
    NotFound: "NotFound",
    AlreadyExists: "AlreadyExists",
    Connection: "Connection",
    Internal: "Internal",
} as const;
```

---

### `ServerError` {#servererror}

Base class for all errors originating from the SurrealDB server. Each error carries structured information about the failure.

**Extends:** `SurrealError`

**Properties:**
- `kind` (string) - The error category (e.g. `"NotAllowed"`, `"NotFound"`)
- `code` (number) - Legacy JSON-RPC numeric error code (0 when unavailable)
- `details` (ErrorDetail | undefined) - Kind-specific structured details from the server
- `cause` (ServerError | undefined) - Optional inner `ServerError` forming a recursive error chain

**Example:**
```ts
try {
    await db.query('INVALID QUERY');
} catch (error) {
    if (error instanceof ServerError) {
        console.error(`Server error [${error.kind}]: ${error.message}`);
        if (error.cause) {
            console.error('Caused by:', error.cause.message);
        }
    }
}
```

---

### `ValidationError` {#validationerror}

Thrown on validation failures such as parse errors, invalid requests, or invalid parameters.

**Extends:** `ServerError` (kind: `"Validation"`)

**Convenience getters:**
- `isParseError` (boolean) - True if this is a SurrealQL parse error
- `parameterName` (string | undefined) - The name of the invalid parameter, if applicable

**Example:**
```ts
try {
    await db.query('SELEC * FROM users'); // Typo
} catch (error) {
    if (error instanceof ValidationError) {
        if (error.isParseError) {
            console.error('SurrealQL syntax error:', error.message);
        }
    }
}
```

---

### `ConfigurationError` {#configurationerror}

Thrown when a feature or configuration is not supported by the server (e.g. live queries, GraphQL).

**Extends:** `ServerError` (kind: `"Configuration"`)

**Convenience getters:**
- `isLiveQueryNotSupported` (boolean) - True if live queries are not supported by the server configuration

---

### `ThrownError` {#thrownerror}

Thrown when a user-thrown error is raised via `THROW` in SurrealQL.

**Extends:** `ServerError` (kind: `"Thrown"`)

**Example:**
```ts
try {
    await db.query('THROW "something went wrong"');
} catch (error) {
    if (error instanceof ThrownError) {
        console.error('SurrealQL THROW:', error.message);
    }
}
```

---

### `QueryError` {#queryerror}

Thrown on query execution failures such as timeouts, cancellations, or skipped statements.

**Extends:** `ServerError` (kind: `"Query"`)

**Convenience getters:**
- `isNotExecuted` (boolean) - True if the query was not executed (e.g. due to a prior error in the batch)
- `isTimedOut` (boolean) - True if the query timed out
- `isCancelled` (boolean) - True if the query was cancelled
- `timeout` (`{ secs: number, nanos: number } | undefined`) - The timeout duration, if this is a timeout error

**Example:**
```ts
try {
    await db.query('SELECT * FROM heavy_table TIMEOUT 1s');
} catch (error) {
    if (error instanceof QueryError) {
        if (error.isTimedOut) {
            console.error('Query timed out after', error.timeout?.secs, 'seconds');
        } else if (error.isNotExecuted) {
            console.error('Query was not executed');
        }
    }
}
```

---

### `SerializationError` {#serializationerror}

Thrown on serialisation or deserialisation failures.

**Extends:** `ServerError` (kind: `"Serialization"`)

**Convenience getters:**
- `isDeserialization` (boolean) - True if this is a deserialisation error (as opposed to serialisation)

---

### `NotAllowedError` {#notallowederror}

Thrown when a permission is denied, a method is not allowed, or a function/scripting call is blocked.

**Extends:** `ServerError` (kind: `"NotAllowed"`)

**Convenience getters:**
- `isTokenExpired` (boolean) - True if the auth token has expired
- `isInvalidAuth` (boolean) - True if authentication credentials are invalid
- `isScriptingBlocked` (boolean) - True if scripting is blocked
- `methodName` (string | undefined) - The method name that is not allowed, if applicable
- `functionName` (string | undefined) - The function name that is not allowed, if applicable

**Example:**
```ts
try {
    await db.query('SELECT * FROM protected_table');
} catch (error) {
    if (error instanceof NotAllowedError) {
        if (error.isTokenExpired) {
            console.error('Token expired, re-authenticate');
        } else if (error.isInvalidAuth) {
            console.error('Invalid credentials');
        }
    }
}
```

---

### `NotFoundError` {#notfounderror}

Thrown when a resource is not found (table, record, namespace, method, etc.).

**Extends:** `ServerError` (kind: `"NotFound"`)

**Convenience getters:**
- `tableName` (string | undefined) - The table name that was not found
- `recordId` (string | undefined) - The record ID that was not found
- `methodName` (string | undefined) - The RPC method name that was not found
- `namespaceName` (string | undefined) - The namespace name that was not found
- `databaseName` (string | undefined) - The database name that was not found

**Example:**
```ts
try {
    await db.query('SELECT * FROM nonexistent');
} catch (error) {
    if (error instanceof NotFoundError) {
        if (error.tableName) {
            console.error(`Table "${error.tableName}" does not exist`);
        } else if (error.recordId) {
            console.error(`Record "${error.recordId}" not found`);
        }
    }
}
```

---

### `AlreadyExistsError` {#alreadyexistserror}

Thrown when a duplicate resource is encountered (record, table, namespace, etc.).

**Extends:** `ServerError` (kind: `"AlreadyExists"`)

**Convenience getters:**
- `recordId` (string | undefined) - The record ID that already exists
- `tableName` (string | undefined) - The table name that already exists

**Example:**
```ts
try {
    await db.create(new RecordId('users', 'john'), { name: 'John' });
} catch (error) {
    if (error instanceof AlreadyExistsError) {
        if (error.recordId) {
            console.error(`Record "${error.recordId}" already exists`);
        }
    }
}
```

---

### `InternalError` {#internalerror}

Thrown on unexpected or unknown internal server errors. Also used as the fallback for unrecognised `kind` strings from newer servers.

**Extends:** `ServerError` (kind: `"Internal"`)

---

### `ResponseError` (Deprecated) {#responseerror}

`ResponseError` is a deprecated alias for [`ServerError`](#servererror). It exists for backward compatibility.

```ts
// ResponseError is the same as ServerError
import { ResponseError } from 'surrealdb';
// Equivalent to:
import { ServerError } from 'surrealdb';
```

---

### `UnexpectedServerResponseError` {#unexpectedserverresponseerror}

Thrown when the server returns a response in an unexpected format.

**Properties:**
- `response` (unknown) - The unexpected response received

**Example:**
```ts
try {
    await db.query(complexQuery);
} catch (error) {
    if (error instanceof UnexpectedServerResponseError) {
        console.error('Unexpected response:', error.response);
    }
}
```

## Authentication errors

### `AuthenticationError` {#authenticationerror}

Thrown when authentication fails.

**Message:** `"Authentication did not succeed"`

**Properties:**
- `cause` (unknown) - The underlying error cause

**Example:**
```ts
try {
    await db.signin({
        username: 'user',
        password: 'wrongpassword'
    });
} catch (error) {
    if (error instanceof AuthenticationError) {
        console.error('Authentication failed:', error.cause);
    }
}
```

---

### `MissingNamespaceDatabaseError` {#missingnamespacedatabaseerror}

Thrown when a namespace and/or database is required but not selected.

**Message:** `"There is no namespace and/or database selected"`

**Example:**
```ts
const db = new Surreal();
await db.connect('ws://localhost:8000');

try {
    await db.select(new Table('users')); // No namespace/database set
} catch (error) {
    if (error instanceof MissingNamespaceDatabaseError) {
        await db.use({ namespace: 'main', database: 'main' });
    }
}
```

## Live query errors

### `LiveSubscriptionError` {#livesubscriptionerror}

Thrown when a live subscription fails to listen.

**Constructor:** `new LiveSubscriptionError(messageOrCause?: string | unknown)`

When called with a string, it is used as the error message. When called with any other value (or no argument), the default message `"Live subscription failed to listen"` is used and the argument is set as `cause`.

**Example:**
```ts
try {
    const subscription = await db.live(new Table('users'));
} catch (error) {
    if (error instanceof LiveSubscriptionError) {
        console.error('Live query failed:', error.cause);
    }
}
```

## Version errors

### `UnsupportedVersionError` {#unsupportedversionerror}

Thrown when the connected SurrealDB version is not supported by the SDK.

**Properties:**
- `version` (string) - The unsupported version
- `minimum` (string) - Minimum supported version (inclusive)
- `maximum` (string) - Maximum supported version (exclusive)

**Example:**
```ts
try {
    await db.connect('ws://localhost:8000', {
        versionCheck: true
    });
} catch (error) {
    if (error instanceof UnsupportedVersionError) {
        console.error(
            `Version ${error.version} not supported. ` +
            `Requires: >= ${error.minimum} < ${error.maximum}`
        );
    }
}
```

## Expression errors

### `ExpressionError` {#expressionerror}

Thrown when a SurrealQL expression fails to compile or execute.

**Constructor:** `new ExpressionError(messageOrCause?: string | unknown)`

When called with a string, it is used as the error message. When called with any other value (or no argument), the default message `"Failed to parse invalid expression"` is used and the argument is set as `cause`.

**Example:**
```ts
try {
    const invalid = expr(() => {
        throw new Error('Invalid expression');
    });
} catch (error) {
    if (error instanceof ExpressionError) {
        console.error('Expression error:', error.message);
    }
}
```

## Event errors

### `PublishError` {#publisherror}

Thrown when one or more event subscribers throw an error during publication.

**Properties:**
- `causes` (unknown[]) - The errors thrown by subscribers
- `message` (string) - Summary message including the cause messages

**Example:**
```ts
db.subscribe('auth', () => {
    throw new Error('Handler failed');
});
// When the event fires, a PublishError may be emitted
```

## Date and time errors

### `InvalidDateError` {#invaliddateerror}

Thrown when a parsed date or datetime is invalid.

**Constructor:** `new InvalidDateError(dateOrMessage: Date | string)`

When called with a `Date`, the message is `"The provided date is invalid: <date>"`. When called with a string, the string is used directly as the error message.

**Example:**
```ts
try {
    new DateTime('not-a-date');
} catch (error) {
    if (error instanceof InvalidDateError) {
        console.error('Invalid date:', error.message);
    }
}
```

## Feature errors

### `UnsupportedFeatureError` {#unsupportedfeatureerror}

Thrown when attempting to use a feature not supported by the configured engine.

**Properties:**
- `feature` (Feature) - The unsupported feature

**Example:**
```ts
try {
    await db.live(new Table('users')); // Not supported by engine
} catch (error) {
    if (error instanceof UnsupportedFeatureError) {
        console.error(`Feature "${error.feature.name}" not supported`);
    }
}
```

---

### `UnavailableFeatureError` {#unavailablefeatureerror}

Thrown when attempting to use a feature not available in the connected SurrealDB version.

**Properties:**
- `feature` (Feature) - The unavailable feature
- `version` (string) - The connected SurrealDB version

**Example:**
```ts
try {
    await db.connect('http://localhost:8000/rpc');
    await db.live(new Table('users'));
} catch (error) {
    if (error instanceof UnavailableFeatureError) {
        console.error(`Feature "${error.feature.name}" not available in version ${error.version}`);
    }
}
```

## API errors

### `SchemaValidationError` {#schemavalidationerror}

Thrown when a value does not match the [Standard Schema](https://standardschema.dev) it was checked against with `.args()`, `.returns()`, `.request()` or `.response()`. This is a client-side check, separate from the server's `ValidationError`.

**Properties:**
- `subject` (string) - What was validated, such as `arguments for fn::add`
- `issues` (ReadonlyArray) - Every issue the schema reported, each with a `message` and an optional `path`

### `UnsuccessfulApiError` {#unsuccessfulapierror}

Thrown when a user-defined API call fails.

**Properties:**
- `path` (string) - The API path that was invoked
- `method` (string) - The HTTP method used
- `response` (ApiResponse) - The error response from the API (includes `body?`, `headers?`, `status?`)
- `message` (string) - Human-readable message (inherited from Error; includes path, method, and status)

**Example:**
```ts
try {
    await db.api().get('/users/999');
} catch (error) {
    if (error instanceof UnsuccessfulApiError) {
        console.error(`API error: ${error.message}`);
        console.error(`Status: ${error.response.status}`);
    }
}
```

## Session errors

### `InvalidSessionError` {#invalidsessionerror}

Thrown when attempting to use an invalid or disposed session.

**Properties:**
- `session` (Session) - The invalid session identifier

**Example:**
```ts
const session = await db.newSession();
await session.closeSession();

try {
    await session.select(new Table('users')); // Session closed
} catch (error) {
    if (error instanceof InvalidSessionError) {
        console.error('Session is no longer valid');
    }
}
```

## Value validation errors

### `InvalidRecordIdError` {#invalidrecordiderror}

Thrown when a `RecordId` or `RecordIdRange` is constructed with invalid parts.

**Example:**
```ts
try {
    new RecordId('', 'id'); // Invalid table name
} catch (error) {
    if (error instanceof InvalidRecordIdError) {
        console.error('Invalid record ID:', error.message);
    }
}
```

---

### `InvalidDurationError` {#invaliddurationerror}

Thrown when a `Duration` string cannot be parsed or a duration operation is invalid.

**Example:**
```ts
try {
    new Duration('not-a-duration');
} catch (error) {
    if (error instanceof InvalidDurationError) {
        console.error('Invalid duration:', error.message);
    }
}
```

---

### `InvalidDecimalError` {#invaliddecimalerror}

Thrown when a `Decimal` operation fails (e.g. division by zero, invalid input).

**Example:**
```ts
try {
    new Decimal('not-a-number');
} catch (error) {
    if (error instanceof InvalidDecimalError) {
        console.error('Invalid decimal:', error.message);
    }
}
```

---

### `InvalidTableError` {#invalidtableerror}

Thrown when a `Table` or `StringRecordId` is constructed with an invalid value.

**Example:**
```ts
try {
    new Table(''); // Empty table name
} catch (error) {
    if (error instanceof InvalidTableError) {
        console.error('Invalid table:', error.message);
    }
}
```

## Error handling patterns

### Basic error handling

```ts
try {
    const result = await db.select(new Table('users'));
} catch (error) {
    if (error instanceof SurrealError) {
        // Handle all SDK errors
        console.error('SDK error:', error.message);
    } else {
        // Handle other errors
        console.error('Unexpected error:', error);
    }
}
```

### Specific error handling

```ts
try {
    await db.connect('ws://localhost:8000');
    await db.use({ namespace: 'main', database: 'main' });
    await db.signin({ username: 'user', password: 'pass' });
} catch (error) {
    if (error instanceof ConnectionUnavailableError) {
        console.error('Cannot connect to database');
    } else if (error instanceof AuthenticationError) {
        console.error('Invalid credentials');
    } else if (error instanceof UnsupportedVersionError) {
        console.error('Database version incompatible');
    } else {
        console.error('Unknown error:', error);
    }
}
```

### Error recovery

```ts
async function executeWithRetry(fn: () => Promise<any>, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            return await fn();
        } catch (error) {
            if (error instanceof ConnectionUnavailableError) {
                // Reconnect and retry
                await db.connect('ws://localhost:8000');
                continue;
            } else if (error instanceof ServerError && attempt < maxRetries - 1) {
                // Retry on server errors
                continue;
            }
            throw error;
        }
    }
}

const result = await executeWithRetry(() => 
    db.select(new Table('users'))
);
```

### Global error handler

```ts
db.subscribe('error', (error) => {
    if (error instanceof ReconnectExhaustionError) {
        // Handle reconnection failure
        notifyUser('Connection lost. Please check your network.');
    } else if (error instanceof UnexpectedConnectionError) {
        // Log unexpected errors
        logger.error('Unexpected connection error:', error.cause);
    }
});
```

## Best practices

### 1. Catch specific errors

Handle specific error types for better error recovery:

```ts
// Good: Specific handling
try {
    await operation();
} catch (error) {
    if (error instanceof AuthenticationError) {
        redirectToLogin();
    } else if (error instanceof ConnectionUnavailableError) {
        showConnectionError();
    }
}

// Avoid: Generic handling
try {
    await operation();
} catch (error) {
    console.error(error); // Lost context
}
```

### 2. Use type guards

TypeScript type guards provide better type safety:

```ts
function isConnectionError(error: unknown): error is ConnectionUnavailableError {
    return error instanceof ConnectionUnavailableError;
}

if (isConnectionError(error)) {
    // TypeScript knows error is ConnectionUnavailableError
    await reconnect();
}
```

### 3. Log error details

Include error details in logs for debugging:

```ts
catch (error) {
    if (error instanceof ServerError) {
        logger.error('Query failed', {
            kind: error.kind,
            code: error.code,
            message: error.message,
            details: error.details,
        });
    }
}
```

### 4. Clean up on error

Ensure resources are cleaned up even when errors occur:

```ts
const session = await db.newSession();
try {
    await session.select(new Table('users'));
} finally {
    await session.closeSession(); // Always clean up
}
```

## See also

- [Core Classes](/docs/reference/javascript/api/core/) - Classes that may throw errors
- [Surreal](/docs/reference/javascript/api/core/surreal.md) - Connection methods that may throw errors
- [SurrealSession](/docs/reference/javascript/api/core/surreal-session.md) - Session methods that may throw errors

**Source:** [errors.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/errors.ts)

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries

# Query builders

The chainable builder classes returned by the SDK's query methods.

Query methods on a session or transaction return a builder rather than a plain promise. Each builder exposes chainable methods for configuring the operation, and is awaited to execute it.

## Builders

- [**SelectPromise**](/docs/reference/javascript/api/queries/select-promise.md) - Configures `SELECT` queries.
- [**CreatePromise**](/docs/reference/javascript/api/queries/create-promise.md) - Configures `CREATE` operations.
- [**UpdatePromise**](/docs/reference/javascript/api/queries/update-promise.md) - Configures `UPDATE` operations.
- [**UpsertPromise**](/docs/reference/javascript/api/queries/upsert-promise.md) - Configures `UPSERT` operations (insert or replace).
- [**InsertPromise**](/docs/reference/javascript/api/queries/insert-promise.md) - Configures `INSERT` operations.
- [**DeletePromise**](/docs/reference/javascript/api/queries/delete-promise.md) - Configures `DELETE` operations.
- [**RelatePromise**](/docs/reference/javascript/api/queries/relate-promise.md) - Configures `RELATE` operations for graph relationships.
- [**Query**](/docs/reference/javascript/api/queries/query.md) - Executes raw SurrealQL, with streaming and batch processing support.
- [**LivePromise**](/docs/reference/javascript/api/queries/live-promise.md) - Manages real-time live query subscriptions.
- [**RunPromise**](/docs/reference/javascript/api/queries/run-promise.md) - Executes SurrealDB functions and SurrealML models.
- [**ApiPromise**](/docs/reference/javascript/api/queries/api-promise.md) - Executes user-defined API calls.

## See also

- [Core classes](/docs/reference/javascript/api/core.md) - The classes these builders are created from
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) - Conceptual overview with examples
- [SurrealQL statements](/docs/reference/query-language/statements/overview.md) - The query language reference

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/api-promise

# ApiPromise

ApiPromise provides methods for executing user-defined API calls.

The `ApiPromise` class provides an interface for executing user-defined API endpoint calls. It extends `Promise`, allowing you to `await` it directly or configure the response handling.

**Returned by:** Methods on [`SurrealApi`](/docs/reference/javascript/api/core/surreal-api.md)

**Source:** [query/api.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/api.ts)

## Type parameters

- `Req` - The request body type
- `Res` - The response body type
- `V` - Boolean for value-only response (default: `false`)
- `J` - Boolean indicating if result is JSON (default: `false`)

## Configuration methods

### `.json()` {#json}

Configure the query to return the result as a JSON string.

```ts title="Method Syntax"
apiPromise.json()
```

#### Returns
`ApiPromise<Req, Res, V, true>` - Promise returning JSON string

#### Example

```ts
const jsonString = await db.api().get('/users').json();
console.log(typeof jsonString); // 'string'
```

---

### `.header()` {#header}

Append a header to the API request.

```ts title="Method Syntax"
apiPromise.header(name, value)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>name</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The header name.</td>
        </tr>
        <tr>
            <td><code>value</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The header value.</td>
        </tr>
    </tbody>
</table>

#### Returns
`ApiPromise<Req, Res, V, J>` - Chainable promise

#### Example

```ts
const result = await db.api().get('/users')
    .header('X-Custom-Header', 'value')
    .header('Authorization', 'Bearer token');
```

---

### `.value()` {#value}

Return only the response body value, without the wrapper object.

```ts title="Method Syntax"
apiPromise.value()
```

#### Returns
`ApiPromise<Req, Res, true, J>` - Promise returning only the value

#### Examples

```ts title="Without .value()"
const response = await db.api().get('/users');
console.log(response.body); // The actual data
console.log(response.status); // HTTP status
console.log(response.headers); // Response headers
```

```ts title="With .value()"
const users = await db.api().get('/users').value();
console.log(users); // Direct access to user array
```

---

### `.request()` {#request}

Check the request body against a [Standard Schema](https://standardschema.dev) before the request is sent. When validation fails, nothing is sent and the promise rejects with a [`SchemaValidationError`](/docs/reference/javascript/api/errors.md). The value the schema produces is what is sent.

```ts title="Method Syntax"
apiPromise.request(schema)
```

#### Example

```ts
await db.api().post('/users', { name: 'Ada' }).request(z.object({ name: z.string() }));
```

---

### `.response()` {#response}

Check the response body against a [Standard Schema](https://standardschema.dev) and type the promise as what the schema produces. Only the body of a successful response (status 200 to 299) is checked. When the body does not match, the promise rejects with a [`SchemaValidationError`](/docs/reference/javascript/api/errors.md).

```ts title="Method Syntax"
apiPromise.response(schema)
```

#### Example

```ts
const users = await db.api().get('/users').response(z.array(User)).value();
```

See [Validating calls](/docs/reference/javascript/concepts/validating-calls.md).

---

### `.compile()` {#compile}

Compile the query into a BoundQuery.

```ts title="Method Syntax"
apiPromise.compile()
```

#### Returns
`BoundQuery<[ApiResponse]>` - The compiled query

---

### `.stream()` {#stream}

Stream results as they are received.

```ts title="Method Syntax"
apiPromise.stream()
```

#### Returns
`AsyncIterable<Frame>` - Async iterable of response frames

---

### `.query()` {#query}

Append a URL query parameter to the API request.

```ts title="Method Syntax"
apiPromise.query(name, value)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>name</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The query parameter name.</td>
        </tr>
        <tr>
            <td><code>value</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The query parameter value.</td>
        </tr>
    </tbody>
</table>

#### Returns
`ApiPromise<Req, Res, V, J>` - A new ApiPromise with the query parameter appended

#### Example

```ts
const result = await db.api().get('/users')
    .query('filter', 'active')
    .query('sort', 'created_at')
    .value();
```

## Response structure

### Default response (without `.value()`)

```ts
interface ApiResponse<T> {
    body?: T;
    headers?: Record<string, string>;
    status?: number;
}
```

### Value response (with `.value()`)

```ts
// Returns Res directly instead of ApiResponse<Res>
```

## Complete examples

### Basic API calls

```ts
import { Surreal } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

const api = db.api();

// GET request
const users = await api.get('/users').value();

// POST request
const newUser = await api.post('/users', {
    name: 'John Doe',
    email: 'john@example.com'
}).value();

// PUT request
const updated = await api.put('/users/123', {
    name: 'John Smith'
}).value();

// DELETE request
await api.delete('/users/123').value();
```

### With full response

```ts
const response = await db.api().get('/users');

console.log('Status:', response.status);
console.log('Headers:', response.headers);
console.log('Body:', response.body);

if (response.status === 200) {
    console.log('Success:', response.body);
}
```

### Custom headers

```ts
const result = await db.api().post('/protected', data)
    .header('Authorization', `Bearer ${token}`)
    .header('X-API-Version', '2.0')
    .value();
```

### Type-safe API calls

```ts
interface User {
    id: string;
    name: string;
    email: string;
}

interface CreateUserRequest {
    name: string;
    email: string;
    password: string;
}

type ApiPaths = {
    "/users": {
        get: [void, User[]];
        post: [CreateUserRequest, User];
    };
};

const api = db.api<ApiPaths>();

// Type-safe calls
const users: User[] = await api.get('/users').value();
const newUser: User = await api.post('/users', {
    name: 'Alice',
    email: 'alice@example.com',
    password: 'secure'
}).value();
```

### Error handling

```ts
try {
    const user = await db.api().get('/users/999').value();
} catch (error) {
    if (error instanceof UnsuccessfulApiError) {
        console.error('API error:', error.response);
        console.error('Status:', error.response.status);
        console.error('Message:', error.response.body);
    }
}
```

### Pagination

```ts
async function fetchPage(page: number, pageSize: number) {
    return db.api().get(`/users?page=${page}&limit=${pageSize}`).value();
}

const page1 = await fetchPage(1, 20);
const page2 = await fetchPage(2, 20);
```

### File upload

```ts
const formData = new FormData();
formData.append('file', fileBlob);
formData.append('name', 'profile-picture');

const result = await db.api().post('/upload', formData)
    .header('Content-Type', 'multipart/form-data')
    .value();
```

### Authentication flow

```ts
// Login
const loginResult = await db.api().post('/auth/login', {
    email: 'user@example.com',
    password: 'password123'
}).value();

const { access_token } = loginResult;

// Use token for subsequent requests
const api = db.api();
api.header('Authorization', `Bearer ${access_token}`);

const profile = await api.get('/profile').value();
const orders = await api.get('/orders').value();
```

### Conditional requests

```ts
const api = db.api();

// Check if resource exists
const checkResponse = await api.get('/users/123');

if (checkResponse.status === 404) {
    // Create if doesn't exist
    await api.post('/users/123', userData).value();
} else {
    // Update if exists
    await api.put('/users/123', userData).value();
}
```

### Batch operations

```ts
const api = db.api();

const promises = userIds.map(id =>
    api.get(`/users/${id}`).value()
);

const users = await Promise.all(promises);
console.log(`Fetched ${users.length} users`);
```

### Response transformation

```ts
const response = await db.api().get('/users');

const transformed = {
    data: response.body,
    timestamp: new Date(),
    status: response.status,
    cached: response.headers?.['X-Cache'] === 'HIT'
};
```

### Retry pattern

```ts
async function apiWithRetry(
    apiCall: () => Promise<any>,
    maxRetries = 3
) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            return await apiCall();
        } catch (error) {
            if (i === maxRetries - 1) throw error;
            await new Promise(r => setTimeout(r, 1000 * (i + 1)));
        }
    }
}

const result = await apiWithRetry(() =>
    db.api().get('/unstable-endpoint').value()
);
```

### Query parameters

```ts
// Build query params manually
const params = new URLSearchParams({
    filter: 'active',
    sort: 'created_at',
    order: 'desc'
});

const users = await db.api().get(`/users?${params}`).value();
```

### WebSocket vs API endpoints

```ts
// Both use the same connection
const db = new Surreal();
await db.connect('ws://localhost:8000');

// Regular query (via WebSocket/HTTP RPC)
const users1 = await db.select(new Table('users'));

// API endpoint (via defined API routes)
const users2 = await db.api().get('/users').value();

// Both work, but API endpoints allow custom logic
```

## Response headers

```ts
const response = await db.api().get('/users');

console.log('Content-Type:', response.headers?.['Content-Type']);
console.log('Cache-Control:', response.headers?.['Cache-Control']);
console.log('X-Custom:', response.headers?.['X-Custom-Header']);
```

## Best practices

### 1. Use type definitions

```ts
// Good: Type-safe API
type MyApi = {
    "/users": { get: [void, User[]] };
};
const api = db.api<MyApi>();

// Avoid: Untyped
const api = db.api();
```

### 2. Use .value() for simpler code

```ts
// Good: Direct value access
const users = await api.get('/users').value();

// More verbose:
const response = await api.get('/users');
const users = response.body;
```

### 3. Handle errors gracefully

```ts
// Good: Specific error handling
try {
    const result = await api.get('/users').value();
} catch (error) {
    if (error instanceof UnsuccessfulApiError) {
        // Handle API errors
    }
}
```

## See also

- [SurrealApi](/docs/reference/javascript/api/core/surreal-api.md) - API instance methods
- [SurrealQueryable.api](/docs/reference/javascript/api/core/surreal-queryable.md#api) - Creating API instances
- [User-Defined APIs](/docs/reference/query-language/statements/define/api.md) - Defining APIs in SurrealDB
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/create-promise

# CreatePromise

CreatePromise provides chainable methods for configuring CREATE operations.

The `CreatePromise` class provides a chainable interface for configuring CREATE operations before execution. It extends `Promise`, allowing you to `await` it directly or chain configuration methods.

**Returned by:** [`SurrealQueryable.create()`](/docs/reference/javascript/api/core/surreal-queryable.md#create)

**Source:** [query/create.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/create.ts)

## Type parameters

- `T` - The result type
- `I` - The input type for record data
- `J` - Boolean indicating if result is JSON (default: `false`)

## Configuration methods

### `.content()` {#content}

Set the complete content for the new record.

```ts title="Method Syntax"
createPromise.content(data)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>data</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>The record data (excluding id field).</td>
        </tr>
    </tbody>
</table>

#### Returns
`CreatePromise<T, I, J>` - Chainable promise

#### Examples

```ts title="Create with Specific ID"
const user = await db.create(new RecordId('users', 'john'))
    .content({
        name: 'John Doe',
        email: 'john@example.com',
        age: 30
    });
```

```ts title="Create with Auto-Generated ID"
const user = await db.create(new Table('users'))
    .content({
        name: 'Jane Doe',
        email: 'jane@example.com',
        age: 28
    });
```

---

### `.patch()` {#patch}

Apply JSON Patch operations to set record data.

```ts title="Method Syntax"
createPromise.patch(operations)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>operations</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>JSON Patch operations to apply.</td>
        </tr>
    </tbody>
</table>

#### Returns
`CreatePromise<T, I, J>` - Chainable promise

#### Example

```ts
const user = await db.create(new Table('users'))
    .patch([
        { op: 'add', path: '/name', value: 'John' },
        { op: 'add', path: '/email', value: 'john@example.com' }
    ]);
```

---

### `.output()` {#output}

Specify which fields to return in the response.

```ts title="Method Syntax"
createPromise.output(fields)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>Output</code></td>
            <td>Output specification: <code>"NONE"</code>, <code>"BEFORE"</code>, <code>"AFTER"</code>, <code>"DIFF"</code>, or field list.</td>
        </tr>
    </tbody>
</table>

#### Returns
`CreatePromise<T, I, J>` - Chainable promise

#### Examples

```ts title="Return Specific Fields"
const user = await db.create(new Table('users'))
    .content(userData)
    .output('id', 'name');
// Returns only id and name
```

```ts title="Return Full Record"
const user = await db.create(new Table('users'))
    .content(userData)
    .output('AFTER');
// Returns complete created record
```

```ts title="Return Nothing"
await db.create(new Table('logs'))
    .content(logData)
    .output('NONE');
// Returns undefined, useful for fire-and-forget
```

---

### `.timeout()` {#timeout}

Set a timeout for the operation.

```ts title="Method Syntax"
createPromise.timeout(duration)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>duration</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/duration.md">Duration</a></code></td>
            <td>Maximum time to wait for operation completion.</td>
        </tr>
    </tbody>
</table>

#### Returns
`CreatePromise<T, I, J>` - Chainable promise

#### Example

```ts
const user = await db.create(new Table('users'))
    .content(userData)
    .timeout(Duration.parse('5s'));
```

---

### `.version()` {#version}

Create the record at a specific version (for versioned storage engines).

```ts title="Method Syntax"
createPromise.version(timestamp)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>timestamp</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/datetime.md">DateTime</a></code></td>
            <td>The version timestamp.</td>
        </tr>
    </tbody>
</table>

#### Returns
`CreatePromise<T, I, J>` - Chainable promise

#### Example

```ts
const user = await db.create(new Table('users'))
    .content(userData)
    .version(DateTime.now());
```

---

### `.retry()` {#retry}

Retry the operation with exponential backoff if it fails due to a write conflict. Retry is off by default, and passing an options object (or calling with no arguments) opts the operation in.

This overrides the connection-wide default set via the [`retry`](/docs/reference/javascript/api/types/#connectoptions) option on `ConnectOptions`.

```ts title="Method Syntax"
createPromise.retry(options?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code>Partial&lt;<a href="/docs/reference/javascript/api/types/#retryoptions">RetryOptions</a>&gt;</code></td>
            <td>Retry configuration. If omitted, retry is enabled with the default configuration.</td>
        </tr>
    </tbody>
</table>

#### Returns
`CreatePromise<T, I, J>` - Chainable promise

#### Example

```ts
const user = await db.create(new Table('users'))
    .content(userData)
    .retry({ attempts: 3 });
```

---

### `.json()` {#json}

Return result as JSON string instead of parsed object.

```ts title="Method Syntax"
createPromise.json()
```

#### Returns
`CreatePromise<T, I, true>` - Promise returning JSON string

#### Example

```ts
const jsonString = await db.create(new Table('users'))
    .content(userData)
    .json();
```

---

### `.compile()` {#compile}

Compile the query into a `BoundQuery` without executing it.

```ts title="Method Syntax"
createPromise.compile()
```

#### Returns
`BoundQuery<[T]>` - The compiled query

#### Example

```ts
const query = db.create(new Table('users'))
    .content(userData)
    .compile();
```

---

### `.stream()` {#stream}

Stream the operation result (useful when creating multiple records).

```ts title="Method Syntax"
createPromise.stream()
```

#### Returns
`AsyncIterableIterator` - Async iterator

#### Example

```ts
const results = db.create(new Table('users'))
    .content(multipleUsers);
    
for await (const user of results.stream()) {
    console.log('Created:', user);
}
```

## Complete examples

### Basic creation

```ts
import { Surreal, RecordId, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Create with specific ID
const user = await db.create(new RecordId('users', 'john'))
    .content({
        name: 'John Doe',
        email: 'john@example.com',
        role: 'admin'
    });

// Create with auto-generated ID
const post = await db.create(new Table('posts'))
    .content({
        title: 'Hello World',
        content: 'My first post',
        author: new RecordId('users', 'john')
    });
```

### Creation with output control

```ts
// Only return the ID
const { id } = await db.create(new Table('users'))
    .content(userData)
    .output('id');

// Return specific fields
const summary = await db.create(new Table('users'))
    .content(userData)
    .output('id', 'name', 'created_at');
```

### Bulk creation with streaming

```ts
const users = [
    { name: 'Alice', email: 'alice@example.com' },
    { name: 'Bob', email: 'bob@example.com' },
    { name: 'Carol', email: 'carol@example.com' }
];

for await (const user of db.create(new Table('users')).content(users).stream()) {
    console.log(`Created user: ${user.name} with ID: ${user.id}`);
}
```

### With relationships

```ts
const post = await db.create(new Table('posts'))
    .content({
        title: 'New Post',
        content: 'Post content here',
        author: new RecordId('users', 'john'),
        tags: [
            new RecordId('tags', 'javascript'),
            new RecordId('tags', 'tutorial')
        ],
        created_at: DateTime.now()
    });
```

### Error handling

```ts
try {
    const user = await db.create(new RecordId('users', 'existing'))
        .content(userData);
} catch (error) {
    if (error instanceof ResponseError) {
        console.error('User already exists:', error.message);
    }
}
```

### With timeout

```ts
const user = await db.create(new Table('users'))
    .content(complexUserData)
    .timeout(Duration.parse('10s'));
```

## Chaining pattern

All configuration methods return a new `CreatePromise`, allowing method chaining:

```ts
const result = await db.create(new Table('users'))
    .content(userData)
    .output('id', 'name', 'email')
    .timeout(Duration.parse('5s'));
```

## See also

- [SurrealQueryable.create()](/docs/reference/javascript/api/core/surreal-queryable.md#create) - Method that returns CreatePromise
- [InsertPromise](/docs/reference/javascript/api/queries/insert-promise.md) - Bulk insertion
- [UpsertPromise](/docs/reference/javascript/api/queries/upsert-promise.md) - Insert or replace
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/delete-promise

# DeletePromise

DeletePromise provides chainable methods for configuring DELETE operations.

The `DeletePromise` class provides a chainable interface for configuring DELETE operations before execution. It extends `Promise`, allowing you to `await` it directly or chain configuration methods.

**Returned by:** [`SurrealQueryable.delete()`](/docs/reference/javascript/api/core/surreal-queryable.md#delete)

**Source:** [query/delete.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/delete.ts)

## Type parameters

- `T` - The result type (deleted record data)
- `J` - Boolean indicating if result is JSON (default: `false`)

## Configuration methods

### `.output()` {#output}

Specify what to return from the delete operation.

```ts title="Method Syntax"
deletePromise.output(fields)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>Output</code></td>
            <td><code>"NONE"</code>, <code>"BEFORE"</code>, or specific field list.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DeletePromise<T, J>` - Chainable promise

#### Examples

```ts title="Return Deleted Record"
const deleted = await db.delete(new RecordId('users', 'john'))
    .output('BEFORE');
// Returns the record before deletion
```

```ts title="Return Specific Fields"
const deleted = await db.delete(new RecordId('users', 'john'))
    .output('id', 'name', 'email');
// Returns only specified fields of deleted record
```

```ts title="Return Nothing"
await db.delete(new RecordId('logs', '123'))
    .output('NONE');
// Faster when you don't need the data
```

---

### `.timeout()` {#timeout}

Set a timeout for the operation.

```ts title="Method Syntax"
deletePromise.timeout(duration)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>duration</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/duration.md">Duration</a></code></td>
            <td>Maximum time to wait.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DeletePromise<T, J>` - Chainable promise

#### Example

```ts
const deleted = await db.delete(new Table('users'))
    .timeout(Duration.parse('10s'));
```

---

### `.version()` {#version}

Delete at a specific version (for versioned storage engines).

```ts title="Method Syntax"
deletePromise.version(timestamp)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>timestamp</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/datetime.md">DateTime</a></code></td>
            <td>The version timestamp.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DeletePromise<T, J>` - Chainable promise

---

### `.retry()` {#retry}

Retry the operation with exponential backoff if it fails due to a write conflict. Retry is off by default, and passing an options object (or calling with no arguments) opts the operation in.

This overrides the connection-wide default set via the [`retry`](/docs/reference/javascript/api/types/#connectoptions) option on `ConnectOptions`.

```ts title="Method Syntax"
deletePromise.retry(options?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code>Partial&lt;<a href="/docs/reference/javascript/api/types/#retryoptions">RetryOptions</a>&gt;</code></td>
            <td>Retry configuration. If omitted, retry is enabled with the default configuration.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DeletePromise<T, J>` - Chainable promise

#### Example

```ts
await db.delete(new RecordId('users', 'john')).retry();
```

---

### `.json()` {#json}

Return result as JSON string.

```ts title="Method Syntax"
deletePromise.json()
```

#### Returns
`DeletePromise<T, true>` - Promise returning JSON string

---

### `.compile()` {#compile}

Compile the query into a `BoundQuery` without executing it.

```ts title="Method Syntax"
deletePromise.compile()
```

#### Returns
`BoundQuery<[T]>` - The compiled query

#### Example

```ts
const query = db.delete(new Table('temp_data'))
    .compile();
```

---

### `.stream()` {#stream}

Stream deleted records as they are removed.

```ts title="Method Syntax"
deletePromise.stream()
```

#### Returns
`AsyncIterableIterator` - Async iterator

## Complete examples

### Basic deletion

```ts
import { Surreal, RecordId, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Delete single record
const deleted = await db.delete(new RecordId('users', 'john'));
console.log('Deleted user:', deleted);

// Delete by range
const deleted = await db.delete(
    new RecordIdRange('users', 'a', 'f')
);
console.log(`Deleted ${deleted.length} users`);

// Delete entire table
const deleted = await db.delete(new Table('temp_data'));
console.log(`Deleted ${deleted.length} records`);
```

### Capture deleted data

```ts
// Store deleted data before removing
const user = await db.delete(new RecordId('users', 'john'))
    .output('BEFORE');

// Archive the deleted user
await db.create(new RecordId('archived_users', user.id))
    .content({
        ...user,
        deleted_at: DateTime.now()
    });
```

### Conditional deletion

```ts
// Note: WHERE clauses are not directly supported on delete promises
// Use query() for conditional deletes
const result = await db.query(
    surql`DELETE FROM users WHERE inactive = true`
).collect();

console.log(`Deleted ${result[0].result.length} inactive users`);
```

### Bulk deletion with streaming

```ts
const deletedRecords = db.delete(new Table('old_logs'));

let count = 0;
for await (const record of deletedRecords.stream()) {
    count++;
    if (count % 100 === 0) {
        console.log(`Deleted ${count} records`);
    }
}
```

### Soft delete pattern

```ts
// Instead of deleting, mark as deleted
const user = await db.update(new RecordId('users', 'john'))
    .merge({
        deleted: true,
        deleted_at: DateTime.now()
    });

// Actual delete with backup
const user = await db.delete(new RecordId('users', 'john'))
    .output('BEFORE');

if (user) {
    await db.create(new RecordId('deleted_users', user.id))
        .content(user);
}
```

### Delete with error handling

```ts
try {
    const deleted = await db.delete(new RecordId('users', 'john'));
    
    if (!deleted) {
        console.log('User not found');
    } else {
        console.log('User deleted successfully');
    }
} catch (error) {
    if (error instanceof ResponseError) {
        console.error('Delete failed:', error.message);
    }
}
```

### Performance optimisation

```ts
// Don't wait for deleted data if not needed
await db.delete(new Table('temp_cache'))
    .output('NONE');
// Faster execution
```

### Delete with timeout

```ts
// For large deletions
const deleted = await db.delete(new Table('old_logs'))
    .timeout(Duration.parse('30s'));
```

### Cascading deletes

```ts
// Delete user and all related data
const userId = new RecordId('users', 'john');

// Delete user
const user = await db.delete(userId);

// Delete related posts
await db.query(
    surql`DELETE FROM posts WHERE author = ${userId}`
).collect();

// Delete related comments
await db.query(
    surql`DELETE FROM comments WHERE author = ${userId}`
).collect();

console.log('User and related data deleted');
```

### Batch deletion

```ts
const idsToDelete = ['user1', 'user2', 'user3'];

for (const id of idsToDelete) {
    await db.delete(new RecordId('users', id));
}

// Or using query for better performance
const result = await db.query(
    surql`DELETE FROM users WHERE id IN ${idsToDelete.map(id => new RecordId('users', id))}`
).collect();
```

### Archive before delete

```ts
async function archiveAndDelete(recordId: RecordId) {
    // Get the record
    const record = await db.select(recordId);
    
    if (!record) {
        throw new Error('Record not found');
    }
    
    // Archive it
    await db.create(new RecordId('archive', record.id))
        .content({
            ...record,
            archived_at: DateTime.now()
        });
    
    // Delete original
    await db.delete(recordId).output('NONE');
    
    return record;
}

await archiveAndDelete(new RecordId('users', 'john'));
```

## Important notes

> [!WARNING]
> DELETE operations are permanent and cannot be undone. Always ensure you have backups or use the `.output('BEFORE')` method to capture data before deletion.

> [!NOTE: Tip]
> For conditional deletions, use [`db.query()`](/docs/reference/javascript/api/core/surreal-queryable.md#query) with a DELETE statement including a WHERE clause.

## Chaining pattern

```ts
const result = await db.delete(new Table('users'))
    .output('BEFORE')
    .timeout(Duration.parse('10s'));
```

## See also

- [SurrealQueryable.delete()](/docs/reference/javascript/api/core/surreal-queryable.md#delete) - Method that returns DeletePromise
- [UpdatePromise](/docs/reference/javascript/api/queries/update-promise.md) - Update records
- [Query](/docs/reference/javascript/api/queries/query.md) - Raw SurrealQL for conditional deletes
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/insert-promise

# InsertPromise

InsertPromise provides chainable methods for configuring INSERT operations.

The `InsertPromise` class provides a chainable interface for configuring INSERT operations for bulk record insertion. It extends `Promise`, allowing you to `await` it directly or chain configuration methods.

**Returned by:** [`SurrealQueryable.insert()`](/docs/reference/javascript/api/core/surreal-queryable.md#insert)

**Source:** [query/insert.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/insert.ts)

## Type parameters

- `T` - The result type
- `J` - Boolean indicating if result is JSON (default: `false`)

## Configuration methods

### `.relation()` {#relation}

Configure the insert to work with relation (edge) records instead of regular records.

```ts title="Method Syntax"
insertPromise.relation()
```

#### Returns
`InsertPromise<T, J>` - Chainable promise

#### Example

```ts
const edges = await db.insert([
    {
        id: new RecordId('likes', '1'),
        in: new RecordId('users', 'john'),
        out: new RecordId('posts', '1')
    }
]).relation();
```

---

### `.ignore()` {#ignore}

Ignore records that already exist (skip duplicates without error).

```ts title="Method Syntax"
insertPromise.ignore()
```

#### Returns
`InsertPromise<T, J>` - Chainable promise

#### Example

```ts
const users = await db.insert([
    { id: new RecordId('users', 'john'), name: 'John' },
    { id: new RecordId('users', 'jane'), name: 'Jane' }
]).ignore();
// If 'john' exists, it's skipped; 'jane' is inserted
```

---

### `.output()` {#output}

Specify which fields to return in the response.

```ts title="Method Syntax"
insertPromise.output(fields)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>Output</code></td>
            <td><code>"NONE"</code>, <code>"AFTER"</code>, or specific field list.</td>
        </tr>
    </tbody>
</table>

#### Returns
`InsertPromise<T, J>` - Chainable promise

#### Examples

```ts title="Return Specific Fields"
const users = await db.insert(userData)
    .output('id', 'name');
// Returns only id and name
```

```ts title="Return Nothing"
await db.insert(logData)
    .output('NONE');
// No return value, useful for performance
```

---

### `.timeout()` {#timeout}

Set a timeout for the operation.

```ts title="Method Syntax"
insertPromise.timeout(duration)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>duration</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/duration.md">Duration</a></code></td>
            <td>Maximum time to wait.</td>
        </tr>
    </tbody>
</table>

#### Returns
`InsertPromise<T, J>` - Chainable promise

---

### `.version()` {#version}

Insert at a specific version (for versioned storage engines).

```ts title="Method Syntax"
insertPromise.version(timestamp)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>timestamp</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/datetime.md">DateTime</a></code></td>
            <td>The version timestamp.</td>
        </tr>
    </tbody>
</table>

#### Returns
`InsertPromise<T, J>` - Chainable promise

---

### `.retry()` {#retry}

Retry the operation with exponential backoff if it fails due to a write conflict. Retry is off by default, and passing an options object (or calling with no arguments) opts the operation in.

This overrides the connection-wide default set via the [`retry`](/docs/reference/javascript/api/types/#connectoptions) option on `ConnectOptions`.

```ts title="Method Syntax"
insertPromise.retry(options?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code>Partial&lt;<a href="/docs/reference/javascript/api/types/#retryoptions">RetryOptions</a>&gt;</code></td>
            <td>Retry configuration. If omitted, retry is enabled with the default configuration.</td>
        </tr>
    </tbody>
</table>

#### Returns
`InsertPromise<T, J>` - Chainable promise

#### Example

```ts
await db.insert(new Table('users')).content(users).retry({ attempts: 3 });
```

---

### `.json()` {#json}

Return result as JSON string.

```ts title="Method Syntax"
insertPromise.json()
```

#### Returns
`InsertPromise<T, true>` - Promise returning JSON string

---

### `.compile()` {#compile}

Compile the query into a `BoundQuery` without executing it.

```ts title="Method Syntax"
insertPromise.compile()
```

#### Returns
`BoundQuery<[T]>` - The compiled query

#### Example

```ts
const query = db.insert(records)
    .compile();
```

---

### `.stream()` {#stream}

Stream results as records are inserted.

```ts title="Method Syntax"
insertPromise.stream()
```

#### Returns
`AsyncIterableIterator` - Async iterator

## Complete examples

### Basic insertion

```ts
import { Surreal, RecordId } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Insert single record
const user = await db.insert({
    id: new RecordId('users', 'alice'),
    name: 'Alice',
    email: 'alice@example.com'
});

// Insert multiple records
const users = await db.insert([
    { id: new RecordId('users', 'bob'), name: 'Bob' },
    { id: new RecordId('users', 'carol'), name: 'Carol' }
]);
```

### Insert into table

```ts
// Let database generate IDs
const users = await db.insert(new Table('users'), [
    { name: 'Dave', email: 'dave@example.com' },
    { name: 'Eve', email: 'eve@example.com' }
]);
```

### Insert a record link

A field typed as a record link needs a [`RecordId`](/docs/reference/javascript/api/values/record-id.md) instance, built from the table name and the id as separate arguments. A string that looks like a record ID stays a string, so the insert fails with `Expected record<company> but found 'company:acme'`.

```ts
import { RecordId, Table } from 'surrealdb';

// Schema: DEFINE FIELD company ON job TYPE record<company>;
await db.insert(new Table('job'), {
    description: 'Hello World',
    company: new RecordId('company', 'acme')
});
```

Where the id arrives as a single string, convert it inside the query with [`type::record()`](/docs/reference/query-language/functions/database-functions/type.md#typerecord) instead:

```ts
await db.query(
    'INSERT INTO job { description: $description, company: type::record($company) }',
    { description: 'Hello World', company: 'company:acme' }
);
```

### Ignore duplicates

```ts
// Skip existing records without error
const users = await db.insert([
    { id: new RecordId('users', 'john'), name: 'John' },
    { id: new RecordId('users', 'jane'), name: 'Jane' }
]).ignore();

console.log(`Inserted ${users.length} new users`);
```

### Bulk insert with streaming

```ts
const largeDataset = generateThousandsOfRecords();

let count = 0;
for await (const record of db.insert(largeDataset).stream()) {
    count++;
    if (count % 100 === 0) {
        console.log(`Inserted ${count} records`);
    }
}
```

### Insert relations (edges)

```ts
const likes = await db.insert([
    {
        id: new RecordId('likes', '1'),
        in: new RecordId('users', 'john'),
        out: new RecordId('posts', '1'),
        created_at: DateTime.now()
    },
    {
        id: new RecordId('likes', '2'),
        in: new RecordId('users', 'jane'),
        out: new RecordId('posts', '1'),
        created_at: DateTime.now()
    }
]).relation();
```

### Optimised insertion

```ts
// Don't wait for return values
await db.insert(logEntries)
    .output('NONE');
// Faster execution when you don't need the results
```

### Insert with timeout

```ts
const users = await db.insert(largeDataset)
    .timeout(Duration.parse('30s'));
```

### Error handling

```ts
try {
    const users = await db.insert([
        { id: new RecordId('users', 'existing'), name: 'Test' }
    ]);
} catch (error) {
    if (error instanceof ResponseError) {
        console.error('Duplicate key error:', error.message);
        
        // Retry with ignore
        const users = await db.insert([
            { id: new RecordId('users', 'existing'), name: 'Test' }
        ]).ignore();
    }
}
```

### Batch processing

```ts
const BATCH_SIZE = 100;
const allUsers = [...]; // Large array

for (let i = 0; i < allUsers.length; i += BATCH_SIZE) {
    const batch = allUsers.slice(i, i + BATCH_SIZE);
    await db.insert(batch);
    console.log(`Inserted batch ${i / BATCH_SIZE + 1}`);
}
```

## INSERT vs CREATE

### When to use INSERT vs CREATE

```ts
// CREATE: For single records, with more configuration options
const user = await db.create(new RecordId('users', 'john'))
    .content(userData);

// INSERT: For bulk operations, optimized for performance
const users = await db.insert([
    { id: new RecordId('users', 'alice'), ...data1 },
    { id: new RecordId('users', 'bob'), ...data2 },
    { id: new RecordId('users', 'carol'), ...data3 }
]);
```

## Chaining pattern

```ts
const result = await db.insert(records)
    .ignore()
    .output('id', 'name')
    .timeout(Duration.parse('10s'));
```

## See also

- [SurrealQueryable.insert()](/docs/reference/javascript/api/core/surreal-queryable.md#insert) - Method that returns InsertPromise
- [CreatePromise](/docs/reference/javascript/api/queries/create-promise.md) - Single record creation
- [UpsertPromise](/docs/reference/javascript/api/queries/upsert-promise.md) - Insert or update
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/live-promise

# LivePromise

LivePromise variants for managing real-time live query subscriptions.

Live query promises provide interfaces for subscribing to real-time updates from SurrealDB. There are two variants: `ManagedLivePromise` for new subscriptions and `UnmanagedLivePromise` for existing ones.

**Returned by:** [`SurrealQueryable.live()`](/docs/reference/javascript/api/core/surreal-queryable.md#live), [`SurrealQueryable.liveOf()`](/docs/reference/javascript/api/core/surreal-queryable.md#liveof)

**Source:** [query/live.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/live.ts)

## `ManagedLivePromise<T>` {#managedlivepromise}

A managed live query subscription that the SDK automatically creates and manages.

**Returned by:** [`SurrealQueryable.live()`](/docs/reference/javascript/api/core/surreal-queryable.md#live)

### Configuration methods

#### `.diff()` {#diff}

Configure the subscription to return only patches (diffs) instead of full records on updates.

```ts title="Method Syntax"
livePromise.diff()
```

**Returns:** `ManagedLivePromise<T>` - Chainable promise

**Example:**
```ts
const subscription = await db.live(new Table('users')).diff();

for await (const update of subscription) {
    console.log('Diff:', update.diff); // Only changed fields
}
```

---

#### `.fields()` {#fields}

Select only specific fields in the live updates.

```ts title="Method Syntax"
livePromise.fields(...fields)
```

**Parameters:**
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>Field&lt;T&gt;[]</code></td>
            <td>Field names to include in updates.</td>
        </tr>
    </tbody>
</table>

**Returns:** `ManagedLivePromise<T>`

**Example:**
```ts
const subscription = await db.live(new Table('users'))
    .fields('name', 'email', 'status');

for await (const update of subscription) {
    // Only includes specified fields
    console.log(update.result); // { name, email, status }
}
```

---

#### `.value()` {#value}

Return only the value of a specific field in updates.

```ts title="Method Syntax"
livePromise.value(field)
```

**Parameters:**
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>field</code> <label label="required" /></td>
            <td><code>Field&lt;T&gt;</code></td>
            <td>Field name to extract.</td>
        </tr>
    </tbody>
</table>

**Returns:** `ManagedLivePromise<T>`

**Example:**
```ts
const subscription = await db.live(new Table('users'))
    .value('name');

for await (const update of subscription) {
    console.log('Name changed:', update.result); // Just the name string
}
```

---

#### `.where()` {#where}

Filter live updates to only receive records matching the condition.

```ts title="Method Syntax"
livePromise.where(expr)
```

**Parameters:**
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>expr</code> <label label="required" /></td>
            <td><code>ExprLike</code></td>
            <td>Condition expression to filter updates (string or <a href="/docs/reference/javascript/api/utilities/expr.md">Expression</a> object).</td>
        </tr>
    </tbody>
</table>

**Returns:** `ManagedLivePromise<T>`

**Example:**
```ts
const subscription = await db.live(new Table('users'))
    .where('age >= 18');

for await (const update of subscription) {
    // Only receives updates for users with age >= 18
    console.log(update.action, update.result);
}
```

---

#### `.fetch()` {#fetch}

Fetch related records in live updates.

```ts title="Method Syntax"
livePromise.fetch(...fields)
```

**Parameters:**
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>Field&lt;T&gt;[]</code></td>
            <td>Related fields to fetch.</td>
        </tr>
    </tbody>
</table>

**Returns:** `ManagedLivePromise<T>`

**Example:**
```ts
const subscription = await db.live(new Table('posts'))
    .fetch('author', 'comments');

for await (const update of subscription) {
    // author and comments are fully populated
    console.log('Post by:', update.result.author.name);
}
```

---

#### `.compile()` {#compile}

Compile the query into a BoundQuery.

```ts title="Method Syntax"
livePromise.compile()
```

**Returns:** `BoundQuery` - The compiled query

---

### Live subscription methods

Once awaited, a `ManagedLivePromise` returns a `LiveSubscription` object:

#### Iteration

```ts
for await (const update of subscription) {
    console.log(update.action); // 'CREATE' | 'UPDATE' | 'DELETE'
    console.log(update.result); // The record data
}
```

#### `.kill()`

Kill the live query subscription.

```ts
await subscription.kill();
```

## `UnmanagedLivePromise` {#unmanagedlivepromise}

An unmanaged subscription to an existing live query by its UUID.

**Returned by:** [`SurrealQueryable.liveOf()`](/docs/reference/javascript/api/core/surreal-queryable.md#liveof)

### Usage

```ts
const liveQueryId: Uuid = /* from somewhere */;
const subscription = await db.liveOf(liveQueryId);

for await (const update of subscription) {
    console.log(update);
}
```

## Complete examples

### Basic live query

```ts
import { Surreal, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Subscribe to all user changes
const subscription = await db.live(new Table('users'));

for await (const update of subscription) {
    console.log(`${update.action}:`, update.result);
}

// Clean up when done
await subscription.kill();
```

### Filtered live query

```ts
// Only receive updates for active users
const subscription = await db.live(new Table('users'))
    .where('status = "active"');

for await (const update of subscription) {
    if (update.action === 'CREATE') {
        console.log('New active user:', update.result);
    } else if (update.action === 'UPDATE') {
        console.log('Active user updated:', update.result);
    } else if (update.action === 'DELETE') {
        console.log('Active user deleted:', update.result);
    }
}
```

### Live query with specific fields

```ts
const subscription = await db.live(new Table('users'))
    .fields('name', 'email', 'status')
    .where('role = "admin"');

for await (const update of subscription) {
    // Only receives name, email, status fields
    console.log('Admin update:', update.result);
}

await subscription.kill();
```

### Diff-based updates

```ts
// Get only changes, not full records
const subscription = await db.live(new Table('users')).diff();

for await (const update of subscription) {
    if (update.action === 'UPDATE') {
        console.log('Changed fields:', update.diff);
        // { email: 'new@example.com' } instead of full record
    }
}
```

### Live query with relations

```ts
const subscription = await db.live(new Table('posts'))
    .fetch('author', 'comments')
    .where('published = true');

for await (const update of subscription) {
    // author is fully populated
    console.log('Post by:', update.result.author.name);
    console.log('Comments:', update.result.comments.length);
}
```

### Real-time dashboard

```ts
async function monitorUsers(callback: (stats: any) => void) {
    const subscription = await db.live(new Table('users'));
    
    const stats = {
        created: 0,
        updated: 0,
        deleted: 0
    };
    
    for await (const update of subscription) {
        if (update.action === 'CREATE') stats.created++;
        else if (update.action === 'UPDATE') stats.updated++;
        else if (update.action === 'DELETE') stats.deleted++;
        
        callback(stats);
    }
}

monitorUsers((stats) => {
    console.log('User stats:', stats);
});
```

### Watch specific record

```ts
// Subscribe to changes on a specific record
const subscription = await db.live(new RecordId('users', 'john'));

for await (const update of subscription) {
    if (update.action === 'UPDATE') {
        console.log('John was updated:', update.result);
    } else if (update.action === 'DELETE') {
        console.log('John was deleted');
        break;
    }
}
```

### Auto-reconnect live query

```ts
let subscription: LiveSubscription | null = null;

async function setupLiveQuery() {
    subscription = await db.live(new Table('users'))
        .where('active = true');
    
    for await (const update of subscription) {
        console.log('Update:', update);
    }
}

// Handle reconnection
db.subscribe('reconnecting', () => {
    console.log('Connection lost, live query will be restored...');
});

db.subscribe('connected', async () => {
    console.log('Reconnected, live queries restored automatically');
});

await setupLiveQuery();
```

### Cleanup pattern

```ts
const subscriptions: LiveSubscription[] = [];

// Create multiple subscriptions
subscriptions.push(await db.live(new Table('users')));
subscriptions.push(await db.live(new Table('posts')));
subscriptions.push(await db.live(new Table('comments')));

// Process updates
// ...

// Cleanup all subscriptions
async function cleanup() {
    await Promise.all(subscriptions.map(sub => sub.kill()));
    console.log('All subscriptions cleaned up');
}

// Call on app shutdown
await cleanup();
```

### Error handling

```ts
try {
    const subscription = await db.live(new Table('users'));
    
    try {
        for await (const update of subscription) {
            await processUpdate(update);
        }
    } catch (error) {
        console.error('Error processing update:', error);
    } finally {
        await subscription.kill();
    }
} catch (error) {
    if (error instanceof LiveSubscriptionError) {
        console.error('Failed to create subscription:', error);
    }
}
```

## Update message structure

Each update message conforms to the [`LiveMessage<T>`](/docs/reference/javascript/api/types/#livemessage) interface with `action`, `result`, and optional `diff` properties.

### Processing updates

```ts
for await (const update of subscription) {
    switch (update.action) {
        case 'CREATE':
            await handleCreate(update.result);
            break;
        case 'UPDATE':
            await handleUpdate(update.result, update.diff);
            break;
        case 'DELETE':
            await handleDelete(update.result);
            break;
    }
}
```

## See also

- [SurrealQueryable.live()](/docs/reference/javascript/api/core/surreal-queryable.md#live) - Create managed live subscription
- [SurrealQueryable.liveOf()](/docs/reference/javascript/api/core/surreal-queryable.md#liveof) - Subscribe to existing live query
- [Live queries guide](/docs/reference/query-language/statements/live-select.md) - SurrealQL LIVE documentation
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/query

# Query

Query class for executing raw SurrealQL with streaming and batch processing support.

The `Query` class provides a configurable interface for executing raw SurrealQL statements with support for streaming, batch processing, and response handling. It extends `Promise`, allowing you to `await` it directly or use specialised methods.

**Returned by:** [`SurrealQueryable.query()`](/docs/reference/javascript/api/core/surreal-queryable.md#query)

**Source:** [query/query.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/query.ts)

## Type parameters

- `R extends unknown[]` - Array of result types for each query statement
- `J extends boolean` - Boolean indicating if result is JSON (default: `false`)

`R` is a tuple with one entry per statement, so a query with one statement still takes a one-element tuple. Destructure the result to get the value of that statement:

```ts title="One statement"
const [people] = await db.query<[Person[]]>('SELECT * FROM person WHERE age > $age', { age: 18 });
```

To read a whole table or one record, [`select<T>()`](/docs/reference/javascript/api/core/surreal-queryable.md#select) takes the record type directly, as in `db.select<Person>(new Table('person'))`.

## Methods

### `.collect()` {#collect}

Collect and return the results of all queries at once. If any query fails, the promise rejects.

You can optionally specify which query indexes to collect.

```ts title="Method Syntax"
query.collect<T>(...queryIndexes?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>queryIndexes</code> <label label="optional" /></td>
            <td><code>number[]</code></td>
            <td>Specific query indexes to collect. If omitted, collects all.</td>
        </tr>
    </tbody>
</table>

#### Type parameters
- `T extends unknown[]` - Override result types

#### Returns
`Promise<Collect<T, J>>` - Array of results

#### Examples

```ts title="Single Query"
const result = await db.query('SELECT * FROM users').collect();
console.log(result); // [{ success: true, result: [...] }]
```

```ts title="Multiple Queries with Types"
const [users, posts] = await db.query<[User[], Post[]]>(`
    SELECT * FROM users;
    SELECT * FROM posts;
`).collect();
```

```ts title="Collect Specific Queries"
const [users] = await db.query(`
    SELECT * FROM users;
    SELECT * FROM posts;
    SELECT * FROM comments;
`).collect<[User[]]>(0); // Only collect first query
```

```ts title="With Bindings"
const result = await db.query(
    'SELECT * FROM users WHERE age > $age',
    { age: 18 }
).collect();
```

---

### `.stream()` {#stream}

Stream response frames as they are received from the database.

Each iteration yields a value frame, error frame, or done frame.

```ts title="Method Syntax"
query.stream()
```

#### Returns
`AsyncIterableIterator<Frame<unknown, J>>` - Async iterator of frames

Each frame is one of three types, distinguished by the `type` property:

**Value Frame** (`type: 'value'`)
- `frame.value` - The result data
- `frame.query` - Query index

**Error Frame** (`type: 'error'`)
- `frame.error` - Error information
- `frame.query` - Query index

**Done Frame** (`type: 'done'`)
- `frame.query` - Query index that completed

#### Examples

```ts title="Stream Processing"
for await (const frame of db.query('SELECT * FROM users').stream()) {
    if (frame.type === 'value') {
        console.log('Received data:', frame.value);
    } else if (frame.type === 'error') {
        console.error('Query error:', frame.error);
    } else if (frame.type === 'done') {
        console.log('Query complete');
    }
}
```

```ts title="Process Large Results"
let count = 0;
for await (const frame of db.query('SELECT * FROM large_table').stream()) {
    if (frame.type === 'value') {
        await processRecord(frame.value);
        count++;
    }
}
console.log(`Processed ${count} records`);
```

---

### `.responses()` {#responses}

Get individual response objects for each query statement, including success/failure status.

You can optionally specify which query indexes to include.

```ts title="Method Syntax"
query.responses<T>(...queries?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>queries</code> <label label="optional" /></td>
            <td><code>number[]</code></td>
            <td>Specific query indexes to include. If omitted, includes all.</td>
        </tr>
    </tbody>
</table>

#### Type parameters
- `T extends unknown[]` - Override result types

#### Returns
`Promise<Responses<T, J>>` - Array of [`QueryResponse`](/docs/reference/javascript/api/types/#queryresponse) objects

#### Examples

```ts title="Handle Individual Responses"
const responses = await db.query(`
    SELECT * FROM users;
    INVALID QUERY;
    SELECT * FROM posts;
`).responses();

for (const [index, response] of responses.entries()) {
    if (response.success) {
        console.log(`Query ${index} succeeded:`, response.result);
    } else {
        console.error(`Query ${index} failed:`, response.error.message);
    }
}
```

```ts title="Check Query Statistics"
const responses = await db.query('SELECT * FROM users').responses();

for (const response of responses) {
    if (response.success && response.stats) {
        console.log('Records scanned:', response.stats.recordsScanned);
        console.log('Duration:', response.stats.duration);
    }
}
```

---

### `.retry()` {#retry}

Retry the whole query with exponential backoff if it fails due to a write conflict. Only applies to `.collect()` (and awaiting the query directly) - it does not apply to `.responses()`, which returns partial results, or `.stream()`, which yields results incrementally and cannot be safely replayed mid-stream.

Retry is off by default, since auto-retrying a non-atomic multi-statement query could apply some statements more than once. Passing an options object (or calling with no arguments) opts the query in. This overrides the connection-wide default set via the [`retry`](/docs/reference/javascript/api/types/#connectoptions) option on `ConnectOptions`.

```ts title="Method Syntax"
query.retry(options?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code>Partial&lt;<a href="/docs/reference/javascript/api/types/#retryoptions">RetryOptions</a>&gt;</code></td>
            <td>Retry configuration. If omitted, retry is enabled with the default configuration.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Query<R, J>` - Chainable query

#### Example

```ts title="Retry on Conflict"
const [n] = await db
    .query<[number]>('UPDATE counter:c SET n += 1 RETURN n')
    .retry({ attempts: 3 })
    .collect();
```

By default, a conflict is detected using [`isRetryableConflict`](/docs/reference/javascript/api/utilities/is-retryable-conflict.md), which matches on the server's error message. You can supply a custom predicate to override this:

```ts title="Custom Retry Predicate"
const result = await db
    .query('UPDATE counter:c SET n += 1 RETURN n')
    .retry({ attempts: 5, retryable: (error) => error.message.includes('conflict') })
    .collect();
```

---

### `.json()` {#json}

Configure the query to return results as JSON strings.

```ts title="Method Syntax"
query.json()
```

#### Returns
`Query<R, true>` - Query returning JSON strings

#### Example

```ts
const jsonResults = await db.query('SELECT * FROM users').json().collect();
console.log(typeof jsonResults[0]); // 'string'
```

## Complete examples

### Basic query execution

```ts
import { Surreal } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Simple query
const result = await db.query('SELECT * FROM users').collect();
console.log(result[0]); // Array of users

// With await (same as .collect())
const result = await db.query('SELECT * FROM users');
```

### Parameterised queries

```ts
// Using bindings object
const result = await db.query(
    'SELECT * FROM users WHERE age > $age AND status = $status',
    { age: 18, status: 'active' }
).collect();

// Using surql template
import { surql } from 'surrealdb';

const minAge = 18;
const result = await db.query(
    surql`SELECT * FROM users WHERE age > ${minAge}`
).collect();
```

### Multiple statements

```ts
const [users, posts, comments] = await db.query<[User[], Post[], Comment[]]>(`
    SELECT * FROM users;
    SELECT * FROM posts WHERE published = true;
    SELECT * FROM comments WHERE approved = true;
`).collect();

console.log('Users:', users);
console.log('Posts:', posts);
console.log('Comments:', comments);
```

### Streaming large results

```ts
const query = db.query('SELECT * FROM large_table');

for await (const frame of query.stream()) {
    if (frame.type === 'value') {
        // Process each chunk as it arrives
        await processChunk(frame.value);
    } else if (frame.type === 'error') {
        console.error('Error:', frame.error);
        break;
    }
}
```

### Error handling with responses

```ts
const responses = await db.query(`
    CREATE users:john SET name = 'John';
    CREATE users:john SET name = 'Duplicate';
    SELECT * FROM users:john;
`).responses();

for (const [i, response] of responses.entries()) {
    if (response.success) {
        console.log(`Query ${i} OK:`, response.result);
    } else {
        console.log(`Query ${i} failed:`, response.error.message);
    }
}
```

### Transaction queries

```ts
const txn = await db.beginTransaction();

try {
    const [created, updated] = await txn.query<[User, User]>(`
        CREATE users:new SET name = 'New User';
        UPDATE users:john SET updated_at = time::now();
    `).collect();
    
    await txn.commit();
} catch (error) {
    await txn.cancel();
}
```

### Complex query with statistics

```ts
const responses = await db.query(`
    SELECT * FROM users WHERE age > 18;
    SELECT count() FROM users GROUP BY status;
`).responses();

for (const response of responses) {
    if (response.success && response.stats) {
        console.log('Execution time:', response.stats.duration);
        console.log('Records scanned:', response.stats.recordsScanned);
        console.log('Bytes received:', response.stats.bytesReceived);
    }
}
```

### Conditional logic

```ts
const status = 'active';
const minAge = 18;

const result = await db.query(
    surql`
        LET $active_users = SELECT * FROM users WHERE status = ${status};
        LET $adult_users = SELECT * FROM users WHERE age >= ${minAge};
        RETURN {
            active: $active_users,
            adults: $adult_users,
            both: SELECT * FROM $active_users WHERE age >= ${minAge}
        };
    `
).collect();
```

### Data migration

```ts
// Batch update with query
const migration = await db.query(`
    -- Add new field to all users
    UPDATE users SET new_field = 'default_value';
    
    -- Migrate data format
    UPDATE users SET profile = {
        bio: bio,
        avatar: avatar_url
    };
    
    -- Remove old fields
    UPDATE users UNSET bio, avatar_url;
`).collect();

console.log('Migration complete');
```

### Streaming with progress

```ts
let totalRecords = 0;
let queriesCompleted = 0;

for await (const frame of db.query('SELECT * FROM users; SELECT * FROM posts;').stream()) {
    if (frame.type === 'value') {
        totalRecords += frame.value.length;
        console.log(`Received ${frame.value.length} records`);
    } else if (frame.type === 'done') {
        queriesCompleted++;
        console.log(`Query ${frame.query} completed`);
    }
}

console.log(`Total: ${totalRecords} records from ${queriesCompleted} queries`);
```

## Best practices

### 1. Use parameterisation

```ts
// Good: Parameterised
const result = await db.query(
    'SELECT * FROM users WHERE name = $name',
    { name: userName }
).collect();

// Better: Use surql template
const result = await db.query(
    surql`SELECT * FROM users WHERE name = ${userName}`
).collect();

// Avoid: String concatenation (SQL injection risk)
const result = await db.query(
    `SELECT * FROM users WHERE name = '${userName}'`
).collect();
```

### 2. Handle errors appropriately

```ts
// Good: Check individual responses
const responses = await db.query(multiStatementQuery).responses();

for (const response of responses) {
    if (!response.success) {
        handleError(response.error);
    }
}

// Simple: Use collect with try-catch
try {
    const results = await db.query(query).collect();
} catch (error) {
    // First error stops execution
}
```

### 3. Use streaming for large results

```ts
// Good: Stream large datasets
for await (const frame of db.query('SELECT * FROM large_table').stream()) {
    if (frame.type === 'value') {
        await processChunk(frame.value);
    }
}

// Avoid: Loading everything into memory
const [large] = await db.query('SELECT * FROM large_table').collect();
// May cause memory issues
```

### 4. Use type parameters

```ts
// Good: Type-safe results
const [users, posts] = await db.query<[User[], Post[]]>(`
    SELECT * FROM users;
    SELECT * FROM posts;
`).collect();

// Now TypeScript knows the types
users[0].name; // string
posts[0].title; // string
```

## See also

- [SurrealQueryable.query()](/docs/reference/javascript/api/core/surreal-queryable.md#query) - Method that returns Query
- [BoundQuery](/docs/reference/javascript/api/utilities/bound-query.md) - Parameterised queries
- [surql](/docs/reference/javascript/api/utilities/surql.md) - Template tag for queries
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/relate-promise

# RelatePromise

RelatePromise provides chainable methods for configuring RELATE operations for graph relationships.

The `RelatePromise` class provides a chainable interface for configuring RELATE operations to create graph relationships (edges) between records. It extends `Promise`, allowing you to `await` it directly or chain configuration methods.

**Returned by:** [`SurrealQueryable.relate()`](/docs/reference/javascript/api/core/surreal-queryable.md#relate)

**Source:** [query/relate.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/relate.ts)

## Type parameters

- `T` - The result type (edge record type)
- `J` - Boolean indicating if result is JSON (default: `false`)

## Configuration methods

### `.unique()` {#unique}

Enforce a unique relationship constraint (only one edge between the same nodes).

```ts title="Method Syntax"
relatePromise.unique()
```

#### Returns
`RelatePromise<T, J>` - Chainable promise

#### Example

```ts
// Only allow one 'likes' edge between user and post
const edge = await db.relate(
    new RecordId('users', 'john'),
    new Table('likes'),
    new RecordId('posts', '1')
).unique();
// If edge already exists, this won't create a duplicate
```

---

### `.output()` {#output}

Specify what to return from the operation.

```ts title="Method Syntax"
relatePromise.output(fields)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>Output</code></td>
            <td><code>"NONE"</code>, <code>"AFTER"</code>, or specific field list.</td>
        </tr>
    </tbody>
</table>

#### Returns
`RelatePromise<T, J>` - Chainable promise

#### Example

```ts
const edge = await db.relate(
    new RecordId('users', 'john'),
    new Table('follows'),
    new RecordId('users', 'jane')
).output('id', 'in', 'out', 'created_at');
```

---

### `.timeout()` {#timeout}

Set a timeout for the operation.

```ts title="Method Syntax"
relatePromise.timeout(duration)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>duration</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/duration.md">Duration</a></code></td>
            <td>Maximum time to wait.</td>
        </tr>
    </tbody>
</table>

#### Returns
`RelatePromise<T, J>` - Chainable promise

---

### `.version()` {#version}

Create the relationship at a specific version.

```ts title="Method Syntax"
relatePromise.version(timestamp)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>timestamp</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/datetime.md">DateTime</a></code></td>
            <td>The version timestamp.</td>
        </tr>
    </tbody>
</table>

#### Returns
`RelatePromise<T, J>` - Chainable promise

---

### `.retry()` {#retry}

Retry the operation with exponential backoff if it fails due to a write conflict. Retry is off by default, and passing an options object (or calling with no arguments) opts the operation in.

This overrides the connection-wide default set via the [`retry`](/docs/reference/javascript/api/types/#connectoptions) option on `ConnectOptions`.

```ts title="Method Syntax"
relatePromise.retry(options?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code>Partial&lt;<a href="/docs/reference/javascript/api/types/#retryoptions">RetryOptions</a>&gt;</code></td>
            <td>Retry configuration. If omitted, retry is enabled with the default configuration.</td>
        </tr>
    </tbody>
</table>

#### Returns
`RelatePromise<T, J>` - Chainable promise

#### Example

```ts
await db.relate(alice, 'follows', bob).retry();
```

---

### `.json()` {#json}

Return result as JSON string.

```ts title="Method Syntax"
relatePromise.json()
```

#### Returns
`RelatePromise<T, true>` - Promise returning JSON string

---

### `.compile()` {#compile}

Compile the query into a BoundQuery.

```ts title="Method Syntax"
relatePromise.compile()
```

#### Returns
`BoundQuery<[T]>` - The compiled query

---

### `.stream()` {#stream}

Stream results as relationships are created.

```ts title="Method Syntax"
relatePromise.stream()
```

#### Returns
`AsyncIterableIterator` - Async iterator

## Complete examples

### Basic relationship

```ts
import { Surreal, RecordId, Table, DateTime } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Create a single relationship
const edge = await db.relate(
    new RecordId('users', 'john'),
    new Table('likes'),
    new RecordId('posts', '1'),
    { created_at: DateTime.now() }
);

console.log('Edge created:', edge.id);
console.log('From:', edge.in);  // users:john
console.log('To:', edge.out);   // posts:1
```

### Multiple relationships

```ts
// Create multiple edges at once
const edges = await db.relate(
    [new RecordId('users', 'john'), new RecordId('users', 'jane')],
    new Table('follows'),
    [new RecordId('users', 'alice'), new RecordId('users', 'bob')]
);

// Creates 4 edges:
// john->follows->alice
// john->follows->bob
// jane->follows->alice
// jane->follows->bob
```

### Unique relationships

```ts
// Prevent duplicate 'likes'
const edge = await db.relate(
    new RecordId('users', 'john'),
    new Table('likes'),
    new RecordId('posts', '1'),
    { timestamp: DateTime.now() }
).unique();

// Second call won't create duplicate
const duplicate = await db.relate(
    new RecordId('users', 'john'),
    new Table('likes'),
    new RecordId('posts', '1')
).unique();
// Returns existing edge
```

### Relationship with data

```ts
const friendship = await db.relate(
    new RecordId('users', 'john'),
    new Table('friends'),
    new RecordId('users', 'jane'),
    {
        since: DateTime.parse('2024-01-15'),
        strength: 0.8,
        mutual: true,
        tags: ['colleague', 'neighbor']
    }
);
```

### Specific edge ID

```ts
// Use specific ID for the edge
const edge = await db.relate(
    new RecordId('users', 'john'),
    new RecordId('likes', 'specific-edge-id'),
    new RecordId('posts', '1'),
    { strength: 10 }
);
```

### Fan-out relationships

```ts
// One user follows many
const edges = await db.relate(
    new RecordId('users', 'john'),
    new Table('follows'),
    [
        new RecordId('users', 'alice'),
        new RecordId('users', 'bob'),
        new RecordId('users', 'carol')
    ]
);

console.log(`Created ${edges.length} follow edges`);
```

### Streaming bulk relationships

```ts
const users = await db.select(new Table('users'));
const popularPost = new RecordId('posts', 'viral-post');

const edges = db.relate(
    users.map(u => u.id),
    new Table('viewed'),
    popularPost,
    { viewed_at: DateTime.now() }
);

for await (const edge of edges.stream()) {
    console.log(`Created view edge: ${edge.id}`);
}
```

### Bidirectional relationships

```ts
// Create friendship in both directions
await db.relate(
    new RecordId('users', 'john'),
    new Table('friends'),
    new RecordId('users', 'jane')
);

await db.relate(
    new RecordId('users', 'jane'),
    new Table('friends'),
    new RecordId('users', 'john')
);
```

### Relationship metadata

```ts
const edge = await db.relate(
    new RecordId('users', 'john'),
    new Table('rated'),
    new RecordId('movies', 'inception'),
    {
        rating: 5,
        review: 'Amazing movie!',
        watched_at: DateTime.parse('2024-01-15'),
        platform: 'Netflix'
    }
);
```

### Temporal relationships

```ts
// Track when relationship was created
const edge = await db.relate(
    new RecordId('users', 'john'),
    new Table('employed_at'),
    new RecordId('companies', 'acme'),
    {
        started_at: DateTime.parse('2024-01-01'),
        position: 'Developer',
        department: 'Engineering'
    }
);
```

### Weighted graph

```ts
// Create weighted edges for graph algorithms
const edge = await db.relate(
    new RecordId('cities', 'new-york'),
    new Table('connected_to'),
    new RecordId('cities', 'boston'),
    {
        distance: 215,  // miles
        travel_time: Duration.parse('4h'),
        cost: 50
    }
);
```

### Delete and recreate pattern

```ts
// Remove existing relationship and create new one
const userId = new RecordId('users', 'john');
const postId = new RecordId('posts', '1');

// Delete existing edge
await db.query(
    surql`DELETE FROM likes WHERE in = ${userId} AND out = ${postId}`
).collect();

// Create new edge with updated data
const edge = await db.relate(
    userId,
    new Table('likes'),
    postId,
    { created_at: DateTime.now() }
);
```

## Graph traversal example

```ts
// Create relationships
await db.relate(
    new RecordId('users', 'john'),
    new Table('follows'),
    new RecordId('users', 'jane')
);

// Query traversal
const followers = await db.query(
    surql`SELECT <-follows<-users.* AS followers FROM users:jane`
).collect();

console.log('Jane has followers:', followers);
```

## Best practices

### 1. Use unique for one-to-one

```ts
// Good: Prevent duplicate likes
await db.relate(from, new Table('likes'), to).unique();

// Avoid: Allowing duplicates
await db.relate(from, new Table('likes'), to);
// May create multiple edges
```

### 2. Include metadata

```ts
// Good: Track when relationship was created
await db.relate(from, edge, to, {
    created_at: DateTime.now(),
    source: 'web-app'
});

// Basic: No metadata
await db.relate(from, edge, to);
```

### 3. Use specific edge IDs for updates

```ts
// Good: use a specific ID so you can update the edge later
const edgeId = new RecordId('likes', [from.toString(), to.toString()]);
await db.relate(from, edgeId, to, metadata);

// Later: update by record ID
await db.update(edgeId).merge({ updated_at: DateTime.now() });
```

As of SurrealDB 3.1.5, when the edge ID already exists, `INSERT RELATION` returns an error unless you use `ON DUPLICATE KEY UPDATE`. See [Explicit edge record IDs](/docs/reference/query-language/statements/relate.md#handling-duplicate-edge-record-ids) for more details.

## See also

- [SurrealQueryable.relate()](/docs/reference/javascript/api/core/surreal-queryable.md#relate) - Method that returns RelatePromise
- [Graph relationships](/docs/reference/query-language/statements/relate.md) - SurrealQL RELATE documentation
- [RecordId](/docs/reference/javascript/api/values.md#custom-data-type-classes) - Record identifier type
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/run-promise

# RunPromise

RunPromise provides methods for executing SurrealDB functions and SurrealML models.

The `RunPromise` class provides an interface for executing SurrealDB functions and SurrealML models. It extends `Promise`, allowing you to `await` it directly or use configuration methods.

**Returned by:** [`SurrealQueryable.run()`](/docs/reference/javascript/api/core/surreal-queryable.md#run)

**Source:** [query/run.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/run.ts)

## Type parameters

- `T` - The return type of the function
- `J` - Boolean indicating if result is JSON (default: `false`)

## Configuration methods

### `.json()` {#json}

Configure the query to return the result as a JSON string.

```ts title="Method Syntax"
runPromise.json()
```

#### Returns
`RunPromise<T, true>` - Promise returning JSON string

#### Example

```ts
const jsonResult = await db.run('fn::calculate', [10, 20]).json();
console.log(typeof jsonResult); // 'string'
```

---

### `.retry()` {#retry}

Retry the operation with exponential backoff if it fails due to a write conflict. Retry is off by default, and passing an options object (or calling with no arguments) opts the operation in.

This overrides the connection-wide default set via the [`retry`](/docs/reference/javascript/api/types/#connectoptions) option on `ConnectOptions`.

```ts title="Method Syntax"
runPromise.retry(options?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code>Partial&lt;<a href="/docs/reference/javascript/api/types/#retryoptions">RetryOptions</a>&gt;</code></td>
            <td>Retry configuration. If omitted, retry is enabled with the default configuration.</td>
        </tr>
    </tbody>
</table>

#### Returns
`RunPromise<T, J>` - Chainable promise

#### Example

```ts
await db.run('fn::allocate_inventory', [productId, 10]).retry({ attempts: 3 });
```

---

### `.args()` {#args}

Check the arguments against a [Standard Schema](https://standardschema.dev) before the call is sent. The schema validates the array of arguments, so use a tuple schema. When validation fails, nothing is sent and the promise rejects with a [`SchemaValidationError`](/docs/reference/javascript/api/errors.md). The value the schema produces is what is sent.

```ts title="Method Syntax"
runPromise.args(schema)
```

#### Returns
`RunPromise<T, J>` - Chainable promise

#### Example

```ts
await db.run('fn::add', [1, 2]).args(z.tuple([z.number(), z.number()]));
```

See [Validating calls](/docs/reference/javascript/concepts/validating-calls.md).

---

### `.returns()` {#returns}

Check the result against a [Standard Schema](https://standardschema.dev) and type the promise as what the schema produces. When the result does not match, the promise rejects with a [`SchemaValidationError`](/docs/reference/javascript/api/errors.md).

```ts title="Method Syntax"
runPromise.returns(schema)
```

#### Returns
`RunPromise<Output, J>` - Chainable promise typed as the output of the schema

#### Example

```ts
const sum = await db.run('fn::add', [1, 2]).returns(z.number()); // number
```

---

### `.compile()` {#compile}

Compile the query into a BoundQuery.

```ts title="Method Syntax"
runPromise.compile()
```

#### Returns
`BoundQuery<[T]>` - The compiled query

---

### `.stream()` {#stream}

Stream the function result.

```ts title="Method Syntax"
runPromise.stream()
```

#### Returns
`AsyncIterableIterator<Frame<T, J>>` - Async iterator

## Complete examples

### Built-in functions

```ts
import { Surreal } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Time functions
const now = await db.run('time::now');
console.log('Current time:', now);

const timestamp = await db.run('time::unix');
console.log('Unix timestamp:', timestamp);

// Array functions
const unique = await db.run('array::distinct', [[1, 2, 2, 3, 3, 3]]);
console.log('Unique values:', unique); // [1, 2, 3]

// String functions
const upper = await db.run('string::uppercase', ['hello world']);
console.log(upper); // 'HELLO WORLD'

// Math functions
const rounded = await db.run('math::round', [3.14159]);
console.log(rounded); // 3

// Crypto functions
const hash = await db.run('crypto::md5', ['password123']);
console.log('Hash:', hash);
```

### Custom functions

```ts
// First, define a custom function in SurrealDB
await db.query(`
    DEFINE FUNCTION fn::calculate_total($items: array) -> number {
        RETURN math::sum($items.map(|$item| $item.price * $item.quantity));
    };
`).collect();

// Then call it
const items = [
    { price: 10, quantity: 2 },
    { price: 5, quantity: 3 }
];

const total = await db.run('fn::calculate_total', [items]);
console.log('Total:', total); // 35
```

### SurrealML models

```ts
// Run a machine learning model
const prediction = await db.run(
    'ml::predict_sentiment',
    '1.0.0', // Model version
    ['This is a great product!']
);

console.log('Sentiment:', prediction);
```

### With type safety

```ts
interface CalculationResult {
    sum: number;
    average: number;
    count: number;
}

const result = await db.run<CalculationResult>(
    'fn::calculate_stats',
    [[10, 20, 30, 40, 50]]
);

console.log('Sum:', result.sum);
console.log('Average:', result.average);
console.log('Count:', result.count);
```

### Complex function arguments

```ts
// Function with multiple arguments
const result = await db.run('fn::process_order', [
    new RecordId('users', 'john'),
    new RecordId('products', 'widget'),
    {
        quantity: 5,
        shipping_address: '123 Main St',
        payment_method: 'credit_card'
    }
]);
```

### Parameterised functions

```ts
// Function that uses session variables
await db.set('discount_rate', 0.1);

const total = await db.run('fn::calculate_price', [
    100, // base price
    // Function can access $discount_rate
]);
```

### Error handling

```ts
try {
    const result = await db.run('fn::risky_operation', [data]);
} catch (error) {
    if (error instanceof ResponseError) {
        console.error('Function failed:', error.message);
    }
}
```

### Batch function calls

```ts
const results = await db.query(`
    RETURN fn::process(${data1});
    RETURN fn::process(${data2});
    RETURN fn::process(${data3});
`).collect();

console.log('Batch results:', results);
```

### With transaction

```ts
const txn = await db.beginTransaction();

try {
    // Call function within transaction
    const result = await txn.run('fn::allocate_inventory', [
        new RecordId('products', 'widget'),
        10
    ]);
    
    // Other transaction operations
    await txn.create(new Table('orders')).content({
        product: result.product,
        allocated: result.quantity
    });
    
    await txn.commit();
} catch (error) {
    await txn.cancel();
}
```

### Scheduled functions

```ts
// Define a scheduled function
await db.query(`
    DEFINE FUNCTION fn::cleanup_old_records() {
        DELETE FROM logs WHERE created_at < time::now() - 30d;
    };
`).collect();

// Run manually
await db.run('fn::cleanup_old_records');
```

### Recursive functions

```ts
await db.query(`
    DEFINE FUNCTION fn::factorial($n: number) -> number {
        IF $n <= 1 {
            RETURN 1
        } ELSE {
            RETURN $n * fn::factorial($n - 1)
        }
    };
`).collect();

const result = await db.run('fn::factorial', [5]);
console.log('5! =', result); // 120
```

### Data transformation

```ts
// Define transformation function
await db.query(`
    DEFINE FUNCTION fn::format_user($user: record) -> object {
        RETURN {
            full_name: string::concat($user.first_name, ' ', $user.last_name),
            email: string::lowercase($user.email),
            age: time::year(time::now()) - time::year($user.birth_date)
        };
    };
`).collect();

// Use it
const user = await db.select(new RecordId('users', 'john'));
const formatted = await db.run('fn::format_user', [user]);
console.log(formatted);
```

### ML model versions

```ts
// Run specific model version
const v1Result = await db.run('ml::classify_image', '1.0.0', [imageData]);
const v2Result = await db.run('ml::classify_image', '2.0.0', [imageData]);

console.log('v1 prediction:', v1Result);
console.log('v2 prediction:', v2Result);
```

### Validation functions

```ts
await db.query(`
    DEFINE FUNCTION fn::validate_email($email: string) -> bool {
        RETURN string::is::email($email);
    };
`).collect();

const isValid = await db.run('fn::validate_email', ['user@example.com']);

if (isValid) {
    // Proceed with user creation
}
```

## Best practices

### 1. Type function results

```ts
// Good: Type the result
interface Stats {
    count: number;
    average: number;
}

const stats = await db.run<Stats>('fn::calculate_stats', [data]);
stats.count; // TypeScript knows this exists

// Avoid: Untyped
const stats = await db.run('fn::calculate_stats', [data]);
stats.count; // No type safety
```

### 2. Handle function errors

```ts
// Good: Handle errors
try {
    const result = await db.run('fn::risky_operation', [data]);
} catch (error) {
    console.error('Function failed:', error);
}

// Consider: Implement error handling in the function itself
await db.query(`
    DEFINE FUNCTION fn::safe_operation($data: any) {
        TRY {
            RETURN fn::risky_operation($data);
        } CATCH {
            RETURN { error: true, message: 'Operation failed' };
        }
    };
`).collect();
```

### 3. Validate function names

Function names must follow the pattern: `namespace::function_name`

```ts
// Good: Valid function names
await db.run('fn::my_function', []);
await db.run('custom::calculate', []);
await db.run('ml::predict', '1.0.0', []);

// Invalid: Will throw error
await db.run('invalid name', []); // No namespace
await db.run('fn:bad-name', []); // Invalid characters
```

## See also

- [SurrealQueryable.run()](/docs/reference/javascript/api/core/surreal-queryable.md#run) - Method that returns RunPromise
- [Functions](/docs/reference/query-language/statements/define/function.md) - SurrealQL function definitions
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/select-promise

# SelectPromise

SelectPromise provides chainable methods for configuring SELECT queries.

The `SelectPromise` class provides a chainable interface for configuring SELECT queries before execution. It extends `Promise`, allowing you to `await` it directly or chain configuration methods.

**Returned by:** [`SurrealQueryable.select()`](/docs/reference/javascript/api/core/surreal-queryable.md#select)

**Source:** [query/select.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/select.ts)

## Type parameters

- `T` - The result type
- `I` - The input type for field selection
- `J` - Boolean indicating if result is JSON (default: `false`)

## Configuration methods

### `.fields()` {#fields}

Specify which fields to select from the records.

```ts title="Method Syntax"
selectPromise.fields(...fields)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>Field&lt;I&gt;[]</code></td>
            <td>Field names to select.</td>
        </tr>
    </tbody>
</table>

#### Returns
`SelectPromise<T, I, J>` - Chainable promise

#### Examples

```ts title="Select Specific Fields"
const users = await db.select(new Table('users'))
    .fields('name', 'email', 'age');
// Returns: [{ name, email, age }, ...]
```

```ts title="Select Nested Fields"
const users = await db.select(new Table('users'))
    .fields('name', 'address.city', 'address.country');
```

```ts title="Select with Aggregations"
const stats = await db.select(new Table('orders'))
    .fields('count()', 'sum(total)', 'avg(items)');
```

---

### `.value()` {#value}

Select only the value of a specific field, unwrapping it from the record structure.

```ts title="Method Syntax"
selectPromise.value(field)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>field</code> <label label="required" /></td>
            <td><code>Field&lt;I&gt;</code></td>
            <td>The field name to extract.</td>
        </tr>
    </tbody>
</table>

#### Returns
`SelectPromise<T, I, J>` - Chainable promise

#### Example

```ts title="Get Array of Values"
const names = await db.select(new Table('users'))
    .value('name');
// Returns: ['John', 'Jane', 'Bob']

// Instead of: [{ name: 'John' }, { name: 'Jane' }, ...]
```

---

### `.where()` {#where}

Add a WHERE clause to filter results.

```ts title="Method Syntax"
selectPromise.where(expr)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>expr</code> <label label="required" /></td>
            <td><code>ExprLike</code></td>
            <td>The condition expression (string or <a href="/docs/reference/javascript/api/utilities/expr.md">Expression</a> object).</td>
        </tr>
    </tbody>
</table>

#### Returns
`SelectPromise<T, I, J>` - Chainable promise

#### Examples

```ts title="String Condition"
const adults = await db.select(new Table('users'))
    .where('age >= 18');
```

```ts title="With Expression Builder"
import { expr } from 'surrealdb';

const activeUsers = await db.select(new Table('users'))
    .where(expr(({ and, eq, gte, field }) =>
        and(
            eq(field('status'), 'active'),
            gte(field('last_login'), new DateTime('2024-01-01'))
        )
    ));
```

```ts title="Parameterised condition"
const users = await db.query(
    surql`SELECT * FROM users WHERE age >= ${minAge}`
).collect();
```

---

### `.fetch()` {#fetch}

Specify related fields to fetch (similar to SQL JOIN).

```ts title="Method Syntax"
selectPromise.fetch(...fields)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>string[]</code></td>
            <td>Field names representing relations to fetch.</td>
        </tr>
    </tbody>
</table>

#### Returns
`SelectPromise<T, I, J>` - Chainable promise

#### Examples

```ts title="Fetch Related Records"
const posts = await db.select(new Table('posts'))
    .fetch('author');
// Expands author RecordId to full user object
```

```ts title="Fetch Multiple Relations"
const posts = await db.select(new Table('posts'))
    .fetch('author', 'comments', 'tags');
```

```ts title="Fetch Nested Relations"
const posts = await db.select(new Table('posts'))
    .fetch('author', 'comments.author');
```

---

### `.start()` {#start}

Set the pagination offset (number of records to skip).

```ts title="Method Syntax"
selectPromise.start(start)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>start</code> <label label="required" /></td>
            <td><code>number</code></td>
            <td>Number of records to skip.</td>
        </tr>
    </tbody>
</table>

#### Returns
`SelectPromise<T, I, J>` - Chainable promise

#### Example

```ts title="Pagination"
const page = 2;
const pageSize = 10;

const users = await db.select(new Table('users'))
    .start((page - 1) * pageSize)
    .limit(pageSize);
```

---

### `.limit()` {#limit}

Limit the number of results returned.

```ts title="Method Syntax"
selectPromise.limit(limit)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>limit</code> <label label="required" /></td>
            <td><code>number</code></td>
            <td>Maximum number of records to return.</td>
        </tr>
    </tbody>
</table>

#### Returns
`SelectPromise<T, I, J>` - Chainable promise

#### Example

```ts title="Get Top 10"
const topUsers = await db.select(new Table('users'))
    .where('score > 0')
    .limit(10);
```

---

### `.timeout()` {#timeout}

Set a timeout for the query operation.

```ts title="Method Syntax"
selectPromise.timeout(duration)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>duration</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/duration.md">Duration</a></code></td>
            <td>Maximum time to wait for query completion.</td>
        </tr>
    </tbody>
</table>

#### Returns
`SelectPromise<T, I, J>` - Chainable promise

#### Example

```ts
const users = await db.select(new Table('users'))
    .timeout(Duration.parse('5s'));
```

---

### `.version()` {#version}

Select records at a specific version/timestamp (time-travel queries).

```ts title="Method Syntax"
selectPromise.version(timestamp)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>timestamp</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/datetime.md">DateTime</a></code></td>
            <td>The timestamp to query at.</td>
        </tr>
    </tbody>
</table>

#### Returns
`SelectPromise<T, I, J>` - Chainable promise

#### Example

```ts title="Query Historical Data"
const historicalUsers = await db.select(new Table('users'))
    .version(DateTime.parse('2024-01-01T00:00:00Z'));
```

---

### `.json()` {#json}

Return results as JSON strings instead of parsed objects.

```ts title="Method Syntax"
selectPromise.json()
```

#### Returns
`SelectPromise<T, I, true>` - Promise returning JSON string

#### Example

```ts
const jsonString = await db.select(new Table('users')).json();
console.log(typeof jsonString); // 'string'
```

---

### `.compile()` {#compile}

Compile the query into a `BoundQuery` without executing it.

```ts title="Method Syntax"
selectPromise.compile()
```

#### Returns
`BoundQuery<[T]>` - The compiled query

#### Example

```ts
const query = db.select(new Table('users'))
    .where('age >= 18')
    .compile();
```

---

### `.stream()` {#stream}

Stream results as they arrive instead of waiting for all results.

```ts title="Method Syntax"
selectPromise.stream()
```

#### Returns
`AsyncIterableIterator` - Async iterator for streaming results

#### Example

```ts
for await (const user of db.select(new Table('users')).stream()) {
    console.log('Received user:', user);
}
```

## Complete examples

### Basic selection

```ts
import { Surreal, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Select all
const allUsers = await db.select(new Table('users'));

// Select specific record
const user = await db.select(new RecordId('users', 'john'));
```

### Filtered selection

```ts
const activeAdults = await db.select(new Table('users'))
    .where('age >= 18 AND status = "active"')
    .fields('name', 'email', 'age');
```

### Paginated selection

```ts
function getPage(page: number, pageSize: number) {
    return db.select(new Table('users'))
        .start((page - 1) * pageSize)
        .limit(pageSize);
}

const page1 = await getPage(1, 20);
const page2 = await getPage(2, 20);
```

### Complex query with relations

```ts
const posts = await db.select(new Table('posts'))
    .where('published = true')
    .fields('title', 'content', 'author', 'created_at')
    .fetch('author', 'comments.author')
    .limit(10);

// posts[0].author is now a full User object
// posts[0].comments[0].author is also expanded
```

### Aggregation

```ts
import { expr, gte } from 'surrealdb';

const stats = await db.select(new Table('orders'))
    .where(expr(gte('created_at', DateTime.parse('2024-01-01'))))
    .fields('count() as total_orders', 'sum(amount) as total_revenue', 'avg(amount) as avg_order');
```

### Streaming large results

```ts
let count = 0;
for await (const user of db.select(new Table('users')).stream()) {
    await processUser(user);
    count++;
    if (count % 100 === 0) {
        console.log(`Processed ${count} users`);
    }
}
```

## Chaining pattern

All configuration methods return a new `SelectPromise`, allowing you to chain them in any order:

```ts
const result = await db.select(new Table('users'))
    .where('status = "active"')
    .fields('name', 'email')
    .fetch('profile')
    .start(0)
    .limit(10)
    .timeout(Duration.parse('5s'));
```

## See also

- [SurrealQueryable.select()](/docs/reference/javascript/api/core/surreal-queryable.md#select) - Method that returns SelectPromise
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes
- [Expression builders](/docs/reference/javascript/api/utilities/expr.md) - Building complex conditions

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/update-promise

# UpdatePromise

UpdatePromise provides chainable methods for configuring UPDATE operations.

The `UpdatePromise` class provides a chainable interface for configuring UPDATE operations before execution. It extends `Promise`, allowing you to `await` it directly or chain configuration methods.

**Returned by:** [`SurrealQueryable.update()`](/docs/reference/javascript/api/core/surreal-queryable.md#update)

**Source:** [query/update.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/update.ts)

## Type parameters

- `T` - The result type
- `I` - The input type for record data
- `J` - Boolean indicating if result is JSON (default: `false`)

## Configuration methods

### `.content()` {#content}

Replace the entire record content with new data.

```ts title="Method Syntax"
updatePromise.content(data)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>data</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>Complete replacement data (excluding id field).</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpdatePromise<T, I, J>` - Chainable promise

#### Example

```ts
const user = await db.update(new RecordId('users', 'john'))
    .content({
        name: 'John Smith',
        email: 'john.smith@example.com',
        age: 31
    });
// Replaces all fields
```

---

### `.merge()` {#merge}

Merge partial updates into the existing record.

```ts title="Method Syntax"
updatePromise.merge(data)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>data</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>Partial data to merge (only specified fields are updated).</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpdatePromise<T, I, J>` - Chainable promise

#### Examples

```ts title="Update Single Field"
const user = await db.update(new RecordId('users', 'john'))
    .merge({ email: 'newemail@example.com' });
// Only updates email, other fields unchanged
```

```ts title="Update Multiple Fields"
const user = await db.update(new RecordId('users', 'john'))
    .merge({
        email: 'new@example.com',
        age: 31,
        updated_at: DateTime.now()
    });
```

---

### `.replace()` {#replace}

Replace specific fields while keeping others unchanged.

```ts title="Method Syntax"
updatePromise.replace(data)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>data</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>Fields to replace.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpdatePromise<T, I, J>` - Chainable promise

#### Example

```ts
const user = await db.update(new RecordId('users', 'john'))
    .replace({ status: 'inactive' });
```

---

### `.patch()` {#patch}

Apply JSON Patch operations to update the record.

```ts title="Method Syntax"
updatePromise.patch(operations)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>operations</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>JSON Patch operations to apply.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpdatePromise<T, I, J>` - Chainable promise

#### Example

```ts
const user = await db.update(new RecordId('users', 'john'))
    .patch([
        { op: 'replace', path: '/email', value: 'new@example.com' },
        { op: 'add', path: '/tags/-', value: 'premium' }
    ]);
```

---

### `.where()` {#where}

Add a WHERE clause to conditionally update records.

```ts title="Method Syntax"
updatePromise.where(expr)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>expr</code> <label label="required" /></td>
            <td><code>ExprLike</code></td>
            <td>The condition expression (string or <a href="/docs/reference/javascript/api/utilities/expr.md">Expression</a> object).</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpdatePromise<T, I, J>` - Chainable promise

#### Examples

```ts title="Conditional Update"
const users = await db.update(new Table('users'))
    .merge({ verified: true })
    .where('email_confirmed = true');
```

```ts title="With Expression Builder"
import { expr } from 'surrealdb';

const users = await db.update(new Table('users'))
    .merge({ status: 'inactive' })
    .where(expr(({ lt, field }) => 
        lt(field('last_login'), DateTime.parse('2024-01-01'))
    ));
```

---

### `.output()` {#output}

Specify what to return from the update operation.

```ts title="Method Syntax"
updatePromise.output(fields)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>Output</code></td>
            <td><code>"NONE"</code>, <code>"BEFORE"</code>, <code>"AFTER"</code>, <code>"DIFF"</code>, or field list.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpdatePromise<T, I, J>` - Chainable promise

#### Examples

```ts title="Return Updated Record"
const user = await db.update(new RecordId('users', 'john'))
    .merge({ email: 'new@example.com' })
    .output('AFTER');
// Returns the record after update
```

```ts title="Return Only Changed Fields"
const diff = await db.update(new RecordId('users', 'john'))
    .merge({ email: 'new@example.com' })
    .output('DIFF');
// Returns only the changed fields
```

```ts title="Return Original Record"
const original = await db.update(new RecordId('users', 'john'))
    .merge({ email: 'new@example.com' })
    .output('BEFORE');
// Returns the record before update
```

---

### `.timeout()` {#timeout}

Set a timeout for the operation.

```ts title="Method Syntax"
updatePromise.timeout(duration)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>duration</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/duration.md">Duration</a></code></td>
            <td>Maximum time to wait.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpdatePromise<T, I, J>` - Chainable promise

---

### `.retry()` {#retry}

Retry the operation with exponential backoff if it fails due to a write conflict. Retry is off by default, and passing an options object (or calling with no arguments) opts the operation in.

This overrides the connection-wide default set via the [`retry`](/docs/reference/javascript/api/types/#connectoptions) option on `ConnectOptions`.

```ts title="Method Syntax"
updatePromise.retry(options?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code>Partial&lt;<a href="/docs/reference/javascript/api/types/#retryoptions">RetryOptions</a>&gt;</code></td>
            <td>Retry configuration. If omitted, retry is enabled with the default configuration.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpdatePromise<T, I, J>` - Chainable promise

#### Example

```ts
await db.update(new RecordId('counter', 'c'))
    .merge({ n: 1 })
    .retry({ attempts: 3 });
```

---

### `.json()` {#json}

Return result as JSON string.

```ts title="Method Syntax"
updatePromise.json()
```

#### Returns
`UpdatePromise<T, I, true>` - Promise returning JSON string

---

### `.compile()` {#compile}

Compile the query into a `BoundQuery` without executing it.

```ts title="Method Syntax"
updatePromise.compile()
```

#### Returns
`BoundQuery<[T]>` - The compiled query

#### Example

```ts
const query = db.update(new Table('users'))
    .merge({ status: 'active' })
    .where('verified = true')
    .compile();
```

---

### `.stream()` {#stream}

Stream results as they arrive.

```ts title="Method Syntax"
updatePromise.stream()
```

#### Returns
`AsyncIterableIterator` - Async iterator

## Complete examples

### Basic updates

```ts
import { Surreal, RecordId, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Update single record with merge
const user = await db.update(new RecordId('users', 'john'))
    .merge({ email: 'john.new@example.com' });

// Replace entire record content
const user = await db.update(new RecordId('users', 'john'))
    .content({
        name: 'John Doe',
        email: 'john@example.com',
        age: 30,
        role: 'admin'
    });
```

### Bulk updates

```ts
// Update all users matching condition
const updated = await db.update(new Table('users'))
    .merge({ verified: true })
    .where('email_confirmed = true');

console.log(`Updated ${updated.length} users`);
```

### Conditional updates

```ts
// Update only if condition is met
const users = await db.update(new Table('users'))
    .merge({ status: 'inactive' })
    .where('last_login < $date', { 
        date: DateTime.parse('2024-01-01') 
    });
```

### Complex merge

```ts
const user = await db.update(new RecordId('users', 'john'))
    .merge({
        profile: {
            bio: 'Updated bio',
            avatar: 'new-avatar.jpg'
        },
        settings: {
            notifications: true,
            theme: 'dark'
        },
        updated_at: DateTime.now()
    });
```

### Tracking changes

```ts
const diff = await db.update(new RecordId('users', 'john'))
    .merge({ 
        email: 'new@example.com',
        age: 31 
    })
    .output('DIFF');

console.log('Changed fields:', diff);
// { email: 'new@example.com', age: 31 }
```

### Batch update with stream

```ts
const updates = db.update(new Table('users'))
    .merge({ last_check: DateTime.now() })
    .where('active = true');

for await (const user of updates.stream()) {
    console.log(`Updated user: ${user.id}`);
}
```

## Difference between methods

### `.content()` vs `.merge()` vs `.replace()`

```ts
// CONTENT: Replaces entire record
await db.update(recordId).content({
    name: 'John',
    email: 'john@example.com'
});
// Result: ONLY name and email exist, all other fields removed

// MERGE: Updates specified fields only
await db.update(recordId).merge({
    email: 'john@example.com'
});
// Result: Only email updated, all other fields preserved

// REPLACE: Similar to merge but with different semantics
await db.update(recordId).replace({
    email: 'john@example.com'
});
// Result: Replaces specified fields
```

## Chaining pattern

```ts
const result = await db.update(new Table('users'))
    .merge({ status: 'active' })
    .where('verified = true')
    .output('AFTER')
    .timeout(Duration.parse('5s'));
```

## See also

- [SurrealQueryable.update()](/docs/reference/javascript/api/core/surreal-queryable.md#update) - Method that returns UpdatePromise
- [UpsertPromise](/docs/reference/javascript/api/queries/upsert-promise.md) - Insert or update
- [CreatePromise](/docs/reference/javascript/api/queries/create-promise.md) - Create records
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/queries/upsert-promise

# UpsertPromise

UpsertPromise provides chainable methods for configuring UPSERT operations (insert or replace).

The `UpsertPromise` class provides a chainable interface for configuring UPSERT operations (insert if not exists, replace if exists). It extends `Promise`, allowing you to `await` it directly or chain configuration methods.

> [!WARNING]
> UPSERT replaces the entire record if it exists. Use [`update().merge()`](/docs/reference/javascript/api/queries/update-promise.md#merge) for partial updates.

**Returned by:** [`SurrealQueryable.upsert()`](/docs/reference/javascript/api/core/surreal-queryable.md#upsert)

**Source:** [query/upsert.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/query/upsert.ts)

## Type parameters

- `T` - The result type
- `I` - The input type for record data
- `J` - Boolean indicating if result is JSON (default: `false`)

## Configuration methods

### `.content()` {#content}

Set the complete content for the record (insert or replace).

```ts title="Method Syntax"
upsertPromise.content(data)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>data</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>Complete record data (excluding id field).</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpsertPromise<T, I, J>` - Chainable promise

#### Example

```ts
const user = await db.upsert(new RecordId('users', 'john'))
    .content({
        name: 'John Doe',
        email: 'john@example.com',
        age: 30
    });
// Inserts if not exists, replaces entirely if exists
```

---

### `.merge()` {#merge}

Merge data into the record (insert if not exists, merge if exists).

```ts title="Method Syntax"
upsertPromise.merge(data)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>data</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>Partial data to merge.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpsertPromise<T, I, J>` - Chainable promise

#### Example

```ts
const user = await db.upsert(new RecordId('users', 'john'))
    .merge({
        name: 'John Doe',
        last_login: DateTime.now()
    });
// If exists: merges fields; if not: creates with these fields
```

---

### `.replace()` {#replace}

Replace specific fields.

```ts title="Method Syntax"
upsertPromise.replace(data)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>data</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>Fields to replace.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpsertPromise<T, I, J>` - Chainable promise

---

### `.patch()` {#patch}

Apply JSON Patch operations.

```ts title="Method Syntax"
upsertPromise.patch(operations)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>operations</code> <label label="required" /></td>
            <td><code>Values&lt;I&gt;</code></td>
            <td>JSON Patch operations.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpsertPromise<T, I, J>` - Chainable promise

---

### `.where()` {#where}

Add a WHERE clause for conditional upsert.

```ts title="Method Syntax"
upsertPromise.where(expr)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>expr</code> <label label="required" /></td>
            <td><code>ExprLike</code></td>
            <td>The condition expression (string or <a href="/docs/reference/javascript/api/utilities/expr.md">Expression</a> object).</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpsertPromise<T, I, J>` - Chainable promise

---

### `.output()` {#output}

Specify what to return.

```ts title="Method Syntax"
upsertPromise.output(fields)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>fields</code> <label label="required" /></td>
            <td><code>Output</code></td>
            <td><code>"NONE"</code>, <code>"BEFORE"</code>, <code>"AFTER"</code>, <code>"DIFF"</code>, or field list.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpsertPromise<T, I, J>` - Chainable promise

---

### `.timeout()` {#timeout}

Set operation timeout.

```ts title="Method Syntax"
upsertPromise.timeout(duration)
```

#### Returns
`UpsertPromise<T, I, J>` - Chainable promise

---

### `.retry()` {#retry}

Retry the operation with exponential backoff if it fails due to a write conflict. Retry is off by default, and passing an options object (or calling with no arguments) opts the operation in.

This overrides the connection-wide default set via the [`retry`](/docs/reference/javascript/api/types/#connectoptions) option on `ConnectOptions`.

```ts title="Method Syntax"
upsertPromise.retry(options?)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code>Partial&lt;<a href="/docs/reference/javascript/api/types/#retryoptions">RetryOptions</a>&gt;</code></td>
            <td>Retry configuration. If omitted, retry is enabled with the default configuration.</td>
        </tr>
    </tbody>
</table>

#### Returns
`UpsertPromise<T, I, J>` - Chainable promise

#### Example

```ts
await db.upsert(new RecordId('users', 'john'))
    .merge({ visits: 1 })
    .retry();
```

---

### `.json()` {#json}

Return result as JSON string.

```ts title="Method Syntax"
upsertPromise.json()
```

#### Returns
`UpsertPromise<T, I, true>` - Promise returning JSON string

---

### `.compile()` {#compile}

Compile the query into a `BoundQuery` without executing it.

```ts title="Method Syntax"
upsertPromise.compile()
```

#### Returns
`BoundQuery<[T]>` - The compiled query

#### Example

```ts
const query = db.upsert(new RecordId('users', 'john'))
    .content(userData)
    .compile();
```

---

### `.stream()` {#stream}

Stream results as they arrive.

```ts title="Method Syntax"
upsertPromise.stream()
```

#### Returns
`AsyncIterableIterator` - Async iterator

## Complete examples

### Basic upsert

```ts
import { Surreal, RecordId } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Upsert: insert if not exists, replace if exists
const user = await db.upsert(new RecordId('users', 'john'))
    .content({
        name: 'John Doe',
        email: 'john@example.com',
        role: 'user'
    });
```

### Upsert with merge

```ts
// Safer: merge instead of replace
const user = await db.upsert(new RecordId('users', 'john'))
    .merge({
        last_login: DateTime.now(),
        login_count: 1
    });
// If user exists: only updates these fields
// If not: creates user with these fields
```

### Bulk upsert

```ts
const users = await db.upsert(new Table('users'))
    .content(userDataArray);
```

### Track changes

```ts
const result = await db.upsert(new RecordId('users', 'john'))
    .content(userData)
    .output('DIFF');

if (result) {
    console.log('Created or updated:', result);
}
```

### Conditional upsert

```ts
const user = await db.upsert(new RecordId('users', 'john'))
    .merge({ status: 'active' })
    .where('verified = true');
```

## UPSERT vs CREATE vs UPDATE

```ts
// CREATE: Fails if record exists
try {
    await db.create(recordId).content(data);
} catch (error) {
    // Error if exists
}

// UPDATE: Fails if record doesn't exist
try {
    await db.update(recordId).merge(data);
} catch (error) {
    // Error if not found
}

// UPSERT: Works in both cases
await db.upsert(recordId).content(data);
// Always succeeds
```

## Use cases

### Session management

```ts
// Update session or create new one
async function updateSession(sessionId: string, data: SessionData) {
    return db.upsert(new RecordId('sessions', sessionId))
        .merge({
            ...data,
            last_activity: DateTime.now()
        });
}
```

### Cache pattern

```ts
// Write-through cache
async function cacheSet(key: string, value: unknown) {
    return db.upsert(new RecordId('cache', key))
        .content({
            value,
            expires_at: DateTime.now().plus(Duration.parse('1h'))
        });
}
```

### Counter pattern

```ts
// Increment counter or initialize
const counter = await db.upsert(new RecordId('counters', 'page_views'))
    .merge({
        count: 1,
        last_increment: DateTime.now()
    });
```

## Chaining pattern

```ts
const result = await db.upsert(new RecordId('users', 'john'))
    .content(userData)
    .output('AFTER')
    .timeout(Duration.parse('5s'));
```

## See also

- [SurrealQueryable.upsert()](/docs/reference/javascript/api/core/surreal-queryable.md#upsert) - Method that returns UpsertPromise
- [CreatePromise](/docs/reference/javascript/api/queries/create-promise.md) - Create only
- [UpdatePromise](/docs/reference/javascript/api/queries/update-promise.md) - Update only
- [Query overview](/docs/reference/javascript/api/queries/) - All query builder classes

---

Source: https://surrealdb.com/docs/reference/javascript/api/types

# TypeScript types

TypeScript type definitions and interfaces used throughout the SDK.

The SDK provides TypeScript type definitions for type-safe development. This page documents the key types and interfaces used throughout the SDK.

## Connection types

### `ConnectionStatus` {#connectionstatus}

Represents the current connection state.

```ts
type ConnectionStatus = "disconnected" | "connecting" | "reconnecting" | "connected"
```

**Example:**
```ts
if (db.status === "connected") {
    console.log('Ready to execute queries');
}
```

---

### `DriverOptions` {#driveroptions}

Configuration options for the Surreal driver.

```ts
interface DriverOptions {
    engines?: Engines;
    codecs?: Codecs;
    codecOptions?: CodecOptions;
    websocketImpl?: typeof WebSocket;
    fetchImpl?: typeof fetch;
}
```

**Properties:**
- `engines` - Custom engine factories for different protocols
- `codecs` - Custom codec factories for encoding/decoding
- `codecOptions` - Options for codec behaviour
- `websocketImpl` - Custom WebSocket implementation
- `fetchImpl` - Custom fetch implementation

**Example:**
```ts
const db = new Surreal({
    codecOptions: {
        useNativeDates: true
    }
});
```

---

### `ConnectOptions` {#connectoptions}

Options for establishing a connection.

```ts
interface ConnectOptions {
    namespace?: string;
    database?: string;
    authentication?: AuthProvider;
    versionCheck?: boolean;
    invalidateOnExpiry?: boolean;
    reconnect?: boolean | Partial<ReconnectOptions>;
    retry?: boolean | Partial<RetryOptions>;
}
```

**Properties:**
- `namespace` - Namespace to use
- `database` - Database to use
- `authentication` - Authentication details or provider function
- `versionCheck` - Enable version compatibility checking (default: true)
- `invalidateOnExpiry` - Invalidate session on token expiry (default: false)
- `reconnect` - Reconnection behaviour configuration (default: true)
- `retry` - Connection-wide default for retrying queries on write conflict (default: disabled)

**Example:**
```ts
await db.connect('ws://localhost:8000', {
    namespace: 'my_namespace',
    database: 'my_database',
    authentication: {
        username: 'root',
        password: 'secret'
    },
    reconnect: {
        attempts: 10,
        retryDelay: 1000
    },
    retry: {
        enabled: true,
        attempts: 5,
        retryDelay: 100
    }
});
```

---

### `ReconnectOptions` {#reconnectoptions}

Configuration for automatic reconnection behaviour.

```ts
interface ReconnectOptions {
    enabled: boolean;
    attempts: number;
    retryDelay: number;
    retryDelayMax: number;
    retryDelayMultiplier: number;
    retryDelayJitter: number;
    catch?: (error: Error) => boolean;
}
```

**Properties:**
- `enabled` - Enable automatic reconnection
- `attempts` - Maximum reconnection attempts (-1 for unlimited)
- `retryDelay` - Initial delay before reconnecting (ms)
- `retryDelayMax` - Maximum delay between attempts (ms)
- `retryDelayMultiplier` - Multiply delay after each failed attempt
- `retryDelayJitter` - Random offset percentage for delays
- `catch` - Custom error handler for reconnection errors

---

### `RetryOptions` {#retryoptions}

Configuration for retrying a query, mutation, or transaction when it fails with a write conflict. Modelled on [`ReconnectOptions`](#reconnectoptions), and applied the same way: as a connection-wide default via [`ConnectOptions.retry`](#connectoptions), or per call via `.retry()`.

```ts
interface RetryOptions {
    enabled: boolean;
    attempts: number;
    retryDelay: number;
    retryDelayMax: number;
    retryDelayMultiplier: number;
    retryDelayJitter: number;
    retryable?: (error: Error) => boolean;
}
```

**Properties:**
- `enabled` - Enable retrying on write conflict
- `attempts` - Maximum retry attempts
- `retryDelay` - Initial delay before retrying (ms)
- `retryDelayMax` - Maximum delay between attempts (ms)
- `retryDelayMultiplier` - Multiply delay after each failed attempt
- `retryDelayJitter` - Random offset percentage for delays
- `retryable` - Custom predicate deciding whether an error should be retried (default: [`isRetryableConflict`](/docs/reference/javascript/api/utilities/is-retryable-conflict.md))

Retry is off by default. Passing an options object, or calling `.retry()` with no arguments, opts a query, mutation, or transaction in. Retrying a non-atomic multi-statement query can apply some statements more than once, so it must always be enabled explicitly.

**Example:**
```ts
// Connection-wide default
await db.connect('ws://localhost:8000', {
    retry: { enabled: true, attempts: 5, retryDelay: 100 }
});

// Per-query override
const [n] = await db
    .query<[number]>('UPDATE counter:c SET n += 1 RETURN n')
    .retry({ attempts: 3 })
    .collect();
```

---

### `VersionInfo` {#versioninfo}

SurrealDB version information.

```ts
interface VersionInfo {
    version: string;
}
```

**Example:**
```ts
const info = await db.version();
console.log(info.version); // "surrealdb-2.1.0"
```

## Authentication types

### `AnyAuth` {#anyauth}

Union type for all authentication methods.

```ts
type AnyAuth = SystemAuth | AccessAuth
```

---

### `SystemAuth` {#systemauth}

Union of system-level authentication types.

```ts
type SystemAuth = RootAuth | NamespaceAuth | DatabaseAuth
```

---

### `RootAuth` {#rootauth}

Root-level authentication.

```ts
interface RootAuth {
    username: string;
    password: string;
}
```

**Example:**
```ts
await db.signin({
    username: 'root',
    password: 'secret'
});
```

---

### `NamespaceAuth` {#namespaceauth}

Namespace-level authentication.

```ts
interface NamespaceAuth {
    namespace: string;
    username: string;
    password: string;
}
```

**Example:**
```ts
await db.signin({
    namespace: 'my_namespace',
    username: 'ns_user',
    password: 'ns_pass'
});
```

---

### `DatabaseAuth` {#databaseauth}

Database-level authentication.

```ts
interface DatabaseAuth {
    namespace: string;
    database: string;
    username: string;
    password: string;
}
```

**Example:**
```ts
await db.signin({
    namespace: 'my_namespace',
    database: 'my_database',
    username: 'db_user',
    password: 'db_pass'
});
```

---

### `AccessAuth` {#accessauth}

Union of access-based authentication types.

```ts
type AccessAuth = AccessSystemAuth | AccessBearerAuth | AccessRecordAuth
```

---

### `AccessSystemAuth` {#accesssystemauth}

System access authentication with credentials.

```ts
interface AccessSystemAuth {
    namespace: string;
    database: string;
    access: string;
    username: string;
    password: string;
}
```

---

### `AccessBearerAuth` {#accessbearerauth}

Bearer token access authentication.

```ts
interface AccessBearerAuth {
    namespace: string;
    database: string;
    access: string;
    token: string;
}
```

---

### `AccessRecordAuth` {#accessrecordauth}

Record user authentication via access methods.

```ts
interface AccessRecordAuth {
    namespace: string;
    database: string;
    access: string;
    variables?: Record<string, unknown>;
}
```

**Example:**
```ts
await db.signup({
    namespace: 'my_namespace',
    database: 'my_database',
    access: 'user_access',
    variables: {
        email: 'user@example.com',
        password: 'password123'
    }
});
```

---

### `Token` {#token}

A string alias representing an authentication token (JWT).

```ts
type Token = string
```

---

### `Tokens` {#tokens}

Authentication token pair.

```ts
interface Tokens {
    access: Token;
    refresh?: Token;
}
```

**Example:**
```ts
const tokens = await db.signin(credentials);
console.log(tokens.access); // JWT access token
console.log(tokens.refresh); // Optional refresh token
```

---

### `AuthProvider` {#authprovider}

Function or static value for providing authentication.

```ts
type AuthProvider = 
    | AnyAuth 
    | (() => AnyAuth | Promise<AnyAuth>)
```

**Example:**
```ts
await db.connect('ws://localhost:8000', {
    authentication: async () => ({
        username: await getUsername(),
        password: await getPassword()
    })
});
```

## Session types

### `Session` {#session}

Session identifier type.

```ts
type Session = Uuid | undefined
```

---

### `NamespaceDatabase` {#namespacedatabase}

Namespace and database pair.

```ts
interface NamespaceDatabase {
    namespace?: string;
    database?: string;
}
```

**Example:**
```ts
await db.use({
    namespace: 'production',
    database: 'main'
});
```

---

### `SessionEvents` {#sessionevents}

Events emitted by sessions.

```ts
type SessionEvents = {
    auth: [Tokens | null];
    using: [NamespaceDatabase];
}
```

---

### `SurrealEvents` {#surrealevents}

Events emitted by Surreal instances.

```ts
type SurrealEvents = SessionEvents & {
    connecting: [];
    connected: [string];
    reconnecting: [];
    disconnected: [];
    error: [Error];
}
```

## Query types

### `RecordResult<T>` {#recordresult}

Ensures records have an `id` field of type `RecordId`.

```ts
type RecordResult<T> = T extends object
    ? { id: RecordId } & T
    : { id: RecordId }
```

**Example:**
```ts
interface User {
    name: string;
    email: string;
}

const user: RecordResult<User> = await db.select(new RecordId('users', 'john'));
console.log(user.id); // RecordId
console.log(user.name); // string
```

---

### `QueryResponse<T>` {#queryresponse}

Response from a query execution.

```ts
type QueryResponse<T = unknown> = 
    | QueryResponseSuccess<T> 
    | QueryResponseFailure

interface QueryResponseSuccess<T> {
    success: true;
    stats?: QueryStats;
    type: "live" | "kill" | "other";
    result: T;
}

interface QueryResponseFailure {
    success: false;
    stats?: QueryStats;
    error: {
        code: number;
        message: string;
    };
}
```

**Example:**
```ts
const responses = await db.query('SELECT * FROM users').responses();

for (const response of responses) {
    if (response.success) {
        console.log('Result:', response.result);
    } else {
        console.error('Error:', response.error.message);
    }
}
```

---

### `QueryStats` {#querystats}

Query execution statistics.

```ts
interface QueryStats {
    recordsReceived: number;
    bytesReceived: number;
    recordsScanned: number;
    bytesScanned: number;
    duration: Duration;
}
```

---

### `Output` {#output}

Output format for query results.

```ts
type Output = "full" | "diff" | "none"
```

---

### `Mutation` {#mutation}

Represents a mutation event from a live query.

```ts
interface Mutation<T = unknown> {
    action: "CREATE" | "UPDATE" | "DELETE";
    result: T;
}
```

---

### `LiveResource` {#liveresource}

Resources that can be subscribed to with live queries.

```ts
type LiveResource = Table
```

---

### `LiveMessage` {#livemessage}

Message received from a live query subscription.

```ts
interface LiveMessage<T = unknown> {
    action: "CREATE" | "UPDATE" | "DELETE";
    result: T;
    diff?: unknown;
}
```

**Example:**
```ts
for await (const message of subscription) {
    console.log(`${message.action}:`, message.result);
}
```

## Value types

### `RecordIdValue` {#recordidvalue}

Valid types for record ID components.

```ts
type RecordIdValue = 
    | string 
    | number 
    | Uuid 
    | bigint 
    | unknown[] 
    | Record<string, unknown>
```

---

### `AnyRecordId` {#anyrecordid}

Union type representing any record identifier.

```ts
type AnyRecordId = RecordId | RecordIdRange
```

---

### `Values<T>` {#values}

Extract values from a type, excluding `id` field.

```ts
type Values<T> = Omit<T, 'id'>
```

**Example:**
```ts
interface User {
    id: RecordId;
    name: string;
    email: string;
}

const userData: Values<User> = {
    name: 'John',
    email: 'john@example.com'
    // id is excluded
};
```

---

### `Nullable<T>` {#nullable}

Make properties nullable.

```ts
type Nullable<T> = {
    [K in keyof T]: T[K] | null;
}
```

## Codec types

### `CodecOptions` {#codecoptions}

Options for value encoding/decoding.

```ts
interface CodecOptions {
    useNativeDates?: boolean;
    valueEncodeVisitor?: (value: unknown) => unknown;
    valueDecodeVisitor?: (value: unknown) => unknown;
}
```

**Properties:**
- `useNativeDates` - Use native Date objects instead of DateTime (loses nanosecond precision)
- `valueEncodeVisitor` - Custom function to transform values before encoding
- `valueDecodeVisitor` - Custom function to transform values after decoding

**Example:**
```ts
const db = new Surreal({
    codecOptions: {
        useNativeDates: true,
        valueDecodeVisitor: (value) => {
            // Custom transformation
            return value;
        }
    }
});
```

## Export/import types

### `SqlExportOptions` {#sqlexportoptions}

Options for database export.

```ts
interface SqlExportOptions {
    users: boolean;
    accesses: boolean;
    params: boolean;
    functions: boolean;
    analyzers: boolean;
    tables: boolean | string[];
    versions: boolean;
    records: boolean;
    sequences: boolean;
    v3: boolean;
}
```

The `v3` option controls whether to include v3-specific export content.

**Example:**
```ts
const sql = await db.export({
    tables: ['users', 'posts'],
    records: true,
    functions: false
});
```

---

### `MlExportOptions` {#mlexportoptions}

Options for exporting a machine learning model.

```ts
interface MlExportOptions {
    name: string;
    version: string;
}
```

**Example:**
```ts
const model = await db.export({
    name: 'prediction-model',
    version: '1.0.0'
});
```

## Utility types

### `Prettify<T>` {#prettify}

Expand type for better IDE display.

```ts
type Prettify<T> = { [K in keyof T]: T[K] } & {}
```

---

### `EventPublisher<T>` {#eventpublisher}

Interface for event subscription.

```ts
interface EventPublisher<T extends Record<string, unknown[]>> {
    subscribe<K extends keyof T>(
        event: K,
        listener: (...payload: T[K]) => void
    ): () => void;
}
```

---

### `ApiRequest<T>` {#apirequest}

Request options for user-defined API endpoints.

```ts
interface ApiRequest<T = unknown> {
    body?: T;
    method?: string;
    headers?: Record<string, string>;
    query?: Record<string, string>;
}
```

**Properties:**
- `body` - Request body to send
- `method` - HTTP method (default: `"get"`)
- `headers` - Additional headers for the request
- `query` - Query parameters to append to the URL

**Example:**
```ts
const api = db.api();
const result = await api.invoke('/custom', {
    method: 'post',
    body: { data: 'value' },
    headers: { 'X-Custom': 'header' },
    query: { filter: 'active' }
});
```

## Best practices

### 1. Use generic type parameters

Use generics for type-safe operations:

```ts
interface User {
    name: string;
    email: string;
}

// Type-safe selection
const users = await db.select<User>(new Table('users'));
users[0].name; // TypeScript knows this is a string
```

### 2. Define custom types

Create types for your data models:

```ts
interface Post {
    title: string;
    content: string;
    author: RecordId<'users'>;
    created_at: DateTime;
}

const posts = await db.select<Post>(new Table('posts'));
```

### 3. Use type guards

Implement type guards for runtime type checking:

```ts
function isUser(value: unknown): value is User {
    return (
        typeof value === 'object' &&
        value !== null &&
        'name' in value &&
        'email' in value
    );
}

if (isUser(data)) {
    console.log(data.email); // Type-safe
}
```

### 4. Handle union types

Properly handle discriminated unions:

```ts
const response = await db.query('SELECT * FROM users').responses();

for (const r of response) {
    if (r.success) {
        console.log(r.result); // Success case
    } else {
        console.error(r.error); // Failure case
    }
}
```

## See also

- [Core classes](/docs/reference/javascript/api/core/) - Classes using these types
- [Value types](/docs/reference/javascript/api/values/) - Value type classes
- [Query builders](/docs/reference/javascript/api/queries/) - Query builder types

**Source:** [types/](https://github.com/surrealdb/surrealdb.js/tree/main/packages/sdk/src/types)

---

Source: https://surrealdb.com/docs/reference/javascript/api/utilities/bound-query

# BoundQuery

Parameterised query class for safe query composition.

The `BoundQuery` class represents a parameterised SurrealQL query with bound variables, providing safe query composition and preventing SQL injection.

**Import:**
```ts
import { BoundQuery } from 'surrealdb';
```

**Source:** [utils/bound-query.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/utils/bound-query.ts)

## Type parameters

- `R extends unknown[]` - Array of result types for the query

## Constructor

### `new BoundQuery(query?, bindings?)` {#constructor}

Create a new bound query.

```ts title="Syntax"
new BoundQuery() // Empty query
new BoundQuery(boundQuery) // Clone existing
new BoundQuery(query, bindings?) // From string and bindings
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>query</code> <label label="optional" /></td>
            <td><code>string | BoundQuery</code></td>
            <td>Query string or existing BoundQuery to clone.</td>
        </tr>
        <tr>
            <td><code>bindings</code> <label label="optional" /></td>
            <td><code>Record&lt;string, unknown&gt;</code></td>
            <td>Parameter bindings.</td>
        </tr>
    </tbody>
</table>

#### Examples

```ts
// Empty query
const query = new BoundQuery();

// From string
const query = new BoundQuery('SELECT * FROM users');

// With bindings
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $age',
    { age: 18 }
);

// Clone existing
const clone = new BoundQuery(existingQuery);
```

## Properties

### `query` {#query}

The query string with parameter placeholders.

**Type:** `string`

```ts
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $age',
    { age: 18 }
);

console.log(query.query);
// 'SELECT * FROM users WHERE age > $age'
```

---

### `bindings` {#bindings}

A copy of the parameter bindings.

**Type:** `Record<string, unknown>`

```ts
console.log(query.bindings);
// { age: 18 }
```

## Methods

### `.append()` {#append}

Append another query or string to this query.

```ts title="Method Syntax"
query.append(other)
query.append(queryString, bindings?)
query.append`template ${value}`
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code></td>
            <td><code>BoundQuery | string | TemplateStringsArray</code></td>
            <td>Query to append.</td>
        </tr>
        <tr>
            <td><code>bindings</code> <label label="optional" /></td>
            <td><code>Record&lt;string, unknown&gt;</code></td>
            <td>Bindings for the appended query.</td>
        </tr>
    </tbody>
</table>

#### Returns
`this` - Chainable

#### Examples

```ts title="Append BoundQuery"
const base = new BoundQuery('SELECT * FROM users WHERE 1=1');
const filter = new BoundQuery(' AND age > $age', { age: 18 });

base.append(filter);
```

```ts title="Append String"
const query = new BoundQuery('SELECT * FROM users');
query.append(' WHERE active = $active', { active: true });
```

```ts title="Append with Template"
const query = new BoundQuery('SELECT * FROM users');
const status = 'active';
query.append` WHERE status = ${status}`;
```

---

### `.toString()` {#tostring}

Get the query string.

```ts title="Method Syntax"
query.toString()
```

#### Returns
`string` - The query string

## Complete examples

### Basic parameterised query

```ts
import { BoundQuery } from 'surrealdb';

const query = new BoundQuery(
    'SELECT * FROM users WHERE age >= $minAge AND status = $status',
    {
        minAge: 18,
        status: 'active'
    }
);

const [users] = await db.query(query).collect();
```

### Building queries incrementally

```ts
// Start with base query
const query = new BoundQuery('SELECT * FROM products WHERE 1=1');

// Add conditions dynamically
if (category) {
    query.append(' AND category = $category', { category });
}

if (minPrice) {
    query.append(' AND price >= $minPrice', { minPrice });
}

if (maxPrice) {
    query.append(' AND price <= $maxPrice', { maxPrice });
}

query.append(' ORDER BY created_at DESC LIMIT $limit', { limit: 10 });

const [products] = await db.query(query).collect();
```

### Query builder pattern

```ts
class QueryBuilder {
    private query: BoundQuery;
    
    constructor(table: string) {
        this.query = new BoundQuery(`SELECT * FROM ${table} WHERE 1=1`);
    }
    
    where(field: string, value: unknown): this {
                this.query.append(` AND ${field} = $${field}`,
            { [field]: value });
        return this;
    }
    
    limit(count: number): this {
        this.query.append(' LIMIT $limit', { limit: count });
        return this;
    }
    
    build(): BoundQuery {
        return this.query;
    }
}

// Usage
const builder = new QueryBuilder('users');
const query = builder
    .where('status', 'active')
    .where('verified', true)
    .limit(10)
    .build();

const [users] = await db.query(query).collect();
```

### Reusable query fragments

```ts
// Define reusable fragments
const activeFilter = new BoundQuery('status = $status',
    { status: 'active' });
const verifiedFilter = new BoundQuery('verified = $verified',
    { verified: true });

// Combine them
const query = new BoundQuery('SELECT * FROM users WHERE ');
query.append(activeFilter);
query.append(' AND ');
query.append(verifiedFilter);

const [users] = await db.query(query).collect();
```

### Complex multi-statement query

```ts
const userId = new RecordId('users', 'john');
const postData = {
    title: 'My Post',
    content: 'Content here'
};

const query = new BoundQuery();

query.append('BEGIN TRANSACTION;');

query.append(
    'UPDATE $userId SET post_count += 1;',
    { userId }
);

query.append(
        'CREATE posts SET author = $author, title = $title,
        content = $content;',
    {
        author: userId,
        title: postData.title,
        content: postData.content
    }
);

query.append('COMMIT TRANSACTION;');

await db.query(query).collect();
```

### Pagination helper

```ts
function paginatedQuery(
    table: string,
    page: number,
    pageSize: number,
    filters?: Record<string, unknown>
): BoundQuery {
    const query = new BoundQuery(`SELECT * FROM ${table} WHERE 1=1`);
    
    if (filters) {
        for (const [key, value] of Object.entries(filters)) {
            query.append(` AND ${key} = $${key}`, { [key]: value });
        }
    }
    
    const offset = (page - 1) * pageSize;
    query.append(' START $offset LIMIT $limit', {
        offset,
        limit: pageSize
    });
    
    return query;
}

// Usage
const query = paginatedQuery('users', 2, 20, { status: 'active' });
const [users] = await db.query(query).collect();
```

## Best practices

### 1. Use surql template instead

For most cases, the `surql` template is easier:

```ts
// Good: surql template (recommended)
const query = surql`SELECT * FROM users WHERE age > ${age}`;

// Also good: BoundQuery (more manual)
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $age',
    { age }
);
```

### 2. Validate parameter names

```ts
// Good: Consistent parameter naming
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $age AND status = $status',
    { age: 18, status: 'active' }
);

// Avoid: Mismatched names
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $minAge',
    { age: 18 } // Wrong key name
);
```

### 3. Use append() for dynamic queries

```ts
// Good: Incremental building
const query = new BoundQuery('SELECT * FROM users WHERE 1=1');
if (filter) {
    query.append(' AND status = $status', { status: filter });
}

// Avoid: String concatenation
let queryStr = 'SELECT * FROM users WHERE 1=1';
if (filter) {
    queryStr += ` AND status = '${filter}'`; // Unsafe!
}
```

## See also

- [surql](/docs/reference/javascript/api/utilities/surql.md) - Template tag for queries
- [Query](/docs/reference/javascript/api/queries/query.md) - Query execution class
- [SurrealQueryable.query()](/docs/reference/javascript/api/core/surreal-queryable.md#query) - Query method
- [expr](/docs/reference/javascript/api/utilities/expr.md) - Expression builder

---

Source: https://surrealdb.com/docs/reference/javascript/api/utilities/equals

# equals

Deep equality comparison for SurrealDB values and JavaScript types.

The `equals()` function performs deep equality comparison for values, including SurrealDB-specific types that may not compare correctly with JavaScript's `===` operator.

**Import:**
```ts
import { equals } from 'surrealdb';
```

**Source:** [utils/equals.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/utils/equals.ts)

## Function signature

```ts
function equals(x: unknown, y: unknown): boolean
```

### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>x</code> <label label="required" /></td>
            <td><code>unknown</code></td>
            <td>First value to compare.</td>
        </tr>
        <tr>
            <td><code>y</code> <label label="required" /></td>
            <td><code>unknown</code></td>
            <td>Second value to compare.</td>
        </tr>
    </tbody>
</table>

### Returns
`boolean` - `true` if values are deeply equal, `false` otherwise

## What it compares

The `equals()` function correctly handles:
- **Primitives** - strings, numbers, booleans, null, undefined
- **SurrealDB Types** - RecordId, DateTime, Duration, Decimal, Uuid, etc.
- **Dates** - JavaScript Date objects
- **Regular Expressions** - RegExp patterns
- **Objects** - Deep object comparison
- **Arrays** - Deep array comparison
- **Mixed types** - Proper handling of bigint/number comparisons

## Examples

### Primitive comparisons

```ts
import { equals } from 'surrealdb';

console.log(equals(42, 42)); // true
console.log(equals('hello', 'hello')); // true
console.log(equals(true, false)); // false
console.log(equals(null, null)); // true
```

### SurrealDB type comparisons

```ts
import { RecordId, DateTime, Uuid, Decimal, equals } from 'surrealdb';

// RecordId comparison
const id1 = new RecordId('users', 'john');
const id2 = new RecordId('users', 'john');
const id3 = new RecordId('users', 'jane');

console.log(equals(id1, id2)); // true
console.log(equals(id1, id3)); // false

// DateTime comparison (including nanoseconds)
const dt1 = new DateTime('2024-01-15T12:00:00.123456789Z');
const dt2 = new DateTime('2024-01-15T12:00:00.123456789Z');

console.log(equals(dt1, dt2)); // true

// Uuid comparison
const uuid1 = Uuid.parse('550e8400-e29b-41d4-a716-446655440000');
const uuid2 = Uuid.parse('550e8400-e29b-41d4-a716-446655440000');

console.log(equals(uuid1, uuid2)); // true

// Decimal comparison (arbitrary precision)
const dec1 = new Decimal('19.99');
const dec2 = new Decimal('19.99');

console.log(equals(dec1, dec2)); // true
```

### Object comparisons

```ts
const obj1 = { name: 'John', age: 30 };
const obj2 = { name: 'John', age: 30 };
const obj3 = { name: 'John', age: 31 };

console.log(equals(obj1, obj2)); // true
console.log(equals(obj1, obj3)); // false

// Deep nested objects
const deep1 = {
    user: {
        profile: {
            name: 'John'
        }
    }
};
const deep2 = {
    user: {
        profile: {
            name: 'John'
        }
    }
};

console.log(equals(deep1, deep2)); // true
```

### Array comparisons

```ts
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
const arr3 = [1, 2, 4];

console.log(equals(arr1, arr2)); // true
console.log(equals(arr1, arr3)); // false

// Arrays with SurrealDB types
const ids1 = [
    new RecordId('users', 'john'),
    new RecordId('users', 'jane')
];
const ids2 = [
    new RecordId('users', 'john'),
    new RecordId('users', 'jane')
];

console.log(equals(ids1, ids2)); // true
```

### Mixed type comparisons

```ts
// bigint and number comparison
console.log(equals(42n, 42)); // true
console.log(equals(42, 42n)); // true

// Date comparison
const date1 = new Date('2024-01-15');
const date2 = new Date('2024-01-15');

console.log(equals(date1, date2)); // true
```

## Use cases

### Checking record existence

```ts
async function hasRecord(recordId: RecordId): Promise<boolean> {
    const records = await db.select(new Table(recordId.tb));
    
    return records.some(record => equals(record.id, recordId));
}

const exists = await hasRecord(new RecordId('users', 'john'));
```

### Deduplication

```ts
function deduplicate<T>(items: T[]): T[] {
    const unique: T[] = [];
    
    for (const item of items) {
        if (!unique.some(u => equals(u, item))) {
            unique.push(item);
        }
    }
    
    return unique;
}

const recordIds = [
    new RecordId('users', 'john'),
    new RecordId('users', 'jane'),
    new RecordId('users', 'john'), // Duplicate
];

const uniqueIds = deduplicate(recordIds);
console.log(uniqueIds.length); // 2
```

### Change detection

```ts
function hasChanged<T>(before: T, after: T): boolean {
    return !equals(before, after);
}

const originalUser = await db.select(userId);
// ... user makes changes ...
const updatedUser = await db.select(userId);

if (hasChanged(originalUser, updatedUser)) {
    console.log('User data was modified');
}
```

### Array difference

```ts
function findNewItems<T>(oldList: T[], newList: T[]): T[] {
    return newList.filter(newItem => 
        !oldList.some(oldItem => equals(oldItem, newItem))
    );
}

const oldTags = ['javascript', 'typescript'];
const newTags = ['javascript', 'typescript', 'react'];

const added = findNewItems(oldTags, newTags);
console.log(added); // ['react']
```

### Caching / memoization

```ts
class Cache<K, V> {
    private cache = new Map<string, { key: K; value: V }>();
    
    set(key: K, value: V): void {
        const entry = { key, value };
        this.cache.set(JSON.stringify(key), entry);
    }
    
    get(key: K): V | undefined {
        for (const entry of this.cache.values()) {
            if (equals(entry.key, key)) {
                return entry.value;
            }
        }
        return undefined;
    }
}

// Use with complex keys
const cache = new Cache<RecordId, User>();
const userId = new RecordId('users', 'john');

cache.set(userId, userData);
const cached = cache.get(new RecordId('users', 'john')); // Found!
```

### Record comparison

```ts
interface User {
    id: RecordId;
    name: string;
    email: string;
    settings: {
        theme: string;
        notifications: boolean;
    };
}

const user1: User = await db.select(new RecordId('users', 'john'));
const user2: User = await db.select(new RecordId('users', 'john'));

// Deep comparison including nested objects and RecordId
console.log(equals(user1, user2)); // true
```

## Why not use `===` or `==`?

JavaScript's equality operators don't work correctly for:

```ts
// Problem 1: Object references
const obj1 = { name: 'John' };
const obj2 = { name: 'John' };
console.log(obj1 === obj2); // false (different references)
console.log(equals(obj1, obj2)); // true (same content)

// Problem 2: SurrealDB types
const id1 = new RecordId('users', 'john');
const id2 = new RecordId('users', 'john');
console.log(id1 === id2); // false (different instances)
console.log(equals(id1, id2)); // true (same value)

// Problem 3: Date comparison
const date1 = new Date('2024-01-15');
const date2 = new Date('2024-01-15');
console.log(date1 === date2); // false
console.log(equals(date1, date2)); // true

// Problem 4: Arrays
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false
console.log(equals(arr1, arr2)); // true
```

## Best practices

### 1. Use for value comparisons

```ts
// Good: Deep equality
if (equals(recordId1, recordId2)) {
    // Same record
}

// Avoid: Reference equality (wrong for objects)
if (recordId1 === recordId2) {
    // Only true if same instance
}
```

### 2. Use for complex type comparisons

```ts
// Good: Proper comparison
const sameDateTime = equals(
    new DateTime('2024-01-15T12:00:00.123456789Z'),
    new DateTime('2024-01-15T12:00:00.123456789Z')
); // true

// Avoid: Direct comparison
const wrong = dt1 === dt2; // false (different instances)
```

### 3. Prefer built-in `.equals()` for single types

```ts
const id1 = new RecordId('users', 'john');
const id2 = new RecordId('users', 'john');

// Both are fine:
console.log(equals(id1, id2)); // true
console.log(id1.equals(id2)); // true

// Use id1.equals(id2) when you know both are RecordId
// Use equals(id1, id2) for generic comparisons
```

## See also

- [Data types](/docs/reference/javascript/api/values/) - SurrealDB data types
- [RecordId.equals()](/docs/reference/javascript/api/values/record-id.md#equals) - RecordId-specific comparison
- [DateTime.equals()](/docs/reference/javascript/api/values/datetime.md#equals) - DateTime-specific comparison

---

Source: https://surrealdb.com/docs/reference/javascript/api/utilities/escape

# Escape functions

Functions for escaping identifiers and values in SurrealQL queries.

Escape functions provide safe handling of identifiers and values in SurrealQL queries when you need to construct queries manually.

> [!NOTE: Tip]
> Prefer using [`surql`](/docs/reference/javascript/api/utilities/surql.md) or [`BoundQuery`](/docs/reference/javascript/api/utilities/bound-query.md) for automatic parameterisation. Use escape functions only when absolutely necessary.

**Import:**
```ts
import { 
    escapeIdent,
    escapeNumber,
    escapeIdPart,
    escapeRangeBound
} from 'surrealdb';
```

**Source:** [utils/escape.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/utils/escape.ts)

## Functions

### `escapeIdent(name)` {#escapeident}

Escape table names, field names, and other identifiers.

```ts title="Signature"
function escapeIdent(name: string): string
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>name</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The identifier to escape.</td>
        </tr>
    </tbody>
</table>

#### Returns
`string` - Escaped identifier

#### Examples

```ts
import { escapeIdent } from 'surrealdb';

// Simple identifiers (no escaping needed)
console.log(escapeIdent('users')); // 'users'
console.log(escapeIdent('first_name')); // 'first_name'

// Special characters (wrapped in backticks)
console.log(escapeIdent('user-table')); // '`user-table`'
console.log(escapeIdent('my table')); // '`my table`'
console.log(escapeIdent('user.name')); // '`user.name`'

// Reserved keywords
console.log(escapeIdent('select')); // '`select`'
console.log(escapeIdent('from')); // '`from`'
```

---

### `escapeNumber(num)` {#escapenumber}

Escape a number to be used as a valid SurrealQL ident.

```ts title="Signature"
function escapeNumber(num: number | bigint): string
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>num</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>The number to escape.</td>
        </tr>
    </tbody>
</table>

#### Returns
`string` - Escaped number representation

#### Examples

```ts
import { escapeNumber } from 'surrealdb';

console.log(escapeNumber(123));    // '123'
console.log(escapeNumber(42n));    // '42'
```

---

### `escapeIdPart(id)` {#escapeidpart}

Escape a record ID value part. Handles `Uuid`, `string`, `number`, `bigint`, and object values.

```ts title="Signature"
function escapeIdPart(id: RecordIdValue): string
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>id</code> <label label="required" /></td>
            <td><code>RecordIdValue</code></td>
            <td>The record ID value part to escape.</td>
        </tr>
    </tbody>
</table>

#### Returns
`string` - Escaped record ID part

#### Examples

```ts
import { escapeIdPart } from 'surrealdb';

// String IDs
console.log(escapeIdPart('john'));        // 'john' or escaped equivalent

// Numeric IDs
console.log(escapeIdPart(123));           // '123'
console.log(escapeIdPart(42n));           // '42'

// UUID values
console.log(escapeIdPart(new Uuid('...')));
```

---

### `escapeRangeBound(bound)` {#escaperangebound}

Escape a range bound value for use in SurrealQL range expressions.

```ts title="Signature"
function escapeRangeBound<T>(bound: Bound<T>): string
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>bound</code> <label label="required" /></td>
            <td><code>Bound&lt;T&gt;</code></td>
            <td>The range bound to escape.</td>
        </tr>
    </tbody>
</table>

#### Returns
`string` - Escaped range bound representation

## Complete examples

### Dynamic table names

```ts
import { escapeIdent } from 'surrealdb';

async function selectFromTable(tableName: string) {
    // Validate and escape table name
    const safeTable = escapeIdent(tableName);
    
    // Use in query (still prefer Table class)
    const query = `SELECT * FROM ${safeTable}`;
    const [results] = await db.query(query).collect();
    
    return results;
}

await selectFromTable('user-sessions'); // Safe
```

### Dynamic field selection

```ts
import { escapeIdent } from 'surrealdb';

async function selectFields(table: string, fields: string[]) {
    const escapedFields = fields.map(escapeIdent).join(', ');
    const escapedTable = escapeIdent(table);
    
    const query = `SELECT ${escapedFields} FROM ${escapedTable}`;
    const [results] = await db.query(query).collect();
    
    return results;
}

await selectFields('users', ['first-name', 'last-name', 'email']);
```

### Using surql instead (recommended)

```ts
// Prefer surql for safe parameterisation
const filters = { status: 'active', age: 18 };
const query = surql`
    SELECT * FROM users 
    WHERE status = ${filters.status} 
    AND age = ${filters.age}
`;
```

## When to use

### ✅ Use escape functions when:
- Constructing queries with user-provided table/field names
- Working with identifiers that have special characters
- Building dynamic schema definitions
- Interfacing with external query builders

### ❌ Prefer other solutions:
- **For values:** Use [`surql`](/docs/reference/javascript/api/utilities/surql.md) or [`BoundQuery`](/docs/reference/javascript/api/utilities/bound-query.md)
- **For tables:** Use [`Table`](/docs/reference/javascript/api/values/table.md) class
- **For record IDs:** Use [`RecordId`](/docs/reference/javascript/api/values/record-id.md) class
- **For conditions:** Use [`expr`](/docs/reference/javascript/api/utilities/expr.md)

## Best practices

### 1. Prefer type-safe alternatives

```ts
// Good: Type-safe
const table = new Table('users');
const users = await db.select(table);

// Avoid: Manual escaping
const escaped = escapeIdent('users');
const users = await db.query(`SELECT * FROM ${escaped}`).collect();
```

### 2. Validate before escaping

```ts
// Good: Validate first
function safeQuery(tableName: string) {
    if (!isValidTable(tableName)) {
        throw new Error('Invalid table name');
    }
    
    const escaped = escapeIdent(tableName);
    return `SELECT * FROM ${escaped}`;
}

// Avoid: Blind escaping
function unsafeQuery(tableName: string) {
    return `SELECT * FROM ${escapeIdent(tableName)}`;
}
```

### 3. Use surql for complex queries

```ts
// Good: Automatic parameterisation
const query = surql`SELECT * FROM users WHERE name = ${name}`;

// Avoid: Manual string construction
const query = `SELECT * FROM users WHERE name = '${name}'`;
```

## Security considerations

> [!WARNING]
> Escaping functions are NOT a complete defence against SQL injection. Always prefer parameterised queries using `surql` or `BoundQuery`.

```ts
// Secure: Parameterised
const query = surql`SELECT * FROM users WHERE name = ${userInput}`;

// Insecure: No escaping
const query = `SELECT * FROM users WHERE name = '${userInput}'`;
```

## See also

- [surql](/docs/reference/javascript/api/utilities/surql.md) - Recommended for parameterised queries
- [BoundQuery](/docs/reference/javascript/api/utilities/bound-query.md) - Parameterised query class
- [Table](/docs/reference/javascript/api/values/table.md) - Type-safe table references
- [RecordId](/docs/reference/javascript/api/values/record-id.md) - Type-safe record identifiers

---

Source: https://surrealdb.com/docs/reference/javascript/api/utilities/expr

# expr

Type-safe expression builder for constructing SurrealQL conditions.

The `expr()` function creates type-safe SurrealQL expressions using standalone operator functions, providing an alternative to writing raw SurrealQL strings.

**Import:**
```ts
import { 
    expr,
    eq, eeq, ne,
    gt, gte, lt, lte,
    and, or, not,
    contains, containsAny, containsAll, containsNone,
    inside, outside, intersects,
    matches, knn,
    between,
    raw
} from 'surrealdb';
```

**Source:** [utils/expr.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/utils/expr.ts)

## Function signature

```ts
function expr(expression: ExprLike): BoundQuery
```

### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>expression</code> <label label="required" /></td>
            <td><code>ExprLike</code></td>
            <td>An expression created using operator functions.</td>
        </tr>
    </tbody>
</table>

### Returns
`BoundQuery` - Compiled query with bindings

## Comparison operators

### `eq(field, value)` {#eq}

Equality comparison (`=`).

```ts
const adults = expr(eq('age', 18));
await db.select(new Table('users')).where(adults);
// WHERE age = 18
```

---

### `eeq(field, value)` {#eeq}

Exact equality comparison (`==`).

```ts
const exact = expr(eeq('count', 0));
// WHERE count == 0
```

---

### `ne(field, value)` {#ne}

Not equal comparison (`!=`).

```ts
const notAdmin = expr(ne('role', 'admin'));
// WHERE role != 'admin'
```

---

### `gt(field, value)`, `gte(field, value)` {#gt-gte}

Greater than (`>`) and greater than or equal (`>=`).

```ts
const adults = expr(gt('age', 17));
const adultsInclusive = expr(gte('age', 18));
```

---

### `lt(field, value)`, `lte(field, value)` {#lt-lte}

Less than (`<`) and less than or equal (`<=`).

```ts
const young = expr(lt('age', 30));
const youngInclusive = expr(lte('age', 29));
```

## Logical operators

### `and(...conditions)` {#and}

Logical AND - all conditions must be true.

```ts
const premiumAdults = expr(and(
    eq('tier', 'premium'),
    gte('age', 18)
));
// WHERE tier = 'premium' AND age >= 18
```

---

### `or(...conditions)` {#or}

Logical OR - at least one condition must be true.

```ts
const adminOrModerator = expr(or(
    eq('role', 'admin'),
    eq('role', 'moderator')
));
// WHERE role = 'admin' OR role = 'moderator'
```

---

### `not(condition)` {#not}

Logical NOT - inverts the condition.

```ts
const notBanned = expr(not(eq('status', 'banned')));
// WHERE NOT status = 'banned'
```

## Collection operators

### `contains(field, value)` {#contains}

Check if field contains value (`CONTAINS`).

```ts
const hasTag = expr(contains('tags', 'featured'));
// WHERE tags CONTAINS 'featured'
```

---

### `containsAny(field, values)` {#containsany}

Check if field contains any of the values (`CONTAINSANY`).

```ts
const hasAnyTag = expr(containsAny('tags', ['new', 'featured', 'trending']));
// WHERE tags CONTAINSANY ['new', 'featured', 'trending']
```

---

### `containsAll(field, values)` {#containsall}

Check if field contains all values (`CONTAINSALL`).

```ts
const hasAllTags = expr(containsAll('tags', ['verified', 'premium']));
// WHERE tags CONTAINSALL ['verified', 'premium']
```

---

### `containsNone(field, values)` {#containsnone}

Check if field contains none of the values (`CONTAINSNONE`).

```ts
const noBadTags = expr(containsNone('tags', ['spam', 'banned']));
// WHERE tags CONTAINSNONE ['spam', 'banned']
```

## Geometry operators

### `inside(field, geometry)` {#inside}

Check if geometry is inside another (`INSIDE`).

```ts
const inRegion = expr(inside('location', regionPolygon));
// WHERE location INSIDE $regionPolygon
```

---

### `outside(field, geometry)` {#outside}

Check if geometry is outside another (`OUTSIDE`).

```ts
const outsideZone = expr(outside('location', restrictedZone));
// WHERE location OUTSIDE $restrictedZone
```

---

### `intersects(field, geometry)` {#intersects}

Check if geometries intersect (`INTERSECTS`).

```ts
const overlaps = expr(intersects('area', otherArea));
// WHERE area INTERSECTS $otherArea
```

## Search operators

### `matches(field, query, ref?)` {#matches}

Full-text search match (`@@` or `@ref@`).

```ts
// Basic match
const searchResults = expr(matches('content', 'searchTerm'));
// WHERE content @@ 'searchTerm'

// With reference number
const searchWithRef = expr(matches('content', 'searchTerm', 1));
// WHERE content @1@ 'searchTerm'
```

---

### `knn(field, value, neighbors, metricOrEf?)` {#knn}

K-nearest neighbors vector search.

```ts
const similar = expr(knn('embedding', [0.1, 0.2, 0.3], 10, 'cosine'));
// WHERE embedding <|10,COSINE|> [0.1, 0.2, 0.3]
```

## Range operator

### `between(field, a, b)` {#between}

Range check - shortcut for `and(gte(field, a), lte(field, b))`.

```ts
const midRange = expr(between('price', 10, 50));
// WHERE price >= 10 AND price <= 50
```

## Raw expressions

### `raw(sql)` {#raw}

Create raw SurrealQL expressions.

> [!WARNING]
> Only use `raw()` when no other operator is applicable. Incorrect use risks SQL injection.

```ts
const custom = expr(raw('custom_function()'));
```

## Complete examples

### Basic filtering

```ts
import { expr, eq, gte } from 'surrealdb';

// Single condition
const active = expr(eq('status', 'active'));
const users = await db.select(new Table('users')).where(active);

// Multiple conditions with AND
const premiumAdults = expr(and(
    eq('tier', 'premium'),
    gte('age', 18),
    eq('active', true)
));

const results = await db.select(new Table('users')).where(premiumAdults);
```

### Complex conditions

```ts
// Nested OR and AND
const eligibleUsers = expr(or(
    and(
        eq('tier', 'premium'),
        gte('age', 18)
    ),
    and(
        eq('role', 'admin'),
        eq('verified', true)
    )
));

const users = await db.select(new Table('users')).where(eligibleUsers);
```

### Date filtering

```ts
import { DateTime, Duration } from 'surrealdb';

const cutoffDate = DateTime.now().minus(Duration.parse('30d'));

const recentUsers = expr(gte('created_at', cutoffDate));
const users = await db.select(new Table('users')).where(recentUsers);
```

### Array operations

```ts
// Check if user has specific tags
const hasFeaturedTag = expr(contains('tags', 'featured'));

// Check if has any of these tags
const hasPromotedTags = expr(containsAny('tags', ['featured', 'trending', 'new']));

// Must have all required tags
const fullyVerified = expr(containsAll('badges', ['email-verified', 'phone-verified']));

// Must not have any bad tags
const cleanContent = expr(containsNone('flags', ['spam', 'inappropriate']));
```

### Geospatial queries

```ts
import { GeometryPoint } from 'surrealdb';

const searchArea = new GeometryPolygon([/* ... */]);

// Find locations inside area
const nearby = expr(inside('location', searchArea));
const locations = await db.select(new Table('stores')).where(nearby);

// Find areas that intersect
const overlapping = expr(intersects('coverage_area', searchArea));
const zones = await db.select(new Table('zones')).where(overlapping);
```

### Full-text search

```ts
// Basic text search
const searchQuery = 'javascript tutorial';
const articles = await db.select(new Table('articles'))
    .where(expr(matches('content', searchQuery)));

// With reference number for multi-field search
const multiField = expr(or(
    matches('title', searchQuery, 1),
    matches('content', searchQuery, 1)
));
```

### Vector search

```ts
// Find similar items using KNN
const queryVector = [0.1, 0.2, 0.3, /* ... */];

const similar = expr(knn('embedding', queryVector, 10, 'cosine'));
const results = await db.select(new Table('items')).where(similar);
```

### Reusable expressions

```ts
// Define reusable filters
const activeFilter = expr(eq('active', true));
const verifiedFilter = expr(eq('verified', true));
const premiumFilter = expr(eq('tier', 'premium'));

// Combine as needed
const premiumActive = expr(and(activeFilter, premiumFilter));
const verifiedActive = expr(and(activeFilter, verifiedFilter));

// Use in queries
const users1 = await db.select(new Table('users')).where(premiumActive);
const users2 = await db.select(new Table('users')).where(verifiedActive);
```

### Update with expressions

```ts
const condition = expr(and(
    eq('status', 'pending'),
    lt('created_at', DateTime.now().minus(Duration.parse('1h')))
));

const updated = await db.update(new Table('orders'))
    .merge({ status: 'expired' })
    .where(condition);
```

### Delete with expressions

```ts
const oldInactive = expr(and(
    eq('active', false),
    lt('last_login', DateTime.now().minus(Duration.parse('90d')))
));

const deleted = await db.delete(new Table('users')).where(oldInactive);
```

## Best practices

### 1. Use expressions for complex conditions

```ts
// Good: Type-safe and reusable
const condition = expr(and(
    gte('age', 18),
    eq('verified', true)
));

// Avoid: Raw strings (no type safety)
const condition = 'age >= 18 AND verified = true';
```

### 2. Build expressions compositionally

```ts
// Good: Compose small expressions
const isAdult = expr(gte('age', 18));
const isVerified = expr(eq('verified', true));
const isActive = expr(eq('active', true));

const eligibleUsers = expr(and(isAdult, isVerified, isActive));

// You can reuse components
const premiumEligible = expr(and(isAdult, isVerified));
```

### 3. Avoid `raw()` when possible

```ts
// Good: Use typed operators
const condition = expr(gte('score', 80));

// Avoid: Raw SQL (SQL injection risk)
const condition = expr(raw(`score >= ${userInput}`));
```

### 4. Parameterise dynamic values

```ts
// Good: Values are automatically parameterised
const minAge = getUserInput();
const condition = expr(gte('age', minAge));

// Safe: minAge is bound as a parameter, not concatenated
```

## Common patterns

### Dynamic filter builder

```ts
function buildUserFilter(options: {
    minAge?: number;
    tier?: string;
    active?: boolean;
}) {
    const conditions: ExprLike[] = [];
    
    if (options.minAge !== undefined) {
        conditions.push(gte('age', options.minAge));
    }
    if (options.tier) {
        conditions.push(eq('tier', options.tier));
    }
    if (options.active !== undefined) {
        conditions.push(eq('active', options.active));
    }
    
    return conditions.length > 0 ? expr(and(...conditions)) : null;
}

// Usage
const filter = buildUserFilter({ minAge: 18, tier: 'premium' });
if (filter) {
    const users = await db.select(new Table('users')).where(filter);
}
```

### Search with multiple criteria

```ts
function searchProducts(criteria: {
    minPrice?: Decimal;
    maxPrice?: Decimal;
    categories?: string[];
    inStock?: boolean;
}) {
    const conditions: ExprLike[] = [];
    
    if (criteria.minPrice) {
        conditions.push(gte('price', criteria.minPrice));
    }
    if (criteria.maxPrice) {
        conditions.push(lte('price', criteria.maxPrice));
    }
    if (criteria.categories?.length) {
        conditions.push(containsAny('categories', criteria.categories));
    }
    if (criteria.inStock !== undefined) {
        conditions.push(eq('in_stock', criteria.inStock));
    }
    
    return expr(and(...conditions));
}
```

## See also

- [Query builders](/docs/reference/javascript/api/queries/) - Using expressions in queries
- [surql](/docs/reference/javascript/api/utilities/surql.md) - Template tag for queries
- [BoundQuery](/docs/reference/javascript/api/utilities/bound-query.md) - Parameterised queries
- [SelectPromise.where()](/docs/reference/javascript/api/queries/select-promise.md#where) - Using expressions with WHERE

---

Source: https://surrealdb.com/docs/reference/javascript/api/utilities/is-retryable-conflict

# isRetryableConflict

Default predicate used to detect retryable write conflicts.

The `isRetryableConflict()` function is the default predicate used by [`.retry()`](/docs/reference/javascript/api/queries/query.md#retry) to decide whether a failed query should be retried. SurrealDB does not currently expose a structured retryable error kind, so the predicate matches on the error message.

**Import:**
```ts
import { isRetryableConflict } from 'surrealdb';
```

**Source:** [utils/index.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/utils/index.ts)

## Function signature

```ts
function isRetryableConflict(error: Error): boolean
```

### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>error</code> <label label="required" /></td>
            <td><code>Error</code></td>
            <td>The error thrown by a failed query.</td>
        </tr>
    </tbody>
</table>

### Returns
`boolean` - `true` if the error looks like a retryable write conflict (its message contains "conflict" or "can be retried"), `false` otherwise

## Overriding the default predicate

Pass a custom `retryable` function in [`RetryOptions`](/docs/reference/javascript/api/types/#retryoptions) to change what counts as retryable, optionally reusing `isRetryableConflict` as a base:

```ts
import { isRetryableConflict } from 'surrealdb';

await db.query('UPDATE counter:c SET n += 1 RETURN n')
    .retry({
        attempts: 5,
        retryable: (error) => isRetryableConflict(error) || error.message.includes('busy')
    })
    .collect();
```

> [!NOTE]
> Confirm the exact conflict message against the SurrealDB server version you target - the heuristic matches on message text rather than a structured error kind.

## See also

- [Query.retry()](/docs/reference/javascript/api/queries/query.md#retry) - Retry queries on write conflict
- [RetryOptions](/docs/reference/javascript/api/types/#retryoptions) - Retry configuration type
- [Error handling](/docs/reference/javascript/concepts/error-handling.md) - Handling SDK errors

---

Source: https://surrealdb.com/docs/reference/javascript/api/utilities/surql

# surql

Tagged template for composing parameterised SurrealQL queries.

The `surql` tagged template function creates parameterised SurrealQL queries with automatic value binding and SQL injection prevention.

**Import:**
```ts
import { surql } from 'surrealdb';
```

**Source:** [utils/tagged-template.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/utils/tagged-template.ts)

## Function signature

```ts
function surql(
    strings: TemplateStringsArray, 
    ...values: unknown[]
): BoundQuery
```

### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>strings</code> <label label="required" /></td>
            <td><code>TemplateStringsArray</code></td>
            <td>Template string segments.</td>
        </tr>
        <tr>
            <td><code>values</code> <label label="required" /></td>
            <td><code>unknown[]</code></td>
            <td>Interpolated values (automatically bound as parameters).</td>
        </tr>
    </tbody>
</table>

### Returns
`BoundQuery` - Parameterised query with automatic bindings

## How it works

The `surql` template automatically:
1. Extracts interpolated values
2. Generates unique parameter names
3. Replaces values with parameter references
4. Returns a `BoundQuery` with query string and bindings

```ts
const age = 18;
const query = surql`SELECT * FROM users WHERE age > ${age}`;

// Internally becomes:
// query.query = "SELECT * FROM users WHERE age > $bind__1"
// query.bindings = { bind__1: 18 }
```

## Basic examples

### Simple parameterised query

```ts
import { surql } from 'surrealdb';

const minAge = 18;
const query = surql`SELECT * FROM users WHERE age >= ${minAge}`;

const [users] = await db.query(query).collect();
```

### Multiple parameters

```ts
const status = 'active';
const minAge = 18;
const tier = 'premium';

const query = surql`
    SELECT * FROM users 
    WHERE status = ${status}
    AND age >= ${minAge}
    AND tier = ${tier}
`;

const [users] = await db.query(query).collect();
```

### With value types

```ts
import { RecordId, DateTime, Duration } from 'surrealdb';

const userId = new RecordId('users', 'john');
const cutoffDate = DateTime.now().minus(Duration.parse('30d'));

const query = surql`
    SELECT * FROM posts 
    WHERE author = ${userId}
    AND created_at >= ${cutoffDate}
    ORDER BY created_at DESC
`;

const [posts] = await db.query(query).collect();
```

## Advanced examples

### Dynamic query building

```ts
function buildUserQuery(filters: {
    status?: string;
    minAge?: number;
    tier?: string;
}) {
    let query = surql`SELECT * FROM users WHERE 1=1`;
    
    if (filters.status) {
        query.append(surql` AND status = ${filters.status}`);
    }
    if (filters.minAge !== undefined) {
        query.append(surql` AND age >= ${filters.minAge}`);
    }
    if (filters.tier) {
        query.append(surql` AND tier = ${filters.tier}`);
    }
    
    return query;
}

const query = buildUserQuery({ status: 'active', minAge: 18 });
const [users] = await db.query(query).collect();
```

### Multi-statement queries

```ts
const userId = new RecordId('users', 'john');
const postId = new RecordId('posts', '123');

const query = surql`
    BEGIN TRANSACTION;
    
    UPDATE ${userId} SET post_count += 1;
    
    CREATE ${postId} SET
        author = ${userId},
        title = ${'My Post'},
        content = ${'Post content here'},
        created_at = time::now();
    
    COMMIT TRANSACTION;
`;

await db.query(query).collect();
```

### Combining with expressions

```ts
import { expr, eq, gte } from 'surrealdb';

const condition = expr(and(
    eq('verified', true),
    gte('age', 18)
));

const tier = 'premium';
const query = surql`
    SELECT * FROM users 
    WHERE ${condition}
    AND tier = ${tier}
`;

const [users] = await db.query(query).collect();
```

### Inserting arrays

```ts
const users = [
    { name: 'Alice', email: 'alice@example.com' },
    { name: 'Bob', email: 'bob@example.com' }
];

const query = surql`INSERT INTO users ${users}`;
await db.query(query).collect();
```

### Graph traversal

```ts
const userId = new RecordId('users', 'john');

const query = surql`
    SELECT 
        *,
        ->follows->users.* AS following,
        <-follows<-users.* AS followers
    FROM ${userId}
`;

const [result] = await db.query(query).collect();
console.log('Following:', result.following);
console.log('Followers:', result.followers);
```

### Conditional updates

```ts
const status = 'inactive';
const threshold = DateTime.now().minus(Duration.parse('90d'));

const query = surql`
    UPDATE users 
    SET status = ${status}
    WHERE active = false 
    AND last_login < ${threshold}
`;

const [updated] = await db.query(query).collect();
console.log(`Updated ${updated.length} users`);
```

### Variable definition

```ts
const minScore = 80;
const category = 'tech';

const query = surql`
    LET $high_scorers = SELECT * FROM users WHERE score >= ${minScore};
    LET $tech_users = SELECT * FROM users WHERE category = ${category};
    
    RETURN {
        high_scorers: $high_scorers,
        tech_users: $tech_users,
        intersection: SELECT * FROM $high_scorers WHERE category = ${category}
    };
`;

const [result] = await db.query(query).collect();
```

### Batch operations

```ts
const recordIds = [
    new RecordId('users', 'alice'),
    new RecordId('users', 'bob'),
    new RecordId('users', 'carol')
];

const query = surql`
    SELECT * FROM [${recordIds[0]}, ${recordIds[1]}, ${recordIds[2]}]
`;

const [users] = await db.query(query).collect();
```

## SQL injection prevention

The `surql` template prevents SQL injection by automatically parameterising all values:

```ts
// User input
const userInput = "'; DROP TABLE users; --";

// Safe: Treated as a parameter value
const query = surql`SELECT * FROM users WHERE name = ${userInput}`;
// Becomes: SELECT * FROM users WHERE name = $bind__1
// With binding: { bind__1: "'; DROP TABLE users; --" }

// The malicious SQL is safely treated as a string value
```

## Best practices

### 1. Always use surql for user input

```ts
// Good: Safe parameterisation
const userName = getUserInput();
const query = surql`SELECT * FROM users WHERE name = ${userName}`;

// Dangerous: SQL injection risk
const query = `SELECT * FROM users WHERE name = '${userName}'`;
```

### 2. Use for complex queries

```ts
// Good: Clear and safe
const query = surql`
    SELECT *,
        ->purchased->products.* AS purchases,
        <-manages<-departments.* AS departments
    FROM ${userId}
    WHERE active = ${true}
`;

// Harder to read and maintain
const query = new BoundQuery(
    'SELECT *, ->purchased->products.* AS purchases FROM $userId WHERE active = $active',
    { userId, active: true }
);
```

### 3. Use the type system

```ts
// Good: Type-safe values
const recordId = new RecordId('users', 'john');
const datetime = DateTime.now();

const query = surql`
    UPDATE ${recordId}
    SET last_login = ${datetime}
`;

// Values maintain their types through the query
```

### 4. Build queries incrementally

```ts
// Good: Append for dynamic queries
let query = surql`SELECT * FROM products WHERE 1=1`;

if (minPrice) {
    query.append(surql` AND price >= ${minPrice}`);
}
if (category) {
    query.append(surql` AND category = ${category}`);
}

query.append(surql` ORDER BY created_at DESC LIMIT ${limit}`);
```

## Common pitfalls

### 1. Identifier interpolation

```ts
// Problem: Table names can't be parameterised
const tableName = 'users';
const wrong = surql`SELECT * FROM ${tableName}`; // Creates $bind__1

// Solution: Use Table class
const table = new Table('users');
const correct = surql`SELECT * FROM ${table}`;
```

### 2. Field names

```ts
// Problem: Field names as parameters
const fieldName = 'age';
const wrong = surql`SELECT * FROM users WHERE ${fieldName} > 18`;

// Solution: Use raw SQL for field names (with validation)
import { escapeIdent } from 'surrealdb';
const validated = escapeIdent(fieldName);
const correct = surql`SELECT * FROM users WHERE ${raw(validated)} > 18`;
```

## See also

- [BoundQuery](/docs/reference/javascript/api/utilities/bound-query.md) - Parameterised query class
- [expr](/docs/reference/javascript/api/utilities/expr.md) - Expression builder
- [Query](/docs/reference/javascript/api/queries/query.md) - Executing queries
- [SurrealQueryable.query()](/docs/reference/javascript/api/core/surreal-queryable.md#query) - Query method

---

Source: https://surrealdb.com/docs/reference/javascript/api/values

# Data types

Type mapping between SurrealQL and JavaScript, and custom data type classes.

The JavaScript SDK provides custom classes for SurrealDB-specific data types, ensuring type safety and data integrity when working with the database. For a conceptual overview with usage examples and best practices, see the [Value types concept page](/docs/reference/javascript/concepts/value-types.md).

## Custom data type classes

- [**RecordId**](/docs/reference/javascript/api/values/record-id.md) - Type-safe record identifiers with table and ID components
  - `new RecordId(table, id)` - Create record ID
  - Also includes `RecordIdRange` for querying ranges

- [**Table**](/docs/reference/javascript/api/values/table.md) - Table references, optionally typed with their record type
  - `new Table<T>(name)` - Create a table reference with a record type
  - `Table.of<T>()(name)` - Same, keeping the table name as a literal type
  - Used in SELECT, CREATE, UPDATE, DELETE operations

- [**DateTime**](/docs/reference/javascript/api/values/datetime.md) - Datetime values with nanosecond precision
  - `DateTime.now()` - Current datetime
  - `new DateTime(string)` - Parse from ISO string
  - `.toDate()` - Convert to JavaScript Date

- [**Duration**](/docs/reference/javascript/api/values/duration.md) - Time duration with support for multiple units
  - `new Duration('5h30m')` - Parse from string
  - `.milliseconds` - Get duration in milliseconds

- [**Decimal**](/docs/reference/javascript/api/values/decimal.md) - Arbitrary precision decimal numbers
  - `new Decimal('19.99')` - Create precise decimal
  - Preserves precision during operations

- [**Uuid**](/docs/reference/javascript/api/values/uuid.md) - Universally unique identifiers
  - `Uuid.v4()` - Generate random UUID
  - `Uuid.v7()` - Generate time-ordered UUID

- [**Range**](/docs/reference/javascript/api/values/range.md) - Generic range values for numeric and date ranges

- [**FileRef**](/docs/reference/javascript/api/values/file-ref.md) - References to files stored in SurrealDB
  - `.bucket` - Storage bucket name
  - `.key` - File key within the bucket

## Geometric types

- [**Geometry**](/docs/reference/javascript/api/values/geometry.md) - Spatial/geometric data types
  - `GeometryPoint` - Single point
  - `GeometryLine` - Line between points
  - `GeometryPolygon` - Polygon shape
  - `GeometryMultiPoint`, `GeometryMultiLine`, `GeometryMultiPolygon`
  - `GeometryCollection` - Mixed geometry collection

## See also

- [Value types concept page](/docs/reference/javascript/concepts/value-types.md) - Usage guide with type mapping, examples, and best practices
- [SurrealQL data types](/docs/reference/query-language/language-primitives/data-types.md) - Database data model
- [Utilities](/docs/reference/javascript/concepts/utilities.md) - Comparing and converting values

---

Source: https://surrealdb.com/docs/reference/javascript/api/values/datetime

# DateTime

Datetime values with nanosecond precision for time-based operations.

The `DateTime` class provides datetime values with nanosecond precision, extending the abstract `Value` class to match SurrealDB's datetime type.

**Import:**
```ts
import { DateTime } from 'surrealdb';
```

**Source:** [value/datetime.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/value/datetime.ts)

## Constructor

### `new DateTime(value?)` {#constructor}

Create a new datetime value.

```ts title="Syntax"
new DateTime() // Current time
new DateTime(datetime) // Clone existing
new DateTime(date) // From JavaScript Date
new DateTime(string) // Parse ISO string
new DateTime(number | bigint) // From timestamp
new DateTime([seconds, nanoseconds]) // From tuple
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>value</code> <label label="optional" /></td>
            <td><code>DateTime | Date | string | number | bigint | [bigint, bigint]</code></td>
            <td>Value to create datetime from. If omitted, uses current time.</td>
        </tr>
    </tbody>
</table>

#### Examples

```ts
// Current time
const now = new DateTime();

// From JavaScript Date
const date = new DateTime(new Date());

// Parse ISO string
const parsed = new DateTime('2024-01-15T12:00:00Z');

// From Unix timestamp (seconds)
const fromTimestamp = new DateTime(1705320000);

// From tuple [seconds, nanoseconds]
const precise = new DateTime([1705320000n, 500000000n]);

// Clone existing
const clone = new DateTime(now);
```

## Static methods

### `DateTime.now()` {#now}

Get the current datetime with nanosecond precision.

```ts title="Syntax"
DateTime.now()
```

#### Returns
`DateTime` - Current datetime

#### Example

```ts
const now = DateTime.now();
console.log(now.toString()); // '2024-01-15T12:30:45.123456789Z'

// Use in queries
await db.create(new Table('events')).content({
    name: 'Meeting',
    timestamp: DateTime.now()
});
```

---

### `DateTime.epoch()` {#epoch}

Returns a `DateTime` representing the Unix epoch (1970-01-01T00:00:00Z).

```ts title="Syntax"
DateTime.epoch()
```

#### Returns
`DateTime` - Unix epoch datetime

#### Example

```ts
const epoch = DateTime.epoch();
console.log(epoch.toString()); // '1970-01-01T00:00:00.000000000Z'
console.log(epoch.seconds); // 0
```

---

### `DateTime.fromEpochNanoseconds(ns)` {#fromepochnanoseconds}

Create a `DateTime` from a nanosecond timestamp since Unix epoch.

```ts title="Syntax"
DateTime.fromEpochNanoseconds(ns)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ns</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Nanoseconds since Unix epoch.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DateTime` - Datetime from the nanosecond timestamp

#### Example

```ts
const dt = DateTime.fromEpochNanoseconds(1705320000123456789n);
console.log(dt.toString()); // '2024-01-15T12:00:00.123456789Z'
```

---

### `DateTime.fromEpochMicroseconds(µs)` {#fromepochmicroseconds}

Create a `DateTime` from a microsecond timestamp since Unix epoch.

```ts title="Syntax"
DateTime.fromEpochMicroseconds(µs)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>µs</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Microseconds since Unix epoch.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DateTime` - Datetime from the microsecond timestamp

#### Example

```ts
const dt = DateTime.fromEpochMicroseconds(1705320000123456n);
console.log(dt.toString()); // '2024-01-15T12:00:00.123456000Z'
```

---

### `DateTime.fromEpochMilliseconds(ms)` {#fromepochmilliseconds}

Create a `DateTime` from a millisecond timestamp since Unix epoch.

```ts title="Syntax"
DateTime.fromEpochMilliseconds(ms)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ms</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Milliseconds since Unix epoch.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DateTime` - Datetime from the millisecond timestamp

#### Example

```ts
const dt = DateTime.fromEpochMilliseconds(1705320000123);
console.log(dt.toString()); // '2024-01-15T12:00:00.123000000Z'
```

---

### `DateTime.fromEpochSeconds(s)` {#fromepochseconds}

Create a `DateTime` from a second timestamp since Unix epoch.

```ts title="Syntax"
DateTime.fromEpochSeconds(s)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>s</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Seconds since Unix epoch.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DateTime` - Datetime from the second timestamp

#### Example

```ts
const dt = DateTime.fromEpochSeconds(1705320000);
console.log(dt.toString()); // '2024-01-15T12:00:00.000000000Z'
```

## Instance methods

### `.toDate()` {#todate}

Convert to JavaScript Date object.

```ts title="Syntax"
datetime.toDate()
```

#### Returns
`Date` - JavaScript Date (millisecond precision)

> [!WARNING]
> JavaScript Date only supports millisecond precision. Nanosecond precision is lost in conversion.

> [!NOTE]
> `DateTime` does not extend JavaScript's `Date`. To use `Date` methods like `.getFullYear()`, `.getMonth()`, `.getDate()`, `.getHours()`, etc., first call `.toDate()`.

#### Example

```ts
const dt = DateTime.now();
const jsDate = dt.toDate();

// Use with JavaScript APIs
const formatted = jsDate.toLocaleDateString();

// Access Date component methods via .toDate()
console.log(jsDate.getFullYear());  // 2024
console.log(jsDate.getMonth());     // 0 (January)
console.log(jsDate.getDate());      // 15
console.log(jsDate.getHours());     // 12
console.log(jsDate.getMinutes());   // 30
console.log(jsDate.getSeconds());   // 45
```

---

### `.toString()` {#tostring}

Convert to ISO 8601 string with full nanosecond precision.

```ts title="Syntax"
datetime.toString()
```

#### Returns
`string` - ISO 8601 formatted string

#### Example

```ts
const dt = DateTime.now();
console.log(dt.toString());
// '2024-01-15T12:30:45.123456789Z'
```

---

### `.toISOString()` {#toisostring}

Convert to ISO 8601 string (alias for `.toString()`).

```ts title="Syntax"
datetime.toISOString()
```

#### Returns
`string` - ISO 8601 formatted string

---

### `.toJSON()` {#tojson}

Serialise for JSON.

```ts title="Syntax"
datetime.toJSON()
```

#### Returns
`string` - ISO string for JSON serialisation

#### Example

```ts
const dt = DateTime.now();
console.log(JSON.stringify({ timestamp: dt }));
// {"timestamp":"2024-01-15T12:30:45.123456789Z"}
```

---

### `.toCompact()` {#tocompact}

Returns the datetime as a compact tuple of seconds and nanoseconds since Unix epoch.

```ts title="Syntax"
datetime.toCompact()
```

#### Returns
`[bigint, bigint]` - Tuple of `[seconds, nanoseconds]`

#### Example

```ts
const dt = new DateTime('2024-01-15T12:00:00.500000000Z');
const [secs, nanos] = dt.toCompact();
console.log(secs);  // 1705320000n
console.log(nanos); // 500000000n

// Round-trip via constructor
const restored = new DateTime([secs, nanos]);
console.log(dt.equals(restored)); // true
```

---

### `.add(duration)` {#add}

Add a duration to the datetime.

```ts title="Syntax"
datetime.add(duration)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>duration</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/duration.md">Duration</a></code></td>
            <td>Duration to add.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DateTime` - New datetime with duration added

#### Example

```ts
import { Duration } from 'surrealdb';

const now = DateTime.now();
const later = now.add(Duration.parse('1h30m'));
const tomorrow = now.add(Duration.parse('24h'));
```

---

### `.sub(duration)` {#sub}

Subtract a duration from the datetime.

```ts title="Syntax"
datetime.sub(duration)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>duration</code> <label label="required" /></td>
            <td><code><a href="/docs/reference/javascript/api/values/duration.md">Duration</a></code></td>
            <td>Duration to subtract.</td>
        </tr>
    </tbody>
</table>

#### Returns
`DateTime` - New datetime with duration subtracted

#### Example

```ts
const now = DateTime.now();
const earlier = now.sub(Duration.parse('1h'));
const yesterday = now.sub(Duration.parse('24h'));
```

---

### `.diff(other)` {#diff}

Calculate the duration between two datetimes.

```ts title="Syntax"
datetime.diff(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>DateTime</code></td>
            <td>The other datetime to calculate the difference from.</td>
        </tr>
    </tbody>
</table>

#### Returns
[`Duration`](/docs/reference/javascript/api/values/duration.md) - Duration between the two datetimes

#### Example

```ts
const start = new DateTime('2024-01-15T12:00:00Z');
const end = new DateTime('2024-01-15T14:30:00Z');

const elapsed = end.diff(start);
console.log(elapsed.toString()); // '2h30m'
```

---

### `.compare(other)` {#compare}

Compare two datetimes for ordering.

```ts title="Syntax"
datetime.compare(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>DateTime</code></td>
            <td>The datetime to compare against.</td>
        </tr>
    </tbody>
</table>

#### Returns
`number` - Returns `-1` if this datetime is before `other`, `0` if equal, `1` if after

#### Example

```ts
const a = new DateTime('2024-01-15T12:00:00Z');
const b = new DateTime('2024-01-16T12:00:00Z');

console.log(a.compare(b)); // -1
console.log(b.compare(a)); // 1
console.log(a.compare(a)); // 0

// Useful for sorting
const dates = [b, a];
dates.sort((x, y) => x.compare(y));
```

---

### `.equals(other)` {#equals}

Check if two datetimes are equal (including nanosecond precision).

```ts title="Syntax"
datetime.equals(other)
```

#### Returns
`boolean` - True if equal

## Properties

### `nanoseconds` {#nanoseconds}

Total nanoseconds since Unix epoch.

**Type:** `bigint`

```ts
const dt = new DateTime('2024-01-15T12:00:00.123456789Z');
console.log(dt.nanoseconds); // 1705320000123456789n
```

---

### `microseconds` {#microseconds}

Total microseconds since Unix epoch.

**Type:** `bigint`

```ts
const dt = new DateTime('2024-01-15T12:00:00.123456789Z');
console.log(dt.microseconds); // 1705320000123456n
```

---

### `milliseconds` {#milliseconds}

Total milliseconds since Unix epoch.

**Type:** `number`

```ts
const dt = new DateTime('2024-01-15T12:00:00.123456789Z');
console.log(dt.milliseconds); // 1705320000123
```

---

### `seconds` {#seconds}

Seconds since Unix epoch.

**Type:** `number`

```ts
const dt = new DateTime('2024-01-15T12:00:00.123456789Z');
console.log(dt.seconds); // 1705320000
```

## Complete examples

### Event timestamps

```ts
import { Surreal, DateTime, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Create event with timestamp
const event = await db.create(new Table('events')).content({
    name: 'User Login',
    user: new RecordId('users', 'john'),
    timestamp: DateTime.now(),
    ip: '192.168.1.1'
});

console.log('Event created at:', event.timestamp.toString());
```

### Date arithmetic

```ts
import { DateTime, Duration } from 'surrealdb';

const now = DateTime.now();

// Add time
const future = now.add(Duration.parse('7d')); // One week later
const meeting = now.add(Duration.parse('2h30m')); // Meeting in 2.5 hours

// Subtract time
const past = now.sub(Duration.parse('30d')); // 30 days ago
const recentCutoff = now.sub(Duration.parse('1h')); // Last hour
```

### Query with date ranges

```ts
const startDate = new DateTime('2024-01-01T00:00:00Z');
const endDate = new DateTime('2024-12-31T23:59:59Z');

const events = await db.query(`
    SELECT * FROM events 
    WHERE timestamp >= $start AND timestamp <= $end
`, {
    start: startDate,
    end: endDate
}).collect();
```

### Time-series data

```ts
// Record metrics with precise timestamps
async function recordMetric(name: string, value: number) {
    await db.create(new Table('metrics')).content({
        name,
        value,
        timestamp: DateTime.now()
    });
}

await recordMetric('cpu_usage', 45.2);
await recordMetric('memory_usage', 78.1);

// Query recent metrics
const recent = await db.query(`
    SELECT * FROM metrics 
    WHERE timestamp > $cutoff
    ORDER BY timestamp DESC
`, {
    cutoff: DateTime.now().sub(Duration.parse('5m'))
}).collect();
```

### Conversion examples

```ts
// From JavaScript Date
const jsDate = new Date('2024-01-15T12:00:00Z');
const dt = new DateTime(jsDate);

// To JavaScript Date
const backToJS = dt.toDate();

// From Unix timestamp
const fromTimestamp = new DateTime(1705320000);

// Get Unix timestamp in milliseconds
const timestamp = dt.milliseconds;

// Parse from string
const parsed = new DateTime('2024-01-15T12:00:00.123456789Z');

// Convert to string
const isoString = dt.toString();

// From epoch helpers
const fromNs = DateTime.fromEpochNanoseconds(1705320000123456789n);
const fromMs = DateTime.fromEpochMilliseconds(1705320000123);
const fromSecs = DateTime.fromEpochSeconds(1705320000);
```

### Scheduled tasks

```ts
// Schedule future task
const scheduledFor = DateTime.now().add(Duration.parse('1h'));

await db.create(new Table('tasks')).content({
    name: 'Send reminder',
    scheduled_for: scheduledFor,
    status: 'pending'
});

// Find overdue tasks
const now = DateTime.now();
const overdue = await db.query(`
    SELECT * FROM tasks 
    WHERE scheduled_for < $now 
    AND status = 'pending'
`, { now }).collect();
```

### Expiration handling

```ts
// Set expiration time
const session = await db.create(new Table('sessions')).content({
    user: userId,
    created_at: DateTime.now(),
    expires_at: DateTime.now().add(Duration.parse('24h'))
});

// Check if expired
function isExpired(expiresAt: DateTime): boolean {
    return DateTime.now().milliseconds > expiresAt.milliseconds;
}

if (isExpired(session.expires_at)) {
    await db.delete(session.id);
}
```

### Timezone handling

```ts
// DateTime is always stored in UTC
const utcTime = DateTime.now();

// Convert to local time for display
const localDate = utcTime.toDate();
const localString = localDate.toLocaleString();

console.log('UTC:', utcTime.toString());
console.log('Local:', localString);
```

## Best practices

### 1. Use DateTime for database timestamps

```ts
// Good: Nanosecond precision preserved
await db.create(new Table('logs')).content({
    timestamp: DateTime.now(),
    message: 'Event occurred'
});

// Avoid: JavaScript Date (millisecond precision only)
await db.create(new Table('logs')).content({
    timestamp: new Date(),
    message: 'Event occurred'
});
```

### 2. Be aware of precision loss

```ts
// Good: Keep as DateTime for precision
const dt = DateTime.now();
const stored = dt.toString(); // Preserves nanoseconds

// Caution: Loses nanosecond precision
const jsDate = dt.toDate(); // Only milliseconds
```

### 3. Use Duration for time arithmetic

```ts
// Good: Type-safe duration arithmetic
const future = now.add(Duration.parse('1h'));

// Avoid: Manual millisecond math
const future2 = DateTime.fromEpochMilliseconds(now.milliseconds + 3600000);
```

### 4. Store as DateTime, display as localized

```ts
// Store in UTC using DateTime
const timestamp = DateTime.now();
await db.create(table).content({ created_at: timestamp });

// Display in user's timezone
const localDisplay = timestamp.toDate().toLocaleString('en-US', {
    timeZone: 'America/New_York'
});
```

## See also

- [Duration](/docs/reference/javascript/api/values/duration.md) - Time duration values
- [Data types overview](/docs/reference/javascript/api/values/) - All custom data types
- [Query builders](/docs/reference/javascript/api/queries/) - Using DateTime in queries
- [SurrealQL datetimes](/docs/reference/query-language/language-primitives/data-types/datetimes.md) - Database datetime type

---

Source: https://surrealdb.com/docs/reference/javascript/api/values/decimal

# Decimal

Arbitrary precision decimal numbers for financial and scientific calculations.

The `Decimal` class provides arbitrary precision decimal numbers for financial calculations and other applications where floating-point precision errors are unacceptable.

**Import:**
```ts
import { Decimal } from 'surrealdb';
```

**Source:** [value/decimal.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/value/decimal.ts)

## Why use Decimal?

JavaScript's `number` type uses floating-point arithmetic, which can lead to precision errors:

```ts
// Floating-point precision error
console.log(0.1 + 0.2); // 0.30000000000000004

// Decimal preserves precision
const a = new Decimal('0.1');
const b = new Decimal('0.2');
console.log(a.add(b).toString()); // '0.3'
```

## Constructor

### `new Decimal(value)` {#constructor}

Create a new arbitrary precision decimal.

```ts title="Syntax"
new Decimal(decimal) // Clone existing
new Decimal(string) // Parse from string
new Decimal(number | bigint) // From number
new Decimal([int, frac, scale]) // From tuple
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>value</code> <label label="required" /></td>
            <td><code>Decimal | string | number | bigint | [bigint, bigint, number]</code></td>
            <td>Value to create decimal from.</td>
        </tr>
    </tbody>
</table>

#### Examples

```ts
// From string (recommended for precision)
const price = new Decimal('19.99');
const precise = new Decimal('0.123456789012345678901234567890');

// From number (may have floating-point precision)
const value = new Decimal(19.99);

// From bigint
const large = new Decimal(1000000n);

// Scientific notation
const scientific = new Decimal('1.23e-10');

// Clone existing
const copy = new Decimal(price);
```

## Static methods

### `Decimal.fromScientificNotation(input)` {#fromscientificnotation}

Parse a decimal from scientific notation string.

```ts title="Syntax"
Decimal.fromScientificNotation(input)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>input</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>Scientific notation string to parse.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Decimal` - Parsed decimal

#### Example

```ts
const value = Decimal.fromScientificNotation('1.23e10');
const small = Decimal.fromScientificNotation('5.67e-8');
```

## Properties

### `.int` {#int}

The integer part of the decimal as a `bigint`.

```ts
const d = new Decimal('19.99');
console.log(d.int); // 19n
```

---

### `.frac` {#frac}

The fractional part of the decimal as a `bigint`.

```ts
const d = new Decimal('19.99');
console.log(d.frac); // 99n
```

---

### `.scale` {#scale}

The number of decimal places.

```ts
const d = new Decimal('19.99');
console.log(d.scale); // 2
```

## Instance methods

### `.toString()` {#tostring}

Convert to string representation (preserves precision).

```ts title="Syntax"
decimal.toString()
```

#### Returns
`string` - String representation

#### Example

```ts
const price = new Decimal('19.99');
console.log(price.toString()); // '19.99'

const precise = new Decimal('0.123456789012345678901234567890');
console.log(precise.toString()); // Full precision preserved
```

---

### `.toFloat()` {#tofloat}

Convert to JavaScript number.

```ts title="Syntax"
decimal.toFloat()
```

#### Returns
`number` - JavaScript number (may lose precision)

> [!WARNING]
> Converting to number may lose precision for very large or very precise values.

#### Example

```ts
const price = new Decimal('19.99');
const num = price.toFloat(); // 19.99

// Precision loss example
const precise = new Decimal('0.123456789012345678901234567890');
const lost = precise.toFloat(); // Precision beyond ~15 digits is lost
```

---

### `.toJSON()` {#tojson}

Serialise for JSON.

```ts title="Syntax"
decimal.toJSON()
```

#### Returns
`string` - String representation for JSON

---

### `.toBigInt()` {#tobigint}

Truncate to a `bigint`, discarding the fractional part.

```ts title="Syntax"
decimal.toBigInt()
```

#### Returns
`bigint` - Integer part of the decimal

#### Example

```ts
const d = new Decimal('19.99');
console.log(d.toBigInt()); // 19n
```

---

### `.toFixed(precision)` {#tofixed}

Format the decimal with a fixed number of decimal places.

```ts title="Syntax"
decimal.toFixed(precision)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>precision</code> <label label="required" /></td>
            <td><code>number</code></td>
            <td>Number of decimal places.</td>
        </tr>
    </tbody>
</table>

#### Returns
`string` - Fixed-point notation string

#### Example

```ts
const d = new Decimal('19.9');
console.log(d.toFixed(4)); // '19.9000'
```

---

### `.toScientific()` {#toscientific}

Convert to scientific notation string.

```ts title="Syntax"
decimal.toScientific()
```

#### Returns
`string` - Scientific notation representation

#### Example

```ts
const d = new Decimal('12300');
console.log(d.toScientific()); // e.g. '1.23e4'
```

---

### `.toParts()` {#toparts}

Decompose the decimal into its constituent parts.

```ts title="Syntax"
decimal.toParts()
```

#### Returns
`{ int: bigint, frac: bigint, scale: number }` - The integer part, fractional part, and scale

#### Example

```ts
const d = new Decimal('19.99');
const parts = d.toParts();
// { int: 19n, frac: 99n, scale: 2 }
```

---

### `.add(other)` {#add}

Add another decimal.

```ts title="Syntax"
decimal.add(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>Decimal</code></td>
            <td>Value to add.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Decimal` - Sum

#### Example

```ts
const price1 = new Decimal('19.99');
const price2 = new Decimal('5.50');
const total = price1.add(price2);
console.log(total.toString()); // '25.49'
```

---

### `.sub(other)` {#sub}

Subtract another decimal.

```ts title="Syntax"
decimal.sub(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>Decimal</code></td>
            <td>Value to subtract.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Decimal` - Difference

---

### `.mul(other)` {#mul}

Multiply by another decimal.

```ts title="Syntax"
decimal.mul(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>Decimal</code></td>
            <td>Value to multiply by.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Decimal` - Product

#### Example

```ts
const price = new Decimal('19.99');
const quantity = new Decimal('3');
const total = price.mul(quantity);
console.log(total.toString()); // '59.97'
```

---

### `.div(other)` {#div}

Divide by another decimal.

```ts title="Syntax"
decimal.div(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>Decimal</code></td>
            <td>Value to divide by.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Decimal` - Quotient

---

### `.mod(other)` {#mod}

Calculate the remainder after division.

```ts title="Syntax"
decimal.mod(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>Decimal</code></td>
            <td>Divisor.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Decimal` - Remainder

#### Example

```ts
const d = new Decimal('10');
const remainder = d.mod(new Decimal('3'));
console.log(remainder.toString()); // '1'
```

---

### `.abs()` {#abs}

Get the absolute value.

```ts title="Syntax"
decimal.abs()
```

#### Returns
`Decimal` - Absolute value

#### Example

```ts
const d = new Decimal('-19.99');
console.log(d.abs().toString()); // '19.99'
```

---

### `.neg()` {#neg}

Negate the decimal.

```ts title="Syntax"
decimal.neg()
```

#### Returns
`Decimal` - Negated value

#### Example

```ts
const d = new Decimal('19.99');
console.log(d.neg().toString()); // '-19.99'
```

---

### `.round(precision)` {#round}

Round to a given number of decimal places.

```ts title="Syntax"
decimal.round(precision)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>precision</code> <label label="required" /></td>
            <td><code>number</code></td>
            <td>Number of decimal places to round to.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Decimal` - Rounded value

#### Example

```ts
const d = new Decimal('19.995');
console.log(d.round(2).toString()); // '20.00'
```

---

### `.equals(other)` {#equals}

Check if two decimals are equal.

```ts title="Syntax"
decimal.equals(other)
```

#### Returns
`boolean` - True if equal

---

### `.compare(other)` {#compare}

Compare two decimals.

```ts title="Syntax"
decimal.compare(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>Decimal</code></td>
            <td>Decimal to compare against.</td>
        </tr>
    </tbody>
</table>

#### Returns
`number` - Returns `-1` if less than, `0` if equal, `1` if greater than

#### Example

```ts
const a = new Decimal('10');
const b = new Decimal('20');
console.log(a.compare(b)); // -1
console.log(b.compare(a)); // 1
console.log(a.compare(a)); // 0
```

---

### `.isZero()` {#iszero}

Check if the decimal is zero.

```ts title="Syntax"
decimal.isZero()
```

#### Returns
`boolean` - True if the value is zero

#### Example

```ts
const zero = new Decimal('0');
console.log(zero.isZero()); // true

const nonZero = new Decimal('1');
console.log(nonZero.isZero()); // false
```

---

### `.isNegative()` {#isnegative}

Check if the decimal is negative.

```ts title="Syntax"
decimal.isNegative()
```

#### Returns
`boolean` - True if the value is negative

#### Example

```ts
const neg = new Decimal('-5');
console.log(neg.isNegative()); // true

const pos = new Decimal('5');
console.log(pos.isNegative()); // false
```

## Complete examples

### Financial calculations

```ts
import { Surreal, Decimal, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Product prices
const product = await db.create(new Table('products')).content({
    name: 'Widget',
    price: new Decimal('19.99'),
    tax_rate: new Decimal('0.075') // 7.5%
});

// Calculate total with tax
const price = new Decimal(product.price);
const taxRate = new Decimal(product.tax_rate);
const tax = price.mul(taxRate);
const total = price.add(tax);

console.log('Price:', price.toString());     // 19.99
console.log('Tax:', tax.toString());         // 1.49925
console.log('Total:', total.toString());     // 21.48925
```

### Order total calculation

```ts
interface OrderItem {
    product: string;
    price: Decimal;
    quantity: number;
}

function calculateOrderTotal(items: OrderItem[]): Decimal {
    let total = new Decimal('0');
    
    for (const item of items) {
        const itemTotal = item.price.mul(new Decimal(item.quantity));
        total = total.add(itemTotal);
    }
    
    return total;
}

const items = [
    { product: 'Widget', price: new Decimal('19.99'), quantity: 2 },
    { product: 'Gadget', price: new Decimal('29.99'), quantity: 1 },
    { product: 'Tool', price: new Decimal('9.99'), quantity: 3 }
];

const orderTotal = calculateOrderTotal(items);
console.log('Order total:', orderTotal.toString()); // '99.94'
```

### Currency exchange

```ts
// Exchange rate calculation
const usdAmount = new Decimal('100.00');
const exchangeRate = new Decimal('1.18'); // USD to EUR

const eurAmount = usdAmount.mul(exchangeRate);
console.log(`$${usdAmount} = €${eurAmount}`);
```

### Interest calculation

```ts
// Calculate compound interest
function calculateCompoundInterest(
    principal: Decimal,
    rate: Decimal,
    periods: number
): Decimal {
    let amount = principal;
    const onePlusRate = new Decimal('1').add(rate);
    
    for (let i = 0; i < periods; i++) {
        amount = amount.mul(onePlusRate);
    }
    
    return amount;
}

const principal = new Decimal('1000.00');
const annualRate = new Decimal('0.05'); // 5%
const years = 10;

const finalAmount = calculateCompoundInterest(principal, annualRate, years);
console.log('Final amount:', finalAmount.toString());
```

### Database storage

```ts
// Store precise financial data
const transaction = await db.create(new Table('transactions')).content({
    user: userId,
    amount: new Decimal('149.99'),
    fee: new Decimal('2.50'),
    tax: new Decimal('11.25'),
    timestamp: DateTime.now()
});

// Query and calculate
const transactions = await db.select(new Table('transactions'));
let totalAmount = new Decimal('0');

for (const txn of transactions) {
    totalAmount = totalAmount.add(txn.amount);
}

console.log('Total:', totalAmount.toString());
```

### Percentage calculations

```ts
// Calculate percentage
function calculatePercentage(value: Decimal, percentage: Decimal): Decimal {
    return value.mul(percentage).div(new Decimal('100'));
}

const price = new Decimal('100.00');
const discount = new Decimal('15'); // 15%

const discountAmount = calculatePercentage(price, discount);
const finalPrice = price.sub(discountAmount);

console.log('Discount:', discountAmount.toString()); // '15.00'
console.log('Final price:', finalPrice.toString()); // '85.00'
```

### Scientific calculations

```ts
// High precision scientific value
const avogadroNumber = new Decimal('6.02214076e23');
const boltzmannConstant = new Decimal('1.380649e-23');

console.log('Avogadro:', avogadroNumber.toString());
console.log('Boltzmann:', boltzmannConstant.toString());
```

## Best practices

### 1. Use strings for input

```ts
// Good: String input preserves precision
const price = new Decimal('19.99');

// Caution: Number input may have floating-point errors
const price = new Decimal(19.99); // Already has float imprecision
```

### 2. Keep as Decimal for calculations

```ts
// Good: All calculations use Decimal
const subtotal = price.mul(quantity);
const tax = subtotal.mul(taxRate);
const total = subtotal.add(tax);

// Avoid: Converting to number mid-calculation
const subtotal = price.toFloat() * quantity; // Loses precision
```

### 3. Convert to string for display

```ts
// Good: String preserves precision
const display = price.toString();
console.log(`$${display}`);

// Avoid: Number may lose precision
const display = price.toFloat().toFixed(2);
```

### 4. Store decimals in database

```ts
// Good: Store as Decimal
await db.create(table).content({
    price: new Decimal('19.99')
});

// Avoid: Store as number
await db.create(table).content({
    price: 19.99 // Float imprecision
});
```

## Common pitfalls

### Floating-point input

```ts
// Problem: Number already has floating-point error
const wrong = new Decimal(0.1 + 0.2); // 0.30000000000000004

// Solution: Use string input
const correct = new Decimal('0.1').add(new Decimal('0.2')); // 0.3
```

### Premature conversion to number

```ts
// Problem: Loses precision
const result = price.toFloat() + tax.toFloat();

// Solution: Keep as Decimal
const result = price.add(tax);
```

## See also

- [Data types overview](/docs/reference/javascript/api/values/) - All custom data types
- [Query builders](/docs/reference/javascript/api/queries/) - Using Decimal in queries
- [SurrealQL decimal](/docs/reference/query-language/language-primitives/data-types/numbers.md) - Database decimal type

---

Source: https://surrealdb.com/docs/reference/javascript/api/values/duration

# Duration

Time duration values with support for multiple units and nanosecond precision.

The `Duration` class provides time duration values with nanosecond precision and support for human-readable formats like `"5h30m"`.

**Import:**
```ts
import { Duration } from 'surrealdb';
```

**Source:** [value/duration.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/value/duration.ts)

## Constructor

### `new Duration(value)` {#constructor}

Create a new duration value.

```ts title="Syntax"
new Duration(duration) // Clone existing
new Duration(string) // Parse human-readable string
new Duration([seconds, nanoseconds]) // From tuple
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>value</code> <label label="required" /></td>
            <td><code>Duration | string | [bigint, bigint]</code></td>
            <td>Value to create duration from.</td>
        </tr>
    </tbody>
</table>

#### Examples

```ts
// Parse human-readable durations
const fiveMinutes = new Duration('5m');
const oneHour = new Duration('1h');
const complex = new Duration('2h30m15s');
const precise = new Duration('1s500ms250us125ns');

// From tuple [seconds, nanoseconds]
const duration = new Duration([300n, 0n]); // 5 minutes

// Clone existing
const clone = new Duration(fiveMinutes);
```

## Supported units

<table>
    <thead>
        <tr>
            <th>Unit</th>
            <th>Symbol</th>
            <th>Example</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Nanoseconds</td>
            <td><code>ns</code></td>
            <td><code>"500ns"</code></td>
        </tr>
        <tr>
            <td>Microseconds</td>
            <td><code>us</code>, <code>µs</code></td>
            <td><code>"250us"</code></td>
        </tr>
        <tr>
            <td>Milliseconds</td>
            <td><code>ms</code></td>
            <td><code>"100ms"</code></td>
        </tr>
        <tr>
            <td>Seconds</td>
            <td><code>s</code></td>
            <td><code>"30s"</code></td>
        </tr>
        <tr>
            <td>Minutes</td>
            <td><code>m</code></td>
            <td><code>"5m"</code></td>
        </tr>
        <tr>
            <td>Hours</td>
            <td><code>h</code></td>
            <td><code>"2h"</code></td>
        </tr>
        <tr>
            <td>Days</td>
            <td><code>d</code></td>
            <td><code>"7d"</code></td>
        </tr>
        <tr>
            <td>Weeks</td>
            <td><code>w</code></td>
            <td><code>"4w"</code></td>
        </tr>
        <tr>
            <td>Years</td>
            <td><code>y</code></td>
            <td><code>"1y"</code></td>
        </tr>
    </tbody>
</table>

## Static methods

### `Duration.nanoseconds(ns)` {#nanoseconds-static}

Create a duration from a number of nanoseconds.

```ts title="Syntax"
Duration.nanoseconds(ns)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ns</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Number of nanoseconds.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Duration representing the given nanoseconds

#### Example

```ts
const d = Duration.nanoseconds(500);
```

---

### `Duration.microseconds(µs)` {#microseconds-static}

Create a duration from a number of microseconds.

```ts title="Syntax"
Duration.microseconds(µs)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>µs</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Number of microseconds.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Duration representing the given microseconds

#### Example

```ts
const d = Duration.microseconds(250);
```

---

### `Duration.milliseconds(ms)` {#milliseconds-static}

Create a duration from a number of milliseconds.

```ts title="Syntax"
Duration.milliseconds(ms)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>ms</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Number of milliseconds.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Duration representing the given milliseconds

#### Example

```ts
const d = Duration.milliseconds(1500);
console.log(d.toString()); // '1s500ms'
```

---

### `Duration.seconds(s)` {#seconds-static}

Create a duration from a number of seconds.

```ts title="Syntax"
Duration.seconds(s)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>s</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Number of seconds.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Duration representing the given seconds

#### Example

```ts
const d = Duration.seconds(90);
console.log(d.toString()); // '1m30s'
```

---

### `Duration.minutes(m)` {#minutes-static}

Create a duration from a number of minutes.

```ts title="Syntax"
Duration.minutes(m)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>m</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Number of minutes.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Duration representing the given minutes

#### Example

```ts
const d = Duration.minutes(5);
console.log(d.toString()); // '5m'
```

---

### `Duration.hours(h)` {#hours-static}

Create a duration from a number of hours.

```ts title="Syntax"
Duration.hours(h)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>h</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Number of hours.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Duration representing the given hours

#### Example

```ts
const d = Duration.hours(2);
console.log(d.toString()); // '2h'
```

---

### `Duration.days(d)` {#days-static}

Create a duration from a number of days.

```ts title="Syntax"
Duration.days(d)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>d</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Number of days.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Duration representing the given days

#### Example

```ts
const d = Duration.days(7);
console.log(d.toString()); // '1w'
```

---

### `Duration.weeks(w)` {#weeks-static}

Create a duration from a number of weeks.

```ts title="Syntax"
Duration.weeks(w)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>w</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Number of weeks.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Duration representing the given weeks

#### Example

```ts
const d = Duration.weeks(4);
console.log(d.toString()); // '4w'
```

---

### `Duration.years(y)` {#years-static}

Create a duration from a number of years.

```ts title="Syntax"
Duration.years(y)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>y</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Number of years.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Duration representing the given years

#### Example

```ts
const d = Duration.years(1);
console.log(d.toString()); // '1y'
```

---

### `Duration.parseFloat(input)` {#parsefloat}

Parse a duration from a float string with a unit suffix.

```ts title="Syntax"
Duration.parseFloat(input)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>input</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>A float string with a unit suffix (e.g., <code>"1.5s"</code>).</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Parsed duration

#### Example

```ts
const d = Duration.parseFloat('1.5s');
console.log(d.milliseconds); // 1500n
```

---

### `Duration.measure()` {#measure}

Returns a function that, when called, returns the elapsed `Duration` since the call to `Duration.measure()`.

```ts title="Syntax"
Duration.measure()
```

#### Returns
`() => Duration` - A function that returns the elapsed duration

#### Example

```ts
const elapsed = Duration.measure();

// ... perform some operation ...

const duration = elapsed();
console.log('Operation took:', duration.toString());
```

## Property getters

Property getters for accessing the total duration in specific units. All return `bigint`.

<table>
    <thead>
        <tr>
            <th>Property</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>nanoseconds</code></td>
            <td><code>bigint</code></td>
            <td>Total nanoseconds</td>
        </tr>
        <tr>
            <td><code>microseconds</code></td>
            <td><code>bigint</code></td>
            <td>Total microseconds</td>
        </tr>
        <tr>
            <td><code>milliseconds</code></td>
            <td><code>bigint</code></td>
            <td>Total milliseconds</td>
        </tr>
        <tr>
            <td><code>seconds</code></td>
            <td><code>bigint</code></td>
            <td>Whole seconds</td>
        </tr>
        <tr>
            <td><code>minutes</code></td>
            <td><code>bigint</code></td>
            <td>Total whole minutes</td>
        </tr>
        <tr>
            <td><code>hours</code></td>
            <td><code>bigint</code></td>
            <td>Total whole hours</td>
        </tr>
        <tr>
            <td><code>days</code></td>
            <td><code>bigint</code></td>
            <td>Total whole days</td>
        </tr>
        <tr>
            <td><code>weeks</code></td>
            <td><code>bigint</code></td>
            <td>Total whole weeks</td>
        </tr>
        <tr>
            <td><code>years</code></td>
            <td><code>bigint</code></td>
            <td>Total whole years</td>
        </tr>
    </tbody>
</table>

### Examples

```ts
const duration = new Duration('2h30m');

console.log(duration.hours);        // 2n
console.log(duration.minutes);      // 150n
console.log(duration.seconds);      // 9000n
console.log(duration.milliseconds); // 9000000n
console.log(duration.nanoseconds);  // 9000000000000n
```

## Instance methods

### `.toString()` {#tostring}

Convert to human-readable string.

```ts title="Syntax"
duration.toString()
```

#### Returns
`string` - Human-readable duration string

#### Example

```ts
const duration = new Duration('2h30m15s');
console.log(duration.toString()); // '2h30m15s'
```

---

### `.toJSON()` {#tojson}

Serialise for JSON.

```ts title="Syntax"
duration.toJSON()
```

#### Returns
`string` - Duration string for JSON

---

### `.toCompact()` {#tocompact}

Convert to a compact tuple representation.

```ts title="Syntax"
duration.toCompact()
```

#### Returns
`[bigint, bigint] | [bigint] | []` - Compact representation: `[seconds, nanoseconds]`, `[seconds]` if nanoseconds is zero, or `[]` for a zero duration.

#### Example

```ts
const d = new Duration('5m30s');
console.log(d.toCompact()); // [330n, 0n] or [330n]
```

---

### `.add(other)` {#add}

Add another duration.

```ts title="Syntax"
duration.add(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>Duration</code></td>
            <td>Duration to add.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Sum of durations

#### Example

```ts
const base = new Duration('1h');
const extra = new Duration('30m');
const total = base.add(extra);
console.log(total.toString()); // '1h30m'
```

---

### `.sub(other)` {#sub}

Subtract another duration.

```ts title="Syntax"
duration.sub(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>Duration</code></td>
            <td>Duration to subtract.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Difference of durations

#### Example

```ts
const total = new Duration('2h');
const part = new Duration('30m');
const remaining = total.sub(part);
console.log(remaining.toString()); // '1h30m'
```

---

### `.mul(factor)` {#mul}

Multiply a duration by a scalar.

```ts title="Syntax"
duration.mul(factor)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>factor</code> <label label="required" /></td>
            <td><code>number | bigint</code></td>
            <td>Scalar to multiply by.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Scaled duration

#### Example

```ts
const base = new Duration('30m');
const doubled = base.mul(2);
console.log(doubled.toString()); // '1h'
```

---

### `.div(divisor)` {#div}

Divide a duration. Overloaded: dividing by a `Duration` returns the ratio as `bigint`, dividing by a number returns a new `Duration`.

```ts title="Syntax"
duration.div(divisor: Duration)       // Returns bigint (ratio)
duration.div(divisor: number | bigint) // Returns Duration
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>divisor</code> <label label="required" /></td>
            <td><code>Duration | number | bigint</code></td>
            <td>Duration for ratio, or scalar for division.</td>
        </tr>
    </tbody>
</table>

#### Returns
`bigint` when dividing by a `Duration`, `Duration` when dividing by a number or bigint.

#### Examples

```ts
const total = new Duration('2h');
const unit = new Duration('30m');

// Ratio: how many 30m intervals in 2h?
const ratio = total.div(unit); // 4n

// Scalar division
const half = total.div(2);
console.log(half.toString()); // '1h'
```

---

### `.mod(mod)` {#mod}

Get the remainder after dividing by another duration.

```ts title="Syntax"
duration.mod(mod)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>mod</code> <label label="required" /></td>
            <td><code>Duration</code></td>
            <td>Duration to divide by.</td>
        </tr>
    </tbody>
</table>

#### Returns
`Duration` - Remainder after division

#### Example

```ts
const total = new Duration('2h20m');
const interval = new Duration('1h');
const remainder = total.mod(interval);
console.log(remainder.toString()); // '20m'
```

---

### `.equals(other)` {#equals}

Check if two durations are equal.

```ts title="Syntax"
duration.equals(other)
```

#### Returns
`boolean` - True if equal

## Complete examples

### Timeouts and expiration

```ts
import { Surreal, Duration, DateTime, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Set session timeout
const session = await db.create(new Table('sessions')).content({
    user: userId,
    created_at: DateTime.now(),
    timeout: new Duration('24h')
});
```

### Query timeouts

```ts
// Set timeout on queries
const users = await db.select(new Table('users'))
    .timeout(new Duration('5s'));

// Timeout on complex query
const result = await db.query(`
    SELECT * FROM complex_view
`).timeout(new Duration('30s')).collect();
```

### Rate limiting

```ts
// Define rate limit window
const rateLimit = {
    window: new Duration('1m'),
    maxRequests: 100
};

// Store rate limit data
await db.create(new Table('rate_limits')).content({
    user: userId,
    window: rateLimit.window,
    requests: 1,
    window_start: DateTime.now()
});
```

### Scheduled tasks

```ts
// Schedule task with delay
const task = await db.create(new Table('tasks')).content({
    name: 'Send email',
    delay: new Duration('5m'),
    created_at: DateTime.now()
});
```

### Cache TTL

```ts
// Set cache entry with TTL
const cacheEntry = await db.create(new Table('cache')).content({
    key: 'user:123',
    value: userData,
    ttl: new Duration('1h'),
    cached_at: DateTime.now()
});

// Check if cache is still valid
function isCacheValid(entry: typeof cacheEntry): boolean {
    const elapsed = DateTime.now().milliseconds - entry.cached_at.milliseconds;
    return elapsed < entry.ttl.milliseconds;
}
```

### Performance measurement

```ts
// Measure operation duration
const elapsed = Duration.measure();

// ... perform operation ...

const duration = elapsed();
console.log('Operation took:', duration.toString());
```

### Conversion examples

```ts
// Parse from string
const duration = new Duration('2h30m');

// Access as different units
const ms = duration.milliseconds; // 9000000n
const secs = duration.seconds;    // 9000n
const mins = duration.minutes;    // 150n
const hrs = duration.hours;       // 2n

// Back to string
const str = duration.toString(); // '2h30m'

// Arithmetic
const doubled = duration.add(duration);
console.log(doubled.toString()); // '5h'
```

### Complex durations

```ts
// Very precise timing
const precise = new Duration('1s500ms250us125ns');

// Multiple units
const complex = new Duration('1d2h30m15s');

// Arithmetic
const extended = complex.add(new Duration('12h'));
console.log(extended.toString()); // '1d14h30m15s'
```

### Static factory methods

```ts
// Create durations from numeric values
const timeout = Duration.seconds(30);
const cacheTTL = Duration.hours(1);
const retryDelay = Duration.milliseconds(500);
const oneWeek = Duration.weeks(1);

// Useful when values come from config or computation
const maxRetries = 3;
const backoff = Duration.seconds(2).mul(maxRetries);
```

## Best practices

### 1. Use human-readable formats

```ts
// Good: Clear intent
const timeout = new Duration('30s');
const cacheTTL = new Duration('1h');

// Avoid: Raw numbers
const timeout = new Duration([30n, 0n]);
```

### 2. Use Duration for time arithmetic

```ts
// Good: Type-safe duration arithmetic
const base = new Duration('1h');
const extended = base.add(new Duration('30m'));

// Good: Use static factories for computed values
const delay = Duration.seconds(retryCount * 2);
```

### 3. Store durations in database

```ts
// Good: Store as Duration for type safety
await db.create(table).content({
    timeout: new Duration('24h')
});

// Avoid: Store as number
await db.create(table).content({
    timeout: 86400000 // What unit is this?
});
```

### 4. Use appropriate units

```ts
// Good: Use largest appropriate unit
const oneDay = new Duration('1d');
const oneWeek = new Duration('1w');

// Avoid: Unnecessary smaller units
const oneDay = new Duration('24h');
const oneWeek = new Duration('168h');
```

### 5. Use duration.measure() for timing

```ts
// Good: Built-in measurement
const elapsed = Duration.measure();
await performOperation();
console.log('Took:', elapsed().toString());
```

## See also

- [DateTime](/docs/reference/javascript/api/values/datetime.md) - Datetime values
- [Data types overview](/docs/reference/javascript/api/values/) - All custom data types
- [Query builders](/docs/reference/javascript/api/queries/) - Using Duration in queries
- [SurrealQL durations](/docs/reference/query-language/language-primitives/data-types/durations.md) - Database duration type

---

Source: https://surrealdb.com/docs/reference/javascript/api/values/file-ref

# FileRef

The FileRef class represents a reference to a file stored in SurrealDB.

The `FileRef` class represents a reference to a file stored in SurrealDB. File references are returned when querying records that contain [file fields](/docs/reference/query-language/language-primitives/data-types/files.md) and provide access to file metadata such as the bucket, key, and media type.

**Import:**
```ts
import { FileRef } from 'surrealdb';
```

**Source:** [value/file-ref.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/value/file-ref.ts)

## Constructor

```ts
new FileRef(bucket: string, key: string)
```

### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>bucket</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The name of the storage bucket.</td>
        </tr>
        <tr>
            <td><code>key</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The unique key identifying the file within the bucket.</td>
        </tr>
    </tbody>
</table>

### Example

```ts
const fileRef = new FileRef('avatars', 'profile-photo.png');

await db.create(new RecordId('users', 'john')).content({
    name: 'John',
    avatar: fileRef
});
```

## Properties

### `bucket` {#bucket}

The name of the storage bucket containing the file.

**Type:** `string`

**Example:**
```ts
console.log(fileRef.bucket); // "avatars"
```

### `key` {#key}

The unique key identifying the file within its bucket.

**Type:** `string`

**Example:**
```ts
console.log(fileRef.key); // "profile-photo.png"
```

## Instance methods

### `.toString()` {#tostring}

Returns the string representation of the file reference.

```ts title="Method Syntax"
fileRef.toString()
```

#### Returns
`string` - The file reference as a string

---

### `.toJSON()` {#tojson}

Serializes the file reference for JSON output.

```ts title="Method Syntax"
fileRef.toJSON()
```

#### Returns
`string` - The JSON-safe representation

---

### `.equals(other)` {#equals}

Compares this file reference with another for equality.

```ts title="Method Syntax"
fileRef.equals(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>unknown</code></td>
            <td>The value to compare against.</td>
        </tr>
    </tbody>
</table>

#### Returns
`boolean` - True if both file references point to the same file

#### Example
```ts
const ref1 = record1.avatar;
const ref2 = record2.avatar;

if (ref1.equals(ref2)) {
    console.log('Same file');
}
```

## Examples

### Reading file references from records

```ts
const user = await db.select(new RecordId('users', 'john'));

if (user.avatar instanceof FileRef) {
    console.log('Bucket:', user.avatar.bucket);
    console.log('Key:', user.avatar.key);
}
```

### Querying records with file fields

```ts
const [records] = await db.query<[{ avatar: FileRef }[]]>(
    'SELECT avatar FROM users WHERE avatar IS NOT NONE'
);

for (const record of records) {
    console.log(record.avatar.key);
}
```

## See also

- [Value types](/docs/reference/javascript/concepts/value-types.md) - Overview of all value types
- [File uploads](/docs/reference/query-language/language-primitives/data-types/files.md) - Working with files in SurrealDB
- [Data types](/docs/reference/javascript/api/values/) - All custom data types

---

Source: https://surrealdb.com/docs/reference/javascript/api/values/geometry

# Geometry

Geometric and spatial data types for location-based applications.

Geometry classes provide support for spatial and geographic data using GeoJSON-compatible structures. These types are used in location-based applications and geospatial queries.

**Import:**
```ts
import { 
    GeometryPoint,
    GeometryLine,
    GeometryPolygon,
    GeometryMultiPoint,
    GeometryMultiLine,
    GeometryMultiPolygon,
    GeometryCollection
} from 'surrealdb';
```

**Source:** [value/geometry.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/value/geometry.ts)

## Geometry types

### `GeometryPoint` {#geometrypoint}

A single point in 2D space (longitude, latitude).

#### Constructor

```ts
new GeometryPoint([longitude, latitude])
new GeometryPoint(point) // Clone existing
```

#### Properties

##### `point` {#geometrypoint-point}

The underlying point data as a `[longitude, latitude]` tuple.

**Type:** `[number, number]`

##### `coordinates` {#geometrypoint-coordinates}

GeoJSON-compatible coordinates for this point. Equivalent to `point`.

**Type:** `[number, number]`

#### Example

```ts
// Create a point (San Francisco)
const point = new GeometryPoint([-122.4194, 37.7749]);

console.log(point.point);       // [-122.4194, 37.7749]
console.log(point.coordinates); // [-122.4194, 37.7749]

// Store location
await db.create(new Table('locations')).content({
    name: 'Office',
    position: point
});
```

---

### `GeometryLine` {#geometryline}

A line defined by two or more points.

#### Constructor

```ts
new GeometryLine([point1, point2, ...points])
new GeometryLine(line) // Clone existing
```

#### Properties

##### `line` {#geometryline-line}

The underlying array of `GeometryPoint` objects that make up this line.

**Type:** `GeometryPoint[]`

##### `coordinates` {#geometryline-coordinates}

GeoJSON-compatible coordinates for this line.

**Type:** `[number, number][]`

#### Methods

##### `.close()` {#geometryline-close}

Closes the line by appending the first point to the end, if it is not already closed. This is useful when constructing polygon boundaries.

```ts
const line = new GeometryLine([
    new GeometryPoint([0, 0]),
    new GeometryPoint([10, 0]),
    new GeometryPoint([10, 10])
]);

line.close();
// Line now ends with GeometryPoint([0, 0])
```

#### Example

```ts
// Create a line (path between two cities)
const line = new GeometryLine([
    new GeometryPoint([-122.4194, 37.7749]), // San Francisco
    new GeometryPoint([-118.2437, 34.0522])  // Los Angeles
]);

console.log(line.line);        // [GeometryPoint, GeometryPoint]
console.log(line.coordinates); // [[-122.4194, 37.7749], [-118.2437, 34.0522]]

// Multi-segment line
const route = new GeometryLine([
    new GeometryPoint([0, 0]),
    new GeometryPoint([1, 1]),
    new GeometryPoint([2, 1]),
    new GeometryPoint([3, 2])
]);
```

---

### `GeometryPolygon` {#geometrypolygon}

A polygon defined by one or more lines (outer boundary and optional holes).

#### Constructor

```ts
new GeometryPolygon([outerBoundary, ...holes])
new GeometryPolygon(polygon) // Clone existing
```

#### Properties

##### `polygon` {#geometrypolygon-polygon}

The underlying array of `GeometryLine` objects (outer boundary and optional holes).

**Type:** `GeometryLine[]`

##### `coordinates` {#geometrypolygon-coordinates}

GeoJSON-compatible coordinates for this polygon.

**Type:** `[number, number][][]`

#### Example

```ts
// Create a triangle
const triangle = new GeometryPolygon([
    new GeometryLine([
        new GeometryPoint([0, 0]),
        new GeometryPoint([4, 0]),
        new GeometryPoint([2, 3]),
        new GeometryPoint([0, 0]) // Close the polygon
    ])
]);

console.log(triangle.polygon); // [GeometryLine]

// Polygon with hole (donut shape)
const donut = new GeometryPolygon([
    // Outer boundary
    new GeometryLine([
        new GeometryPoint([0, 0]),
        new GeometryPoint([10, 0]),
        new GeometryPoint([10, 10]),
        new GeometryPoint([0, 10]),
        new GeometryPoint([0, 0])
    ]),
    // Inner hole
    new GeometryLine([
        new GeometryPoint([2, 2]),
        new GeometryPoint([8, 2]),
        new GeometryPoint([8, 8]),
        new GeometryPoint([2, 8]),
        new GeometryPoint([2, 2])
    ])
]);
```

---

### `GeometryMultiPoint` {#geometrymultipoint}

A collection of points.

#### Constructor

```ts
new GeometryMultiPoint([point1, point2, ...points])
new GeometryMultiPoint(multiPoint) // Clone existing
```

#### Properties

##### `points` {#geometrymultipoint-points}

The underlying array of `GeometryPoint` objects.

**Type:** `GeometryPoint[]`

##### `coordinates` {#geometrymultipoint-coordinates}

GeoJSON-compatible coordinates for this multi-point.

**Type:** `[number, number][]`

#### Example

```ts
// Multiple store locations
const stores = new GeometryMultiPoint([
    new GeometryPoint([-122.4194, 37.7749]), // SF
    new GeometryPoint([-118.2437, 34.0522]), // LA
    new GeometryPoint([-87.6298, 41.8781])   // Chicago
]);

console.log(stores.points); // [GeometryPoint, GeometryPoint, GeometryPoint]
```

---

### `GeometryMultiLine` {#geometrymultiline}

A collection of lines.

#### Constructor

```ts
new GeometryMultiLine([line1, line2, ...lines])
new GeometryMultiLine(multiLine) // Clone existing
```

#### Properties

##### `lines` {#geometrymultiline-lines}

The underlying array of `GeometryLine` objects.

**Type:** `GeometryLine[]`

##### `coordinates` {#geometrymultiline-coordinates}

GeoJSON-compatible coordinates for this multi-line.

**Type:** `[number, number][][]`

#### Example

```ts
// Multiple delivery routes
const routes = new GeometryMultiLine([
    new GeometryLine([
        new GeometryPoint([0, 0]),
        new GeometryPoint([1, 1])
    ]),
    new GeometryLine([
        new GeometryPoint([2, 2]),
        new GeometryPoint([3, 3])
    ])
]);

console.log(routes.lines); // [GeometryLine, GeometryLine]
```

---

### `GeometryMultiPolygon` {#geometrymultipolygon}

A collection of polygons.

#### Constructor

```ts
new GeometryMultiPolygon([polygon1, polygon2, ...polygons])
new GeometryMultiPolygon(multiPolygon) // Clone existing
```

#### Properties

##### `polygons` {#geometrymultipolygon-polygons}

The underlying array of `GeometryPolygon` objects.

**Type:** `GeometryPolygon[]`

##### `coordinates` {#geometrymultipolygon-coordinates}

GeoJSON-compatible coordinates for this multi-polygon.

**Type:** `[number, number][][][]`

#### Example

```ts
// Multiple service areas
const areas = new GeometryMultiPolygon([
    new GeometryPolygon([/* first area */]),
    new GeometryPolygon([/* second area */])
]);

console.log(areas.polygons); // [GeometryPolygon, GeometryPolygon]
```

---

### `GeometryCollection` {#geometrycollection}

A heterogeneous collection of geometry types.

#### Constructor

```ts
new GeometryCollection([geometry1, geometry2, ...geometries])
new GeometryCollection(collection) // Clone existing
```

#### Properties

##### `collection` {#geometrycollection-collection}

The underlying array of geometry objects in this collection.

**Type:** `Geometry[]`

##### `geometries` {#geometrycollection-geometries}

Getter that returns the array of geometry objects. Equivalent to `collection`.

**Type:** `Geometry[]`

##### `coordinates` {#geometrycollection-coordinates}

GeoJSON-compatible coordinates for this collection.

**Type:** `unknown[]`

#### Example

```ts
// Mixed geometry types
const collection = new GeometryCollection([
    new GeometryPoint([0, 0]),
    new GeometryLine([
        new GeometryPoint([1, 1]),
        new GeometryPoint([2, 2])
    ]),
    new GeometryPolygon([/* polygon data */])
]);

console.log(collection.collection);  // [GeometryPoint, GeometryLine, GeometryPolygon]
console.log(collection.geometries);  // [GeometryPoint, GeometryLine, GeometryPolygon]
```

## Common methods

All geometry types share these methods:

### `.is(type)` {#is}

Type guard that checks if a geometry matches a specific type. Each geometry subclass implements this method.

```ts
is(type: "Point"): this is GeometryPoint
is(type: "LineString"): this is GeometryLine
is(type: "Polygon"): this is GeometryPolygon
is(type: "MultiPoint"): this is GeometryMultiPoint
is(type: "MultiLineString"): this is GeometryMultiLine
is(type: "MultiPolygon"): this is GeometryMultiPolygon
is(type: "GeometryCollection"): this is GeometryCollection
```

```ts
function describeGeometry(geo: Geometry) {
    if (geo.is("Point")) {
        console.log('Point at', geo.point);
    } else if (geo.is("Polygon")) {
        console.log('Polygon with', geo.polygon.length, 'rings');
    }
}
```

### `.toJSON()` {#tojson}

Convert to GeoJSON format.

```ts
const point = new GeometryPoint([-122.4194, 37.7749]);
console.log(point.toJSON());
// { type: "Point", coordinates: [-122.4194, 37.7749] }
```

### `.toString()` {#tostring}

Convert to JSON string.

```ts
const point = new GeometryPoint([-122.4194, 37.7749]);
console.log(point.toString());
// '{"type":"Point","coordinates":[-122.4194,37.7749]}'
```

### `.clone()` {#clone}

Create a deep copy.

```ts
const original = new GeometryPoint([0, 0]);
const copy = original.clone();
```

### `.equals(other)` {#equals}

Check if two geometries are equal.

```ts
const a = new GeometryPoint([0, 0]);
const b = new GeometryPoint([0, 0]);
console.log(a.equals(b)); // true
```

## Complete examples

### Store locations

```ts
import { Surreal, GeometryPoint, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Store locations with coordinates
const locations = [
    {
        name: 'Main Office',
        address: '123 Market St, San Francisco, CA',
        position: new GeometryPoint([-122.4194, 37.7749])
    },
    {
        name: 'LA Branch',
        address: '456 Sunset Blvd, Los Angeles, CA',
        position: new GeometryPoint([-118.2437, 34.0522])
    }
];

for (const location of locations) {
    await db.create(new Table('locations')).content(location);
}
```

### Delivery routes

```ts
// Define delivery route
const route = new GeometryLine([
    new GeometryPoint([-122.4194, 37.7749]), // Start: SF
    new GeometryPoint([-122.2711, 37.8044]), // Stop 1: Oakland
    new GeometryPoint([-122.0838, 37.3861]), // Stop 2: Mountain View
    new GeometryPoint([-121.8863, 37.3382])  // End: San Jose
]);

await db.create(new Table('routes')).content({
    driver: new RecordId('drivers', 'john'),
    route: route,
    estimated_time: new Duration('2h30m'),
    created_at: DateTime.now()
});
```

### Service areas

```ts
// Define service coverage area (polygon)
const serviceArea = new GeometryPolygon([
    new GeometryLine([
        new GeometryPoint([-122.5, 37.7]),
        new GeometryPoint([-122.3, 37.7]),
        new GeometryPoint([-122.3, 37.8]),
        new GeometryPoint([-122.5, 37.8]),
        new GeometryPoint([-122.5, 37.7]) // Close the polygon
    ])
]);

await db.create(new Table('service_areas')).content({
    name: 'SF Downtown',
    area: serviceArea,
    active: true
});
```

### Geospatial queries

```ts
// Find locations near a point
const centerPoint = new GeometryPoint([-122.4194, 37.7749]);

const nearbyLocations = await db.query(`
    SELECT * FROM locations 
    WHERE geo::distance(position, $center) < 5000
    ORDER BY geo::distance(position, $center)
`, {
    center: centerPoint
}).collect();

console.log('Nearby locations:', nearbyLocations);
```

### Polygon containment

```ts
// Check if a point is within a polygon
const region = new GeometryPolygon([
    new GeometryLine([
        new GeometryPoint([0, 0]),
        new GeometryPoint([10, 0]),
        new GeometryPoint([10, 10]),
        new GeometryPoint([0, 10]),
        new GeometryPoint([0, 0])
    ])
]);

const testPoint = new GeometryPoint([5, 5]);

const result = await db.query(`
    RETURN $region CONTAINS $point
`, {
    region,
    point: testPoint
}).collect();

console.log('Point is inside:', result[0]);
```

### Multiple locations (MultiPoint)

```ts
// Store multiple branch locations
const branches = new GeometryMultiPoint([
    new GeometryPoint([-122.4194, 37.7749]), // SF
    new GeometryPoint([-118.2437, 34.0522]), // LA
    new GeometryPoint([-87.6298, 41.8781]),  // Chicago
    new GeometryPoint([-74.0060, 40.7128])   // NYC
]);

await db.create(new Table('companies')).content({
    name: 'Tech Corp',
    headquarters: new GeometryPoint([-122.4194, 37.7749]),
    all_branches: branches,
    founded: new DateTime('2020-01-01')
});
```

### Distance calculations

```ts
// Calculate distance between two points
const pointA = new GeometryPoint([-122.4194, 37.7749]); // SF
const pointB = new GeometryPoint([-118.2437, 34.0522]); // LA

const distance = await db.query(`
    RETURN geo::distance($a, $b)
`, {
    a: pointA,
    b: pointB
}).collect();

console.log('Distance in meters:', distance[0]);
```

### GeoJSON export

```ts
// Export as GeoJSON for mapping libraries
const point = new GeometryPoint([-122.4194, 37.7749]);
const geoJson = point.toJSON();

// Use with mapping libraries (Leaflet, Mapbox, etc.)
/*
{
    type: "Point",
    coordinates: [-122.4194, 37.7749]
}
*/
```

### Complex region with holes

```ts
// Define a park with a lake (hole)
const park = new GeometryPolygon([
    // Outer boundary (park border)
    new GeometryLine([
        new GeometryPoint([0, 0]),
        new GeometryPoint([100, 0]),
        new GeometryPoint([100, 100]),
        new GeometryPoint([0, 100]),
        new GeometryPoint([0, 0])
    ]),
    // Inner hole (lake)
    new GeometryLine([
        new GeometryPoint([40, 40]),
        new GeometryPoint([60, 40]),
        new GeometryPoint([60, 60]),
        new GeometryPoint([40, 60]),
        new GeometryPoint([40, 40])
    ])
]);

await db.create(new Table('parks')).content({
    name: 'Central Park',
    boundary: park,
    has_lake: true
});
```

## GeoJSON compatibility

All geometry types are compatible with GeoJSON format:

```ts
const point = new GeometryPoint([-122.4194, 37.7749]);
const geoJson = point.toJSON();

// GeoJSON structure
console.log(geoJson);
/*
{
    type: "Point",
    coordinates: [-122.4194, 37.7749]
}
*/

// Use with any GeoJSON-compatible library
```

## Best practices

### 1. Use correct coordinate order

```ts
// Good: [longitude, latitude] (GeoJSON standard)
const point = new GeometryPoint([-122.4194, 37.7749]);

// Avoid: [latitude, longitude] (Google Maps format)
const wrong = new GeometryPoint([37.7749, -122.4194]);
```

### 2. Close polygons properly

```ts
// Good: First and last points are the same
const polygon = new GeometryPolygon([
    new GeometryLine([
        new GeometryPoint([0, 0]),
        new GeometryPoint([10, 0]),
        new GeometryPoint([10, 10]),
        new GeometryPoint([0, 10]),
        new GeometryPoint([0, 0]) // Closes the polygon
    ])
]);

// The library automatically closes polygons if needed
```

### 3. Use appropriate Geometry type

```ts
// Good: Single location
const office = new GeometryPoint([-122.4194, 37.7749]);

// Good: Multiple locations
const branches = new GeometryMultiPoint([point1, point2, point3]);

// Avoid: Using MultiPoint for single location
const wrong = new GeometryMultiPoint([point1]);
```

### 4. Validate coordinates

```ts
// Good: Valid coordinates
const valid = new GeometryPoint([-122.4194, 37.7749]);

// Avoid: Invalid coordinates (out of range)
// Longitude: -180 to 180, Latitude: -90 to 90
const invalid = new GeometryPoint([200, 100]); // Will create but may cause issues
```

## Use cases

- **Location-based Services** - Store and query business locations
- **Delivery Systems** - Define delivery routes and service areas
- **Real Estate** - Property boundaries and service zones
- **Transportation** - Transit routes and coverage areas
- **Environmental** - Conservation areas, wildlife habitats
- **Urban Planning** - City zones, districts, infrastructure

## See also

- [Data types overview](/docs/reference/javascript/api/values/) - All custom data types
- [Query builders](/docs/reference/javascript/api/queries/) - Using Geometry in queries
- [SurrealQL geometry](/docs/reference/query-language/language-primitives/data-types/geometries.md) - Database geometry types
- [GeoJSON Specification](https://geojson.org/) - GeoJSON format standard

---

Source: https://surrealdb.com/docs/reference/javascript/api/values/range

# Range

Generic range values for numeric, datetime, and other ordered types.

The `Range` class provides generic range values for representing inclusive or exclusive ranges of ordered data types.

**Import:**
```ts
import { Range, BoundIncluded, BoundExcluded } from 'surrealdb';
```

**Source:** [value/range.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/value/range.ts)

## Type parameters

- `Beg` - The type of the beginning bound
- `End` - The type of the ending bound

## Bound types

Bounds are represented using `BoundIncluded` and `BoundExcluded` classes:

- `new BoundIncluded(value)` - The bound includes the value
- `new BoundExcluded(value)` - The bound excludes the value
- `undefined` - Unbounded (no limit)

**Type:** `Bound<T> = BoundIncluded<T> | BoundExcluded<T> | undefined`

## Constructor

### `new Range(begin, end)` {#constructor}

Create a new range value.

```ts title="Syntax"
new Range(begin, end)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>begin</code> <label label="required" /></td>
            <td><code>Bound&lt;Beg&gt;</code></td>
            <td>The beginning bound (can be inclusive, exclusive, or undefined for unbounded).</td>
        </tr>
        <tr>
            <td><code>end</code> <label label="required" /></td>
            <td><code>Bound&lt;End&gt;</code></td>
            <td>The ending bound (can be inclusive, exclusive, or undefined for unbounded).</td>
        </tr>
    </tbody>
</table>

#### Examples

```ts
// Numeric range [1, 100]
const numericRange = new Range(
    new BoundIncluded(1),
    new BoundIncluded(100)
);

// Date range
const dateRange = new Range(
    new BoundIncluded(new DateTime('2024-01-01')),
    new BoundIncluded(new DateTime('2024-12-31'))
);

// Half-open range [0, 10)
const halfOpen = new Range(
    new BoundIncluded(0),
    new BoundExcluded(10)
);
```

## Properties

### `begin` {#begin}

The beginning bound of the range.

**Type:** `Bound<Beg>` - A `BoundIncluded<Beg>`, `BoundExcluded<Beg>`, or `undefined`

```ts
const range = new Range(
    new BoundIncluded(1),
    new BoundIncluded(10)
);

console.log(range.begin.value); // 1
console.log(range.begin instanceof BoundIncluded); // true
```

---

### `end` {#end}

The ending bound of the range.

**Type:** `Bound<End>` - A `BoundIncluded<End>`, `BoundExcluded<End>`, or `undefined`

```ts
const range = new Range(
    new BoundIncluded(1),
    new BoundIncluded(10)
);

console.log(range.end.value); // 10
console.log(range.end instanceof BoundIncluded); // true
```

## Instance methods

### `.toString()` {#tostring}

Convert to string representation.

```ts title="Syntax"
range.toString()
```

#### Returns
`string` - Range string (e.g., `1..10`, `1..=10`, `1>..10`)

#### Examples

```ts
// Inclusive range [1, 10]
const inclusive = new Range(
    new BoundIncluded(1),
    new BoundIncluded(10)
);
console.log(inclusive.toString()); // '1..=10'

// Exclusive range (1, 10)
const exclusive = new Range(
    new BoundExcluded(1),
    new BoundExcluded(10)
);
console.log(exclusive.toString()); // '1>..10'

// Half-open [1, 10)
const halfOpen = new Range(
    new BoundIncluded(1),
    new BoundExcluded(10)
);
console.log(halfOpen.toString()); // '1..10'
```

---

### `.toJSON()` {#tojson}

Serialise for JSON.

```ts title="Syntax"
range.toJSON()
```

#### Returns
`string` - Range string representation

---

### `.equals(other)` {#equals}

Check if two ranges are equal.

```ts title="Syntax"
range.equals(other)
```

#### Returns
`boolean` - True if equal

## Complete examples

### Numeric ranges

```ts
import { Surreal, Range, BoundIncluded } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Define age range for filtering
const adultRange = new Range(
    new BoundIncluded(18),
    new BoundIncluded(120)
);

// Query with range
const adults = await db.query(`
    SELECT * FROM users 
    WHERE age IN $range
`, {
    range: adultRange
}).collect();
```

### Date ranges

```ts
import { DateTime, Range, BoundIncluded } from 'surrealdb';

// Define fiscal year
const fiscalYear = new Range(
    new BoundIncluded(new DateTime('2024-01-01T00:00:00Z')),
    new BoundIncluded(new DateTime('2024-12-31T23:59:59Z'))
);

// Query events in date range
const events = await db.query(`
    SELECT * FROM events 
    WHERE date IN $range
`, {
    range: fiscalYear
}).collect();
```

### Price ranges

```ts
import { Decimal, Range, BoundIncluded } from 'surrealdb';

// Define price range for product filtering
const budgetRange = new Range(
    new BoundIncluded(new Decimal('0')),
    new BoundIncluded(new Decimal('100'))
);

const products = await db.query(`
    SELECT * FROM products 
    WHERE price IN $range
`, {
    range: budgetRange
}).collect();
```

### Score ranges

```ts
import { Range, BoundIncluded } from 'surrealdb';

// Grade ranges
const gradeA = new Range(
    new BoundIncluded(90),
    new BoundIncluded(100)
);

const gradeB = new Range(
    new BoundIncluded(80),
    new BoundIncluded(89)
);

// Assign grades
const students = await db.query(`
    SELECT *,
        CASE
            WHEN score IN $gradeA THEN 'A'
            WHEN score IN $gradeB THEN 'B'
            ELSE 'C'
        END AS grade
    FROM students
`, {
    gradeA,
    gradeB
}).collect();
```

### Time-based filtering

```ts
import { DateTime, Duration, Range, BoundIncluded } from 'surrealdb';

// Recent activity (last 30 days)
const now = DateTime.now();
const thirtyDaysAgo = now.sub(new Duration('30d'));

const recentRange = new Range(
    new BoundIncluded(thirtyDaysAgo),
    new BoundIncluded(now)
);

const recentActivity = await db.query(`
    SELECT * FROM activity_log 
    WHERE timestamp IN $range
    ORDER BY timestamp DESC
`, {
    range: recentRange
}).collect();
```

### Pagination with ID ranges

```ts
import { RecordId, Range, BoundIncluded, BoundExcluded } from 'surrealdb';

// Fetch records in ID range
const idRange = new Range(
    new BoundIncluded(new RecordId('users', 'a')),
    new BoundExcluded(new RecordId('users', 'f'))
);

const users = await db.query(`
    SELECT * FROM users 
    WHERE id IN $range
`, {
    range: idRange
}).collect();
```

### Exclusive ranges

```ts
import { Range, BoundExcluded } from 'surrealdb';

// Exclusive range (not including boundaries)
const exclusive = new Range(
    new BoundExcluded(0),
    new BoundExcluded(100)
);
// Matches: 0 < x < 100

const results = await db.query(`
    SELECT * FROM measurements 
    WHERE value IN $range
`, {
    range: exclusive
}).collect();
```

### Half-open ranges

```ts
import { Range, BoundIncluded, BoundExcluded } from 'surrealdb';

// Half-open range [start, end)
const halfOpen = new Range(
    new BoundIncluded(0),
    new BoundExcluded(10)
);
// Matches: 0 <= x < 10

// Common for array-like indexing
const items = await db.query(`
    SELECT * FROM items 
    WHERE index IN $range
`, {
    range: halfOpen
}).collect();
```

### Multiple ranges

```ts
import { Range, BoundIncluded } from 'surrealdb';

// Define multiple severity levels
const low = new Range(
    new BoundIncluded(0),
    new BoundIncluded(3)
);

const medium = new Range(
    new BoundIncluded(4),
    new BoundIncluded(6)
);

const high = new Range(
    new BoundIncluded(7),
    new BoundIncluded(10)
);

// Query by severity
const criticalIssues = await db.query(`
    SELECT * FROM issues 
    WHERE severity IN $range
`, {
    range: high
}).collect();
```

### Range queries in application

```ts
import { Range, BoundIncluded, BoundExcluded, DateTime } from 'surrealdb';

function queryByRange<T>(
    start: T,
    end: T,
    inclusive: boolean = true
): Range<T, T> {
    return new Range(
        new BoundIncluded(start),
        inclusive ? new BoundIncluded(end) : new BoundExcluded(end)
    );
}

// Usage
const dateRange = queryByRange(
    new DateTime('2024-01-01'),
    new DateTime('2024-12-31')
);

const orders = await db.query(`
    SELECT * FROM orders 
    WHERE created_at IN $range
`, {
    range: dateRange
}).collect();
```

## Range notation

SurrealDB uses specific notation for ranges:

| Notation | Description | Example |
|----------|-------------|---------|
| `a..=b` | Inclusive both ends `[a, b]` | `1..=10` |
| `a..b` | Inclusive start, exclusive end `[a, b)` | `1..10` |
| `a>..b` | Exclusive start, exclusive end `(a, b)` | `1>..10` |
| `a>..=b` | Exclusive start, inclusive end `(a, b]` | `1>..=10` |

## Best practices

### 1. Use appropriate inclusivity

```ts
// Good: Inclusive for dates (including full days)
const dateRange = new Range(
    new BoundIncluded(new DateTime('2024-01-01T00:00:00Z')),
    new BoundIncluded(new DateTime('2024-01-31T23:59:59Z'))
);

// Good: Exclusive end for array-like indexing
const arrayRange = new Range(
    new BoundIncluded(0),
    new BoundExcluded(10)
); // [0, 10)
```

### 2. Type safety

```ts
// Good: Explicit types
const range: Range<number, number> = new Range(
    new BoundIncluded(1),
    new BoundIncluded(100)
);

// Good: Consistent types
const dateRange: Range<DateTime, DateTime> = new Range(
    new BoundIncluded(DateTime.now()),
    new BoundIncluded(DateTime.now().add(new Duration('1d')))
);
```

### 3. Validate bounds

```ts
// Good: Ensure start <= end
function createRange<T>(start: T, end: T): Range<T, T> | null {
    if (start > end) {
        console.error('Invalid range: start must be <= end');
        return null;
    }
    
    return new Range(
        new BoundIncluded(start),
        new BoundIncluded(end)
    );
}
```

## Use cases

- **Filtering** - Filter records by numeric, date, or other ordered values
- **Pagination** - Query records in ID ranges
- **Time Windows** - Define time-based query windows
- **Price Filtering** - E-commerce price range searches
- **Score Categorisation** - Categorise by score ranges (grades, ratings)
- **Geographic Bounds** - Coordinate ranges for maps

## See also

- [RecordIdRange](/docs/reference/javascript/api/values/record-id.md#recordidrange) - Range of record IDs
- [DateTime](/docs/reference/javascript/api/values/datetime.md) - Datetime values for date ranges
- [Decimal](/docs/reference/javascript/api/values/decimal.md) - Precise numbers for price ranges
- [Data types overview](/docs/reference/javascript/api/values/) - All custom data types
- [SurrealQL Ranges](/docs/reference/query-language/language-primitives/data-types/ranges.md) - Database range syntax

---

Source: https://surrealdb.com/docs/reference/javascript/api/values/record-id

# RecordId

Type-safe record identifiers with table name and ID components.

The `RecordId` class provides type-safe record identifiers in SurrealDB. Each record ID consists of a table name and an ID value, represented as `table:id` in SurrealQL.

**Import:**
```ts
import { RecordId } from 'surrealdb';
```

**Source:** [value/record-id.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sqon/src/value/record-id.ts)

## Type parameters

- `Tb extends string` - The table name (string literal type for type safety)
- `Id` - The ID value type (string, number, object, etc.)
- `T` - The type of the records in the table (optional, defaults to `unknown`). It is taken from the [`Table`](/docs/reference/javascript/api/values/table.md) the record ID is created from. See [Typed record IDs](#typed).

> [!NOTE]
> `T` is a compile-time annotation only. It is not validated against the data returned by the server.

## Constructor

### `new RecordId(table, id)` {#constructor}

Create a new record identifier.

```ts title="Syntax"
new RecordId(table, id)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> <label label="required" /></td>
            <td><code>Tb | Table&lt;Tb, T&gt;</code></td>
            <td>The table name, either as a string or a <code>Table</code> instance.</td>
        </tr>
        <tr>
            <td><code>id</code> <label label="required" /></td>
            <td><code>Id</code></td>
            <td>The record ID value (string, number, object, array, or RecordId).</td>
        </tr>
    </tbody>
</table>

#### Examples

```ts
// String IDs
const user = new RecordId('users', 'john');
const post = new RecordId('posts', '123');

// Numeric IDs
const product = new RecordId('products', 42);

// Complex/structured IDs
const metric = new RecordId('metrics', { 
    service: 'api', 
    timestamp: 1234567890 
});

// Array IDs
const compound = new RecordId('items', ['type-a', 123]);

// Nested RecordId
const nested = new RecordId('links', new RecordId('users', 'alice'));

// Type-safe with generics
const typedUser = new RecordId<'users', string>('users', 'john');
```

### Typed record IDs {#typed}

A record ID created from a [typed table](/docs/reference/javascript/api/values/table.md#typed) keeps the table's record type. The methods that take a record ID then infer their result type, so you don't need to pass the record type on each call.

```ts
interface User {
    name: string;
    email: string;
}

const users = Table.of<User>()('users');
const id = new RecordId(users, 'john');   // RecordId<"users", string, User>

const user = await db.select(id);         // User | undefined (with `id`)
await db.update(id).merge({ name: 'John' });
await db.delete(id);
```

`select`, `create`, `update`, `upsert` and `delete` all infer the record type this way.

There are several ways to create a record ID, and they differ in the type they produce:

| Form | Resulting type |
| --- | --- |
| `new RecordId('users', 'john')` | `RecordId<'users', string, unknown>` |
| `new RecordId<'users', string>('users', 'john')` | `RecordId<'users', string, unknown>` |
| `new RecordId(Table.of<User>()('users'), 'john')` | `RecordId<'users', string, User>` |
| `const id: RecordId<'users', string, User> = new RecordId('users', 'john')` | `RecordId<'users', string, User>` |
| `new RecordId<'users', string, User>('users', 'john')` | `RecordId<'users', string, User>` |
| `new RecordId(new Table<User>('users'), 'john')` | `RecordId<string, string, User>` |
| `new RecordId(new Table('users'), 1)` | `RecordId<'users', number, unknown>` |

There is no `new RecordId<User>(...)` shorthand, because the first type parameter is the table name. Take the record type from a typed table, or annotate the variable. A record ID built from a table declared as `new Table<User>('users')` keeps the record type, but its table name is only typed as `string`.

A typed record ID can be used where an untyped one is expected, but not where a different record type or table name is expected:

```ts
const a: RecordId = id;                                  // OK
const b: RecordId<'users', string, Post> = id;           // Error: wrong record type
const c: RecordId<'user', string, User> = new RecordId('users', 'john');   // Error: 'users' is not 'user'
```

If you pass an explicit type on the call, such as `db.select<Post>(id)`, that type takes precedence over the record ID's own. Prefer letting the type come from the record ID.

## Properties

### `table` {#table}

The table name component, returned as a `Table` instance.

**Type:** `Table<Tb, T>`

```ts
const userId = new RecordId('users', 'john');
console.log(userId.table);      // Table { name: 'users' }
console.log(userId.table.name); // 'users'
```

---

### `id` {#id}

The ID value component.

**Type:** `Id`

```ts
const userId = new RecordId('users', 'john');
console.log(userId.id); // 'john'

const productId = new RecordId('products', 42);
console.log(productId.id); // 42
```

## Instance methods

### `.toString()` {#tostring}

Convert the record ID to its string representation.

```ts title="Syntax"
recordId.toString()
```

#### Returns
`string` - String representation in format `table:id`

#### Examples

```ts
const userId = new RecordId('users', 'john');
console.log(userId.toString()); // 'users:john'

const productId = new RecordId('products', 42);
console.log(productId.toString()); // 'products:42'

const complex = new RecordId('items', { type: 'widget', id: 5 });
console.log(complex.toString()); // 'items:{ type: "widget", id: 5 }'
```

---

### `.toJSON()` {#tojson}

Serialise the record ID for JSON.

```ts title="Syntax"
recordId.toJSON()
```

#### Returns
`string` - JSON-safe string representation

```ts
const userId = new RecordId('users', 'john');
console.log(JSON.stringify(userId)); // '"users:john"'
```

---

### `.equals(other)` {#equals}

Check if two record IDs are equal.

```ts title="Syntax"
recordId.equals(other)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code> <label label="required" /></td>
            <td><code>unknown</code></td>
            <td>Value to compare.</td>
        </tr>
    </tbody>
</table>

#### Returns
`boolean` - True if equal

```ts
const a = new RecordId('users', 'john');
const b = new RecordId('users', 'john');
const c = new RecordId('users', 'jane');

console.log(a.equals(b)); // true
console.log(a.equals(c)); // false
```

## Complete examples

### Basic usage

```ts
import { Surreal, RecordId } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Create record with specific ID
const user = await db.create(new RecordId('users', 'john'))
    .content({
        name: 'John Doe',
        email: 'john@example.com'
    });

// Select by record ID
const retrieved = await db.select(new RecordId('users', 'john'));

// Update by record ID
await db.update(new RecordId('users', 'john'))
    .merge({ status: 'active' });

// Delete by record ID
await db.delete(new RecordId('users', 'john'));
```

### Type-safe record IDs

```ts
interface User {
    id: RecordId<'users', string>;
    name: string;
    email: string;
}

// TypeScript enforces correct table name
const userId: RecordId<'users', string> = new RecordId('users', 'alice');

// This would be a type error:
// const wrong: RecordId<'posts', string> = new RecordId('users', 'alice');

const user = await db.select<User>(userId);
```

### Complex ID structures

```ts
// Time-series data with structured IDs
const metricId = new RecordId('metrics', {
    service: 'api',
    host: 'server-01',
    timestamp: 1234567890
});

await db.create(metricId).content({
    cpu: 45.2,
    memory: 78.1
});

// Compound keys
const sessionId = new RecordId('sessions', {
    userId: 'john',
    deviceId: 'device-123'
});
```

### Parsing from strings

Use `StringRecordId` to pass a record ID string directly without parsing it into table and ID components. The string is sent as-is to SurrealDB.

```ts
import { StringRecordId } from 'surrealdb';

// Use a record ID string directly
const userInput = 'users:john';
const userId = new StringRecordId(userInput);
const user = await db.select(userId);

// Use in queries
const recordId = new StringRecordId('users:alice');
const result = await db.select(recordId);
```

### Working with relations

```ts
// Create relationship using record IDs
const from = new RecordId('users', 'john');
const to = new RecordId('posts', '123');

const edge = await db.relate(
    from,
    new Table('likes'),
    to,
    { timestamp: DateTime.now() }
);

console.log('Edge from:', edge.in);  // RecordId('users', 'john')
console.log('Edge to:', edge.out);   // RecordId('posts', '123')
```

### UUID-based record IDs

```ts
import { Uuid } from 'surrealdb';

// Generate time-ordered IDs
const userId = new RecordId('users', Uuid.v7());

// Generate random IDs
const sessionId = new RecordId('sessions', Uuid.v4());

await db.create(userId).content({
    name: 'Alice',
    created: DateTime.now()
});
```

### Validation

```ts
// Table names are validated automatically
try {
    const invalid = new RecordId('invalid-table!', 'id');
} catch (error) {
    console.error('Invalid table name');
}
```

## RecordIdRange {#recordidrange}

The `RecordIdRange` class represents a range of record IDs for querying multiple records. It takes the same type parameters as `RecordId`: `RecordIdRange<Tb, Id, T>`, where `Id` is the type of the bounds and `T` is the record type, taken from the `Table` it is created from.

Bounds are specified using the `BoundIncluded` and `BoundExcluded` classes, or `undefined` for unbounded:
- `new BoundIncluded(value)` - inclusive bound (`>=` or `<=`)
- `new BoundExcluded(value)` - exclusive bound (`>` or `<`)
- `undefined` - unbounded (no limit)

### Constructor

```ts
new RecordIdRange(table, begin, end)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> <label label="required" /></td>
            <td><code>Tb | Table&lt;Tb, T&gt;</code></td>
            <td>The table name, either as a string or a <code>Table</code> instance.</td>
        </tr>
        <tr>
            <td><code>begin</code> <label label="required" /></td>
            <td><code>Bound&lt;Id&gt;</code></td>
            <td>Start of the range. Use <code>BoundIncluded</code>, <code>BoundExcluded</code>, or <code>undefined</code>.</td>
        </tr>
        <tr>
            <td><code>end</code> <label label="required" /></td>
            <td><code>Bound&lt;Id&gt;</code></td>
            <td>End of the range. Use <code>BoundIncluded</code>, <code>BoundExcluded</code>, or <code>undefined</code>.</td>
        </tr>
    </tbody>
</table>

### Examples

```ts
import { RecordIdRange, BoundIncluded, BoundExcluded } from 'surrealdb';

// Select range of users from 'a' (inclusive) to 'f' (exclusive)
const range = new RecordIdRange(
    'users',
    new BoundIncluded('a'),
    new BoundExcluded('f'),
);
const users = await db.select(range);

// Numeric range: 1 (inclusive) to 100 (inclusive)
const numRange = new RecordIdRange(
    'items',
    new BoundIncluded(1),
    new BoundIncluded(100),
);
const items = await db.select(numRange);

// Unbounded start, exclusive end
const upToF = new RecordIdRange(
    'users',
    undefined,
    new BoundExcluded('f'),
);

// Inclusive start, unbounded end
const fromA = new RecordIdRange(
    'users',
    new BoundIncluded('a'),
    undefined,
);

// Delete range
await db.delete(new RecordIdRange(
    'logs',
    new BoundIncluded('2024-01-01'),
    new BoundExcluded('2024-02-01'),
));
```

### Typed ranges {#typed-ranges}

A range created from a [typed table](/docs/reference/javascript/api/values/table.md#typed) keeps the record type, in the same way as a [typed record ID](#typed):

```ts
const users = Table.of<User>()('users');

const range = new RecordIdRange(users, new BoundIncluded('a'), new BoundExcluded('f'));
// RecordIdRange<"users", string, User>

const some = await db.select(range);      // User[] (with `id`)
await db.delete(range);
```

`select`, `update`, `upsert` and `delete` infer the record type from a range. A range created from a plain table name, such as `new RecordIdRange('users', ...)`, is `RecordIdRange<'users', string, unknown>`, and you can still pass the type on the call: `db.select<User>(range)`.

## Best practices

### 1. Use type parameters

```ts
// Good: Type-safe
type UserId = RecordId<'users', string>;
const userId: UserId = new RecordId('users', 'john');

// Better: Enforce at compile time
function getUser(id: RecordId<'users', string>) {
    return db.select(id);
}
```

### 2. Prefer RecordId over strings

```ts
// Good: Type-safe with validation
const user = await db.select(new RecordId('users', 'john'));

// Avoid: String-based (no validation)
const user = await db.query('SELECT * FROM users:john').collect();
```

### 3. Use structured IDs for complex keys

```ts
// Good: Structured composite key
const id = new RecordId('events', {
    userId: 'john',
    timestamp: Date.now()
});

// Avoid: String concatenation
const id = new RecordId('events', `john-${Date.now()}`);
```

### 4. Handle construction errors

```ts
// Good: Safe construction
try {
    const id = new RecordId(tableName, idValue);
    const record = await db.select(id);
} catch (error) {
    console.error('Invalid record ID format');
}
```

## See also

- [Data types overview](/docs/reference/javascript/api/values/) - All custom data types
- [Table](/docs/reference/javascript/api/values/table.md) - Table references
- [Query builders](/docs/reference/javascript/api/queries/) - Using RecordId in queries
- [SurrealQL record IDs](/docs/reference/query-language/language-primitives/data-types/record-ids.md) - Database record ID documentation

---

Source: https://surrealdb.com/docs/reference/javascript/api/values/table

# Table

Table references, optionally typed with the records they contain.

The `Table` class is a reference to a database table. It can optionally carry the type of the records stored in the table, so that TypeScript type checks query results and the data you write.

**Import:**
```ts
import { Table } from 'surrealdb';
```

**Source:** [value/table.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sqon/src/value/table.ts)

## Type parameters

- `Tb` - The table name, as a string literal type (optional, defaults to `string`)
- `T` - The type of the records in this table (optional, defaults to `unknown`)

> [!NOTE]
> `T` is a compile-time annotation only. It is not validated against the data returned by the server.

## Constructor

### `new Table(name)` {#constructor}

Create a new table reference.

```ts title="Syntax"
new Table(name)
new Table<RecordType>(name)
new Table<"name", RecordType>(name)
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>name</code> <label label="required" /></td>
            <td><code>string</code></td>
            <td>The table name.</td>
        </tr>
    </tbody>
</table>

#### Examples

```ts
// Basic table reference
const users = new Table('users');

// Table reference with a record type
interface User {
    id: RecordId;
    name: string;
    email: string;
}

const users = new Table<User>('users');

// TypeScript now knows the result type
const allUsers: User[] = await db.select(users);
```

### Declaring a typed table {#typed}

There are four ways to give a table a record type. They differ in whether the table name is kept as a literal type (`"users"`) or widened to `string`.

```ts
// 1. Record type only
const users = new Table<User>('users');                    // Table<string, User>

// 2. Table.of: keeps the literal name and the record type
const users = Table.of<User>()('users');                   // Table<"users", User>

// 3. Annotated variable: the name is inferred, the record type comes from the annotation
const users: Table<'users', User> = new Table('users');    // Table<"users", User>

// 4. Both type arguments, explicitly
const users = new Table<'users', User>('users');           // Table<"users", User>
```

Form 1 is the shortest and is enough for most code. Use forms 2 to 4 if you need the table name as a literal type, for example in a function that only accepts `Table<'users', User>`. A table declared with form 1 is `Table<string, User>`, which can't be passed where a `Table<'users', User>` is expected.

In forms 3 and 4, a table name or record type that doesn't match the annotation is a compile error:

```ts
const users: Table<'user', User> = new Table('users');    // Error: "users" is not "user"
```

A table created without a record type, such as `new Table('users')`, is `Table<'users', unknown>`. You can still give the record type on the call instead:

```ts
const allUsers = await db.select<User>(new Table('users'));
```

The record type is picked up by `select`, `create`, `update`, `upsert`, `delete` and `insert`.

A `RecordId` or `RecordIdRange` created from a typed table keeps the record type, so the methods that take one infer it as well:

```ts
const users = Table.of<User>()('users');
const id = new RecordId(users, 'john');   // RecordId<"users", string, User>
const range = new RecordIdRange(users, new BoundIncluded(1), new BoundExcluded(5));

const user = await db.select(id);         // User | undefined (with `id`)
const some = await db.select(range);      // User[] (with `id`)
```

Both take the record type as a third type parameter, `RecordId<Tb, Id, T>` and `RecordIdRange<Tb, Id, T>`. There is no `new RecordId<User>(...)` shorthand: take the record type from a typed table, or annotate the variable, as in `const id: RecordId<'users', string, User> = new RecordId('users', 'john')`.

> [!NOTE]
> Passing an explicit type that differs from the table's own record type, such as `db.select<Post>(users)` where `users` is a `Table<'users', User>`, is not an error: the explicit type takes precedence. Prefer letting the type come from the table.

## Properties

### `name` {#name}

The table name.

**Type:** `Tb` (the table name type, `string` unless declared as a literal)

```ts
const users = new Table('users');
console.log(users.name); // 'users'
```

## Instance methods

### `.toString()` {#tostring}

Convert to string representation (escaped table name).

```ts title="Syntax"
table.toString()
```

#### Returns
`string` - Escaped table name

#### Example

```ts
const users = new Table('users');
console.log(users.toString()); // 'users'

// Handles special characters
const special = new Table('my-table');
console.log(special.toString()); // Properly escaped
```

---

### `.toJSON()` {#tojson}

Serialise for JSON.

```ts title="Syntax"
table.toJSON()
```

#### Returns
`string` - Table name

---

### `.equals(other)` {#equals}

Check if two table references are equal.

```ts title="Syntax"
table.equals(other)
```

#### Returns
`boolean` - True if equal

## Complete examples

### Type-safe queries

```ts
import { Surreal, Table } from 'surrealdb';

// Define your data model
interface User {
    id: RecordId;
    name: string;
    email: string;
    age: number;
}

interface Post {
    id: RecordId;
    title: string;
    content: string;
    author: RecordId;
}

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Create typed table references
const users = new Table<User>('users');
const posts = new Table<Post>('posts');

// TypeScript knows the return types
const allUsers: User[] = await db.select(users);
const allPosts: Post[] = await db.select(posts);

// Type checking in IDE
allUsers[0].name; // ✓ TypeScript knows this exists
allUsers[0].title; // ✗ TypeScript error: Property 'title' does not exist
```

### Select operations

```ts
const users = new Table<User>('users');

// Select all records
const all = await db.select(users);

// Select with filtering
const active = await db.select(users)
    .where('active = true');

// Select with pagination
const page = await db.select(users)
    .limit(10)
    .start(0);
```

### Create operations

```ts
const users = new Table<User>('users');

// Create single record with auto-generated ID
const user = await db.create(users).content({
    name: 'John Doe',
    email: 'john@example.com',
    age: 30
});

// Create multiple records
const newUsers = await db.insert(users, [
    { name: 'Alice', email: 'alice@example.com', age: 25 },
    { name: 'Bob', email: 'bob@example.com', age: 35 }
]);
```

### Update operations

```ts
const users = new Table<User>('users');

// Update all records in table
const updated = await db.update(users)
    .merge({ verified: true })
    .where('email_confirmed = true');

// Update with content
await db.update(users)
    .content({
        name: 'New Name',
        email: 'new@example.com',
        age: 31
    })
    .where('id = users:john');
```

### Delete operations

```ts
const users = new Table<User>('users');

// Delete all inactive users
const deleted = await db.delete(users)
    .where('inactive = true');

// Get deleted records
const deletedWithData = await db.delete(users)
    .output('BEFORE')
    .where('created_at < $date', { 
        date: DateTime.parse('2024-01-01') 
    });
```

### Multiple tables

```ts
interface Product {
    id: RecordId;
    name: string;
    price: Decimal;
}

interface Order {
    id: RecordId;
    user: RecordId;
    products: RecordId[];
    total: Decimal;
}

const products = new Table<Product>('products');
const orders = new Table<Order>('orders');
const users = new Table<User>('users');

// Work with multiple tables
const [allProducts, allOrders, allUsers] = await Promise.all([
    db.select(products),
    db.select(orders),
    db.select(users)
]);
```

### Query builder pattern

```ts
const users = new Table<User>('users');

// Build complex queries
const query = db.select(users)
    .fields('name', 'email', 'age')
    .where('age >= $minAge', { minAge: 18 })
    .order('name ASC')
    .limit(100);

const results = await query;
```

### Live queries

```ts
const users = new Table<User>('users');

// Subscribe to table changes
const subscription = await db.live(users);

for await (const update of subscription) {
    if (update.action === 'CREATE') {
        console.log('New user:', update.result);
    } else if (update.action === 'UPDATE') {
        console.log('User updated:', update.result);
    } else if (update.action === 'DELETE') {
        console.log('User deleted:', update.result);
    }
}

// Clean up
await subscription.kill();
```

### Transactions

```ts
const users = new Table<User>('users');
const orders = new Table<Order>('orders');

const txn = await db.beginTransaction();

try {
    // Create user
    const user = await txn.create(users).content({
        name: 'Jane Doe',
        email: 'jane@example.com'
    });
    
    // Create order
    await txn.create(orders).content({
        user: user.id,
        products: [],
        total: new Decimal('0')
    });
    
    await txn.commit();
} catch (error) {
    await txn.cancel();
}
```

### Generic table functions

```ts
// Create reusable functions
// `Table<string, T>` accepts any table, and picks up its record type
async function getAllFromTable<T>(
    db: Surreal,
    table: Table<string, T>
): Promise<T[]> {
    return db.select(table);
}

async function deleteAllFromTable<T>(
    db: Surreal,
    table: Table<string, T>
): Promise<T[]> {
    return db.delete(table);
}

// Use with any table
const users = await getAllFromTable(db, new Table<User>('users'));
const posts = await getAllFromTable(db, new Table<Post>('posts'));
```

### Table schema validation

```ts
// Define table with strict types
interface StrictUser {
    id: RecordId<'users', string>;
    name: string;
    email: string;
    age: number;
    created_at: DateTime;
    updated_at: DateTime;
}

const users = new Table<StrictUser>('users');

// TypeScript enforces the schema
const user = await db.create(users).content({
    name: 'Alice',
    email: 'alice@example.com',
    age: 25,
    created_at: DateTime.now(),
    updated_at: DateTime.now()
    // Missing fields or wrong types will cause TypeScript errors
});
```

### Repository pattern

```ts
class UserRepository {
    private table = new Table<User>('users');
    
    constructor(private db: Surreal) {}
    
    async getAll(): Promise<User[]> {
        return this.db.select(this.table);
    }
    
    async getById(id: RecordId): Promise<User | undefined> {
        return this.db.select(id);
    }
    
    async create(data: Omit<User, 'id'>): Promise<User> {
        return this.db.create(this.table).content(data);
    }
    
    async update(id: RecordId, data: Partial<User>): Promise<User> {
        return this.db.update(id).merge(data);
    }
    
    async delete(id: RecordId): Promise<void> {
        await this.db.delete(id);
    }
    
    async findByEmail(email: string): Promise<User[]> {
        return this.db.select(this.table)
            .where('email = $email', { email });
    }
}

// Usage
const userRepo = new UserRepository(db);
const users = await userRepo.getAll();
const john = await userRepo.findByEmail('john@example.com');
```

## Best practices

### 1. Give tables a record type

```ts
// Good: Type-safe
const users = new Table<User>('users');
const results = await db.select(users); // User[] (with `id`)

// Avoid: No record type
const users = new Table('users');
const results = await db.select(users); // unknown[]
```

If you need the table name as a literal type as well, see [Declaring a typed table](#typed).

### 2. Define interfaces for tables

```ts
// Good: Clear data structure
interface User {
    id: RecordId;
    name: string;
    email: string;
    created_at: DateTime;
}

const users = new Table<User>('users');

// Avoid: Using 'unknown' or no interface
const users = new Table('users');
```

### 3. Reuse table references

```ts
// Good: Single source of truth
const USERS_TABLE = new Table<User>('users');

async function getUsers() {
    return db.select(USERS_TABLE);
}

async function createUser(data: Omit<User, 'id'>) {
    return db.create(USERS_TABLE).content(data);
}

// Avoid: Creating new references everywhere
async function getUsers() {
    return db.select(new Table('users'));
}
```

### 4. Use with recordId for type safety

```ts
// Good: Strongly typed
interface User {
    id: RecordId<'users', string>;
    name: string;
}

const users = new Table<User>('users');

// TypeScript ensures correct table name
const userId: RecordId<'users', string> = new RecordId('users', 'john');
```

## Common patterns

### Enum-based tables

```ts
enum Tables {
    Users = 'users',
    Posts = 'posts',
    Comments = 'comments'
}

const users = new Table<User>(Tables.Users);
const posts = new Table<Post>(Tables.Posts);
```

### Table factory

```ts
function createTable<T>(name: string): Table<string, T> {
    return new Table<T>(name);
}

const users = createTable<User>('users');
const posts = createTable<Post>('posts');
```

## See also

- [RecordId](/docs/reference/javascript/api/values/record-id.md) - Record identifiers
- [Data types overview](/docs/reference/javascript/api/values/) - All custom data types
- [Query builders](/docs/reference/javascript/api/queries/) - Using Table in queries
- [SurrealQL tables](/docs/reference/query-language/statements/define/table.md) - Database table definitions

---

Source: https://surrealdb.com/docs/reference/javascript/api/values/uuid

# Uuid

Universally unique identifiers for generating unique IDs.

The `Uuid` class provides universally unique identifiers (UUIDs) with support for UUID v4 (random) and UUID v7 (time-ordered) generation.

**Import:**
```ts
import { Uuid } from 'surrealdb';
```

**Source:** [value/uuid.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/value/uuid.ts)

## Constructor

### `new Uuid(value)` {#constructor}

Create a UUID from an existing value.

```ts title="Syntax"
new Uuid(uuid) // Clone existing
new Uuid(string) // Parse from string
new Uuid(bytes) // From binary representation
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>value</code> <label label="required" /></td>
            <td><code>Uuid | string | ArrayBuffer | Uint8Array</code></td>
            <td>Value to create UUID from.</td>
        </tr>
    </tbody>
</table>

#### Examples

```ts
// Parse from string
const uuid = new Uuid('550e8400-e29b-41d4-a716-446655440000');

// From binary
const bytes = new Uint8Array([/* 16 bytes */]);
const uuid = new Uuid(bytes);

// Clone existing
const copy = new Uuid(uuid);
```

## Static methods

### `Uuid.v4()` {#v4}

Generate a random UUID v4.

```ts title="Syntax"
Uuid.v4()
```

#### Returns
`Uuid` - Random UUID v4

#### Example

```ts
const randomId = Uuid.v4();
console.log(randomId.toString());
// '550e8400-e29b-41d4-a716-446655440000'

// Use as record ID
const userId = new RecordId('users', Uuid.v4());
await db.create(userId).content(userData);
```

---

### `Uuid.v7()` {#v7}

Generate a time-ordered UUID v7.

```ts title="Syntax"
Uuid.v7()
```

#### Returns
`Uuid` - Time-ordered UUID v7

> [!NOTE: Tip]
> UUID v7 includes a timestamp component, making it sortable by creation time. This is useful for time-series data and maintaining chronological order.

#### Example

```ts
const timeOrderedId = Uuid.v7();

// Sequential v7 UUIDs are sortable
const id1 = Uuid.v7();
// ... time passes ...
const id2 = Uuid.v7();

// id1 < id2 (lexicographically)
```

## Instance methods

### `.toString()` {#tostring}

Convert to string representation.

```ts title="Syntax"
uuid.toString()
```

#### Returns
`string` - UUID string in standard format (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`)

#### Example

```ts
const uuid = Uuid.v4();
console.log(uuid.toString());
// '550e8400-e29b-41d4-a716-446655440000'
```

---

### `.toJSON()` {#tojson}

Serialise for JSON.

```ts title="Syntax"
uuid.toJSON()
```

#### Returns
`string` - UUID string

---

### `.toUint8Array()` {#touint8array}

Get the binary representation as Uint8Array.

```ts title="Syntax"
uuid.toUint8Array()
```

#### Returns
`Uint8Array` - 16-byte binary representation

#### Example

```ts
const uuid = Uuid.v4();
const bytes = uuid.toUint8Array();
console.log(bytes.length); // 16
```

---

### `.toBuffer()` {#tobuffer}

Get the binary representation as ArrayBuffer.

```ts title="Syntax"
uuid.toBuffer()
```

#### Returns
`ArrayBufferLike` - Binary representation

---

### `.equals(other)` {#equals}

Check if two UUIDs are equal.

```ts title="Syntax"
uuid.equals(other)
```

#### Returns
`boolean` - True if equal

## Complete examples

### Session management

```ts
import { Surreal, Uuid, RecordId, DateTime, Duration, Table } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Create session with UUID
const sessionId = Uuid.v4();
const session = await db.create(new RecordId('sessions', sessionId))
    .content({
        user: userId,
        created_at: DateTime.now(),
        expires_at: DateTime.now().plus(new Duration('24h')),
        ip_address: '192.168.1.1'
    });

console.log('Session ID:', sessionId.toString());
```

### Time-ordered records

```ts
// Use v7 for time-series data
const events = [];

for (let i = 0; i < 100; i++) {
    const eventId = Uuid.v7();
    
    await db.create(new RecordId('events', eventId)).content({
        type: 'user_action',
        timestamp: DateTime.now(),
        data: { action: 'click', target: 'button' }
    });
    
    events.push(eventId);
}

// Events are naturally sorted by creation time
```

### Unique identifiers

```ts
// Generate unique IDs for various purposes
const requestId = Uuid.v4();
const correlationId = Uuid.v4();
const traceId = Uuid.v7(); // Time-ordered for distributed tracing

await db.create(new Table('requests')).content({
    id: requestId,
    correlation_id: correlationId,
    trace_id: traceId,
    timestamp: DateTime.now()
});
```

### API keys

```ts
// Generate API keys
function generateApiKey(): string {
    return `api_${Uuid.v4().toString().replace(/-/g, '')}`;
}

const apiKey = generateApiKey();
console.log(apiKey); // 'api_550e8400e29b41d4a716446655440000'

await db.create(new Table('api_keys')).content({
    key: apiKey,
    user: userId,
    created_at: DateTime.now()
});
```

### Distributed system IDs

```ts
// Use UUID v7 for distributed systems (sortable)
class IdGenerator {
    static generateOrderId(): Uuid {
        return Uuid.v7(); // Time-ordered
    }
    
    static generateTransactionId(): Uuid {
        return Uuid.v7(); // Time-ordered
    }
    
    static generateRandomId(): Uuid {
        return Uuid.v4(); // Random
    }
}

const orderId = IdGenerator.generateOrderId();
const txnId = IdGenerator.generateTransactionId();
```

### File upload tracking

```ts
// Track file uploads with UUIDs
async function uploadFile(file: File): Promise<string> {
    const uploadId = Uuid.v4();
    
    await db.create(new RecordId('uploads', uploadId)).content({
        filename: file.name,
        size: file.size,
        mime_type: file.type,
        uploaded_at: DateTime.now(),
        status: 'processing'
    });
    
    return uploadId.toString();
}
```

### Parsing and validation

```ts
// Parse UUID from user input
function validateUuid(input: string): Uuid | null {
    try {
        return new Uuid(input);
    } catch (error) {
        console.error('Invalid UUID format');
        return null;
    }
}

const userInput = '550e8400-e29b-41d4-a716-446655440000';
const uuid = validateUuid(userInput);

if (uuid) {
    const record = await db.select(new RecordId('items', uuid));
}
```

### Batch ID generation

```ts
// Generate multiple UUIDs
function generateBatchIds(count: number, useV7 = false): Uuid[] {
    const ids: Uuid[] = [];
    
    for (let i = 0; i < count; i++) {
        ids.push(useV7 ? Uuid.v7() : Uuid.v4());
    }
    
    return ids;
}

const randomIds = generateBatchIds(100); // 100 random UUIDs
const sortableIds = generateBatchIds(100, true); // 100 time-ordered UUIDs
```

## UUID v4 vs UUID v7

### UUID v4 (random)

- **Pros:** Truly random, no predictability
- **Cons:** Not sortable, no time information
- **Use when:** You need unpredictable, secure IDs

```ts
const randomId = Uuid.v4();
```

### UUID v7 (time-ordered)

- **Pros:** Sortable, includes timestamp, better for database indexes
- **Cons:** Slightly predictable (timestamp component)
- **Use when:** You need sortable IDs or time-series data

```ts
const timeOrderedId = Uuid.v7();
```

## Best practices

### 1. Choose the right version

```ts
// Good: v7 for time-series and events
const eventId = Uuid.v7();

// Good: v4 for security tokens
const token = Uuid.v4();
```

### 2. Use with RecordId

```ts
// Good: UUID as record ID
const userId = new RecordId('users', Uuid.v7());

// Good: Provides uniqueness and sortability
await db.create(userId).content(userData);
```

### 3. Validate user input

```ts
// Good: Validate before use
try {
    const uuid = new Uuid(userInput);
    await processUuid(uuid);
} catch (error) {
    return { error: 'Invalid UUID format' };
}
```

### 4. Store as UUID type

```ts
// Good: Store as UUID
await db.create(table).content({
    session_id: Uuid.v4()
});

// Avoid: Store as string
await db.create(table).content({
    session_id: Uuid.v4().toString()
});
```

## See also

- [RecordId](/docs/reference/javascript/api/values/record-id.md) - Record identifiers
- [Data types overview](/docs/reference/javascript/api/values/) - All custom data types
- [Query builders](/docs/reference/javascript/api/queries/) - Using Uuid in queries
- [SurrealQL UUID](/docs/reference/query-language/language-primitives/data-types/uuids.md) - Database UUID type

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/authentication

# Authentication

SurrealDB supports a number of methods for authenticating users and securing the database.

SurrealDB supports multiple levels of authentication, from [system users](/docs/learn/security/authentication/users.md#system-users) to fine-grained [record-level access](/docs/learn/security/authentication/users.md#record-users). The JavaScript SDK provides methods for signing up and signing in users, managing tokens, and automatically restoring sessions on reconnect.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#signin"> <code> db.signin(auth) </code></a></td>
            <td scope="row" data-label="Description">Signs in as a root, namespace, database, or record user</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#signup"> <code> db.signup(auth) </code></a></td>
			<td scope="row" data-label="Description">Signs up a new record user using an access method</td>
		</tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#authenticate"> <code> db.authenticate(token) </code></a></td>
            <td scope="row" data-label="Description">Authenticates the session with an existing token or token pair</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#invalidate"> <code> db.invalidate() </code></a></td>
            <td scope="row" data-label="Description">Invalidates the current authentication, signing the user out</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#subscribe"> <code> db.subscribe("auth", callback) </code></a></td>
            <td scope="row" data-label="Description">Subscribes to authentication state changes</td>
        </tr>
	</tbody>
</table>

## Signing in users

The `.signin()` method authenticates an existing user. SurrealDB supports multiple authentication levels, and the properties you provide determine which level is used.

**Root user**

Authenticate as a root user with full access to all namespaces and databases.

```ts
const tokens = await db.signin({
	username: 'root',
	password: 'secret',
});
```

**Namespace user**

Authenticate as a [namespace user](/docs/reference/query-language/statements/define/user.md) with access to all databases in the specified namespace.

```ts
const tokens = await db.signin({
	namespace: 'surrealdb',
	username: 'tobie',
	password: 'secret',
});
```

**Database user**

Authenticate as a [database user](/docs/reference/query-language/statements/define/user.md) with access scoped to a specific database.

```ts
const tokens = await db.signin({
	namespace: 'surrealdb',
	database: 'docs',
	username: 'tobie',
	password: 'secret',
});
```

**Record access**

Authenticate as a record user through a defined [record access method](/docs/reference/query-language/statements/define/access/record.md). Pass any required variables under the `variables` key.

```ts
const tokens = await db.signin({
	namespace: 'surrealdb',
	database: 'docs',
	access: 'account',
	variables: {
		email: 'info@surrealdb.com',
		pass: '123456',
	},
});
```

On success, the method returns a [`Tokens`](/docs/reference/javascript/api/types/#tokens) object containing the access token and an optional refresh token. The session is automatically authenticated after signing in.

## Signing up users

The `.signup()` method creates a new record user through a defined [record access method](/docs/reference/query-language/statements/define/access/record.md). You must specify the namespace, database, and access method, along with any variables expected by the access definition.

```ts
const tokens = await db.signup({
    namespace: 'surrealdb',
    database: 'docs',
    access: 'account',
    variables: {
        email: 'info@surrealdb.com',
        pass: '123456',
    },
});
```

Much like the `.signin()` method, the `.signup()` method returns a [`Tokens`](/docs/reference/javascript/api/types/#tokens) object containing the access token and an optional refresh token. The session is automatically authenticated after signing up.

## Providing credentials on connect

Rather than calling `.signin()` separately, you can pass authentication credentials directly to the `.connect()` method using the `authentication` option. This is the preferred approach for system users because it allows the SDK to automatically re-authenticate when the connection drops and reconnects.

```ts
await db.connect('ws://127.0.0.1:8000', {
    namespace: 'surrealdb',
    database: 'docs',
    authentication: {
        username: 'root',
        password: 'secret',
    },
});
```

A user defined on a namespace or a database signs in the same way, with the namespace and database it is defined on added to the credentials. The following connection signs in as `clerk`, a user defined with `DEFINE USER clerk ON DATABASE`:

```ts
await db.connect('ws://127.0.0.1:8000', {
    namespace: 'shop',
    database: 'orders',
    authentication: {
        namespace: 'shop',
        database: 'orders',
        username: 'clerk',
        password: 'hunter123',
    },
});
```

The `namespace` and `database` inside `authentication` say where the user is defined, and the ones outside it select what the connection works in, so the two pairs are usually the same. Over an HTTP connection, the SDK signs in once and sends the token it receives with each request.

The `authentication` option also accepts an async function, allowing you to retrieve credentials dynamically.

```ts
await db.connect('ws://127.0.0.1:8000', {
    namespace: 'surrealdb',
    database: 'docs',
    authentication: async () => ({
        username: await getUsername(),
        password: await getPassword(),
    }),
});
```

See the full list of connection options in the [`ConnectOptions`](/docs/reference/javascript/api/types/#connectoptions) type reference.

> [!NOTE]
> When you call `.signup()` or `.signin()` manually, the `authentication` property passed to `.connect()` is no longer used for automatic re-authentication. You become responsible for handling token expiry by listening to the [`auth`](/docs/reference/javascript/api/core/surreal-session.md#event-auth) event.

## Authenticating with an existing token

If you already have an access token (for example, stored from a previous session), you can authenticate using the `.authenticate()` method instead of signing in again. This is useful for restoring a user's session without re-entering credentials.

```ts
await db.authenticate(accessToken);
```

When you have a refresh token available, you can pass both tokens as an object. The SDK will exchange the refresh token for a new token pair.

```ts
const newTokens = await db.authenticate({
    access: oldAccessToken,
    refresh: refreshToken,
});
```

A token does not belong to the connection that created it. A server can sign a user in, send the access token to the browser, and the browser can authenticate its own connection with it. The token names the namespace, the database and the access method, so the browser's connection uses those even when it was opened without them:

```ts title="On the server"
const tokens = await db.signin({
    namespace: 'main',
    database: 'main',
    access: 'user',
    variables: { email, pass },
});

// Send tokens.access to the browser, for example in the session
```

```ts title="In the browser"
await db.connect('wss://example.com/rpc');
await db.authenticate(accessToken);

// Queries now run as the signed-in record user
```

The browser has to connect to the same database, because the database holds the key that the token was signed with. The token stops working when the `DURATION FOR TOKEN` of the access method runs out.

## Signing out

The `.invalidate()` method signs the current user out by clearing the session's authentication state. After calling this method, any subsequent queries will run without authentication.

```ts
await db.invalidate();
```

## Accessing authentication state

The SDK exposes the current authentication tokens through the `.accessToken` property on any [`Surreal`](/docs/reference/javascript/api/core/surreal.md) or [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md) instance. This is useful for checking whether the current session is authenticated or for forwarding tokens to other services.

```ts
if (db.accessToken) {
    console.log('Session is authenticated');
}
```

## Listening to authentication events

The SDK emits an `auth` event whenever the authentication state changes, including on sign in, sign up, token refresh, or invalidation. You can subscribe to this event using the `.subscribe()` method.

```ts
db.subscribe('auth', (tokens) => {
    if (tokens) {
        console.log('Authenticated:', tokens.access);
    } else {
        console.log('Signed out');
    }
});
```

This is particularly useful for record access, where you can subscribe to the `auth` event to automatically update UI or other components when the authentication state changes.

## Using isolated sessions

You can create multiple isolated sessions within a single connection, each with their own namespace, database, variables, and authentication state. This is useful when different parts of your application need to operate under different credentials or contexts.

```ts
const session = await db.newSession();

await session.signin({
    namespace: 'surrealdb',
    database: 'docs',
    access: 'account',
    variables: {
        email: 'info@surrealdb.com',
        pass: '123456',
    },
});

const users = await session.select('users');

await session.closeSession();
```

## Learn more

- [SurrealSession API reference](/docs/reference/javascript/api/core/surreal-session.md) for authentication method signatures
- [Authentication types reference](/docs/reference/javascript/api/types/#anyauth) for all credential type definitions
- [Authentication in SurrealDB](/docs/learn/security/authentication/users.md) for how authentication works at the database level
- [Security best practices](/docs/learn/security/best-practices/security-best-practices.md#authentication) for securing your application
- [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md) for defining access methods with SurrealQL
- [DEFINE USER](/docs/reference/query-language/statements/define/user.md) for creating system users
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for connection setup and reconnection behaviour
- [Multiple sessions](/docs/reference/javascript/concepts/multiple-sessions.md) for isolated session management

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/bound-queries

# Bound queries

The JavaScript SDK provides bound queries and template tags for safely composing parameterised SurrealQL queries.

When composing dynamic queries, it is important to avoid string interpolation to prevent injection vulnerabilities. The JavaScript SDK provides bound queries and the `surql` template tag to safely parameterise values, along with an expressions API for composing dynamic conditions.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Utility</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/surql.md"> <code> surql </code></a></td>
			<td scope="row" data-label="Description">Tagged template literal for composing parameterised queries</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/bound-query.md"> <code> BoundQuery </code></a></td>
			<td scope="row" data-label="Description">Class for manually building parameterised queries</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/expr.md"> <code> expr() </code></a></td>
			<td scope="row" data-label="Description">Composes type-safe expressions for use in queries</td>
		</tr>
	</tbody>
</table>

## Using the surql template tag

The `surql` tagged template literal is the recommended way to compose parameterised queries. Interpolated values are automatically bound as parameters, preventing injection and preserving type safety.

```ts
import { surql } from 'surrealdb';

const name = 'John';
const minAge = 18;

const query = surql`SELECT * FROM users WHERE name = ${name} AND age > ${minAge}`;
const [users] = await db.query(query);
```

The `surrealql` export is an alias for `surql` if you prefer the longer name.

```ts
import { surrealql } from 'surrealdb';

const query = surrealql`CREATE person CONTENT ${{ name: 'Tobie' }}`;
```

> [!NOTE]
> The SurrealQL [VSCode extension](https://marketplace.visualstudio.com/items?itemName=surrealdb.surrealql) provides syntax highlighting for surql template literals.

## Building queries with boundquery

The `BoundQuery` class provides manual control over query composition. You can construct a query with initial bindings, and incrementally append fragments with additional parameters.

```ts
import { BoundQuery } from 'surrealdb';

const query = new BoundQuery(
    'SELECT * FROM users WHERE status = $status',
    { status: 'active' },
);

await db.query(query);
```

### Appending query fragments

Use the `.append()` method to conditionally add SurrealQL fragments. The method uses the same tagged template literal syntax as `surql`, so interpolated values are automatically bound.

```ts
const query = new BoundQuery('SELECT * FROM person');
const filterName = 'Alice';

if (filterName) {
    query.append(surql` WHERE name = ${filterName}`);
}

const [results] = await db.query(query);
```

## Composing expressions

The [expressions API](/docs/reference/javascript/api/utilities/expr.md) provides functions for building dynamic conditions in a type-safe way. Expressions integrate with both `surql` and query builder methods like `.where()`.

```ts
const checkActive = true;

await db.query(surql`SELECT * FROM users WHERE ${eq('active', checkActive)}`);
```

## Learn more

- [surql API reference](/docs/reference/javascript/api/utilities/surql.md) for template tag details
- [BoundQuery API reference](/docs/reference/javascript/api/utilities/bound-query.md) for manual query building
- [expr API reference](/docs/reference/javascript/api/utilities/expr.md) for the full expressions API
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) for running queries against the database

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/codecs

# Codecs

The SQON library provides codecs for serialising and deserialising SurrealDB value types over CBOR and JSON wire formats.

Record IDs, nanosecond datetimes, decimals, and durations do not survive a round trip through plain JSON or untagged CBOR unchanged. SQON (SurrealQL Object Notation) is the wire format SurrealDB uses for these types. The [`@surrealdb/sqon`](https://www.npmjs.com/package/@surrealdb/sqon) package ships the codecs that convert between JavaScript value instances and that format.

When you use the `surrealdb` SDK over WebSocket or HTTP, those codecs run automatically on every request and response. You can also import them on their own if you are building a custom client, middleware, or data pipeline and do not need the full driver.

## Available codecs

Two codecs are fully implemented today:

<table>
	<thead>
		<tr>
			<th scope="col">Codec</th>
			<th scope="col">Wire format</th>
			<th scope="col">When to use</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Codec"><code>CborCodec</code></td>
			<td scope="row" data-label="Wire format"><code>Uint8Array</code> (CBOR with SurrealDB tags)</td>
			<td scope="row" data-label="When to use">RPC transport to SurrealDB - WebSocket and HTTP engines encode every request and decode every response with CBOR</td>
		</tr>
		<tr>
			<td scope="row" data-label="Codec"><code>JsonCodec</code></td>
			<td scope="row" data-label="Wire format">Plain object tree (SQON JSON)</td>
			<td scope="row" data-label="When to use">JSON-safe interchange - logging, caching, REST APIs, or any environment where binary CBOR is impractical</td>
		</tr>
	</tbody>
</table>

Both codecs represent the same values. If you encode with one and decode with the other, types are preserved as long as you decode back into the matching value classes.

> [!NOTE]
> `FlatBufferCodec` is exported for forward compatibility but is not implemented in this version.

## Why you need a codec

Without one, SurrealDB-specific types get flattened or misread on the way in or out:

- A `RecordId` can turn into a plain string and lose its table
- A `Decimal` can be rounded by JavaScript's `number` type
- `none` can be confused with a missing JSON field or with `null`
- Datetimes can drop from nanoseconds to milliseconds

That is why query results come back as `DateTime`, `RecordId`, and the other value classes: the CBOR codec decodes every inbound RPC message before your code sees it.

## Using codecs with the SDK

A new `Surreal` instance registers default codecs. WebSocket and HTTP engines use `CborCodec` for RPC traffic, so you normally never call a codec yourself.

Pass `codecOptions` in the driver options to change decoding behaviour:

```ts
import { Surreal } from 'surrealdb';

const db = new Surreal({
    codecOptions: {
        useNativeDates: true,
    },
});
```

With `useNativeDates` set, datetimes decode as native `Date` objects instead of `DateTime`. You lose nanosecond precision, which is the trade-off if the rest of your code expects `Date`.

You can replace the default codec factories when you need custom encode or decode logic:

```ts
import { CborCodec, JsonCodec, type CodecOptions } from '@surrealdb/sqon';
import { Surreal } from 'surrealdb';

const db = new Surreal({
    codecOptions: {
        valueDecodeVisitor: (value) => {
            // Transform decoded values before they reach your application
            return value;
        },
    },
    codecs: {
        cbor: (options: CodecOptions) => new CborCodec(options),
        json: (options: CodecOptions) => new JsonCodec(options),
    },
});
```

## Using codecs standalone

Install `@surrealdb/sqon` when you only need serialisation and not the database client:

```sh
bun add @surrealdb/sqon
```

### CBOR codec

`CborCodec` outputs compact binary. Use it when you speak SurrealDB's RPC protocol or want a small, type-safe binary payload.

```ts
import { CborCodec, RecordId, Decimal, Duration } from '@surrealdb/sqon';

const codec = new CborCodec({
	// optional options
});

const payload = {
    id: new RecordId('order', 42),
    total: new Decimal('99.95'),
    sla: Duration.parse('24h'),
};

const bytes = codec.encode(payload);
const restored = codec.decode<typeof payload>(bytes);

console.log(restored.id instanceof RecordId); // true
console.log(restored.total instanceof Decimal); // true
```

The CBOR layout uses SurrealDB's tagged values. See the [CBOR protocol reference](/docs/reference/rest-api/cbor-protocol.md) for the tag list.

### JSON codec

`JsonCodec` builds a JSON-safe object tree in SQON JSON notation. Typed values sit inside wrapper objects such as `$recordId`, `$datetime`, and `$decimal`:

```ts
import { JsonCodec, RecordId, DateTime } from '@surrealdb/sqon';

const codec = new JsonCodec({
	// optional options
});

const value = {
    created: DateTime.parse('2024-01-15T12:00:00.123456789Z'),
    author: new RecordId('user', 'tobie'),
};

const sqonJson = codec.encode(value);
```

```json
{
    "created": { "$datetime": "2024-01-15T12:00:00.123456789Z" },
    "author": { "$recordId": { "tb": "user", "id": "tobie" } }
}
```

Decode the structure back to value instances:

```ts
const restored = codec.decode<typeof value>(sqonJson);
console.log(restored.author instanceof RecordId); // true
```

`JsonCodec` fits logging, browser storage, and anything that only accepts JSON/text such as LLMs. `CborCodec` fits talking to SurrealDB directly and cases where size and binary safety matter.

## Choosing a codec

<table>
	<thead>
		<tr>
			<th scope="col">Scenario</th>
			<th scope="col">Recommended codec</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>WebSocket or HTTP RPC to SurrealDB</td>
			<td><code>CborCodec</code> (used automatically by the SDK)</td>
		</tr>
		<tr>
			<td>Storing query results in a JSON document store</td>
			<td><code>JsonCodec</code> or <code>jsonify()</code> for string representations</td>
		</tr>
		<tr>
			<td>Logging or debugging typed values</td>
			<td><code>JsonCodec</code> or <code>jsonify()</code></td>
		</tr>
		<tr>
			<td>Custom RPC client implementation</td>
			<td><code>CborCodec</code></td>
		</tr>
		<tr>
			<td>Browser storage (<code>localStorage</code>, IndexedDB as JSON)</td>
			<td><code>JsonCodec</code></td>
		</tr>
	</tbody>
</table>

For a plain string form (good for display or simple serialisation), [`jsonify()`](/docs/reference/javascript/concepts/utilities.md#jsonifying-query-results) converts value instances to SurrealQL strings without the SQON JSON wrappers.

## Codec options

Both codecs accept a shared `CodecOptions` object:

<table>
	<thead>
		<tr>
			<th scope="col">Option</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td><code>useNativeDates</code></td>
			<td>Decode datetimes as native <code>Date</code> instead of <code>DateTime</code> (loses nanosecond precision)</td>
		</tr>
		<tr>
			<td><code>valueEncodeVisitor</code></td>
			<td>Custom function applied to each value before encoding</td>
		</tr>
		<tr>
			<td><code>valueDecodeVisitor</code></td>
			<td>Custom function applied to each value after decoding</td>
		</tr>
	</tbody>
</table>

Use `valueEncodeVisitor` and `valueDecodeVisitor` to map values to your own types, or to strip fields before encoding.

## Learn more

- [CBOR protocol reference](/docs/reference/rest-api/cbor-protocol.md) for the full CBOR tag specification
- [Value types](/docs/reference/javascript/concepts/value-types.md) for the value classes codecs operate on
- [Utilities](/docs/reference/javascript/concepts/utilities.md) for `jsonify()` and other SQON helpers
- [`@surrealdb/sqon` on npm](https://www.npmjs.com/package/@surrealdb/sqon) for standalone installation

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/connecting-to-surrealdb

# Connecting to SurrealDB

Connecting to SurrealDB from JavaScript. Remote and embedded connections with the official SDK.

When creating a new connection to a SurrealDB instance, you can choose to connect to a local or remote endpoint, specify a namespace and database pair to use, authenticate with an existing token, authenticate using a pair of credentials, or use advanced custom logic to prepare the connection to the database.

First, you need to initialise a new instance of the Surreal class and connect it to a database endpoint using the `.connect()` method. Then you can specify the connection details such as the URL, namespace, and database.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal.md#connect"> <code> db.connect(url, options) </code></a></td>
			<td scope="row" data-label="Description">Connects to a local or remote database endpoint</td>
		</tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal.md#close"> <code> db.close() </code></a></td>
            <td scope="row" data-label="Description">Closes the persistent connection to the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal.md#ready"> <code> db.ready </code></a></td>
			<td scope="row" data-label="Description">Waits for the connection to the database to succeed</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#use"> <code> db.use(namespace, database)</code></a></td>
			<td scope="row" data-label="Description">Switch to a specific namespace and database</td>
		</tr>
	</tbody>
</table>

## Opening a connection

Before you can execute any queries, you need to open a connection to a SurrealDB instance. This is done using the `.connect()` method.
This method accepts a connection string and a set of options, including namespace, database, and authentication details.

### Connection string

The connection string represents a URI pointing to a SurrealDB instance. Supported connection protocols include:

- **WebSocket** (`ws://`) for long lived connections (e.g. backend or frontend applications)
- **HTTP** (`http://`) for short lived stateless connections (e.g. server-side rendering applications)
- **Embedded** protocols using the [WebAssembly engine](/docs/reference/javascript/engines/wasm.md) or [Node.js engine](/docs/reference/javascript/engines/node.md)

**Local endpoint**

```ts
// Over WebSocket
await db.connect('ws://127.0.0.1:8000');

// Over HTTP
await db.connect('http://127.0.0.1:8000');
```

**Remote endpoint**

```ts
// Over WebSocket
await db.connect('wss://cloud.surrealdb.com');

// Over HTTP
await db.connect('https://cloud.surrealdb.com');
```

**Embedded endpoint**

```ts
// In-memory database
await db.connect('mem://');

// IndexedDB database (browser)
await db.connect('indxdb://localhost:8000');

// File-system database (backend)
await db.connect('rocksdb://localhost:8000');
```

### Connection options

The optional connection options allow you to further configure the connection to the database, including namespace and database, reconnect logic, and authentication details.

#### Namespace and database

You can directly specify the [namespace](/docs/reference/query-language/statements/define/namespace.md) and [database](/docs/reference/query-language/statements/define/database.md) to use using the `namespace` and `database` options. If you do not specify these options, the default namespace and database will be used.
Once the connection is established, you can switch the active namespace and database with the `.use()` method.

#### Authentication details

When connecting as a [system user](/docs/learn/security/authentication/users.md#system-users) or [token](/docs/learn/security/authentication/users.md#token), you can directly pass your credentials to the `authentication` option.
While you can also use the dedicated `.signin()` method to authenticate, passing the authentication details to the `.connect()` method is the preferred way and allows for automatic reconnecting.

#### Reconnection behaviour

You can configure the reconnection behaviour using the `reconnect` option. The SDK features a built-in reconnection mechanism for WebSocket connections that automatically reconnects to the database if the connection is lost.
Additionally, you can configure the behaviour with exponential backoff and jitter to prevent overwhelming the database with reconnection attempts.

| Option                | Description                                              |
|-----------------------|----------------------------------------------------------|
| `enabled`             | Enable automatic reconnection                            |
| `attempts`            | Maximum reconnection attempts (`-1` for unlimited)       |
| `retryDelay`          | Initial delay before reconnecting (ms)                   |
| `retryDelayMax`       | Maximum delay between attempts (ms)                      |
| `retryDelayMultiplier`| Multiply delay after each failed attempt                 |
| `retryDelayJitter`    | Random offset percentage for delays                      |

#### Retrying on write conflict

Under concurrent write load, a query can fail with a read/write conflict when another transaction touched the same data. You can configure the SDK to replay the conflicting work automatically with exponential backoff, using the `retry` option. It shares the same shape as `reconnect`, and is off by default since retrying a non-atomic multi-statement query could apply some statements more than once.

```ts
await db.connect('ws://localhost:8000', {
    namespace: 'my_namespace',
    database: 'my_database',
    retry: { enabled: true, attempts: 5, retryDelay: 100 }
});
```

This sets the connection-wide default. It can be overridden per call with `.retry()` on a [`query()`](/docs/reference/javascript/concepts/executing-queries.md) or on [mutation methods](/docs/reference/javascript/concepts/executing-queries.md) like `.create()` and `.delete()` - including when called on a [transaction](/docs/reference/javascript/concepts/transactions.md), since it exposes the same query methods.

```ts
const [n] = await db
    .query<[number]>('UPDATE counter:c SET n += 1 RETURN n')
    .retry({ attempts: 3 })
    .collect();
```

### Waiting for a connection

You can wait for the connection to the database to succeed by awaiting the `.connect()` method. If the connection fails for any reason, the promise will reject.
If you want to await without opening a connection, you can make use of the `.ready()` method instead.

```ts
// Open a new connection and wait for it to succeed
await db.connect('ws://127.0.0.1:8000');

// Wait for the connection to succeed without opening a new connection
await db.ready();
```

### Effect of connection protocol on token & session duration

The connection protocol you choose affects how authentication tokens and sessions work:

- **WebSocket** connections open a single long-lived stateful connection. After the initial authentication, the session duration applies. If no session duration is specified, it defaults to `NONE`, which means that the session never expires.
- **HTTP** connections are short-lived and stateless. Each request is authenticated individually with the token, which creates a short-lived session, so the token duration applies. The token duration defaults to 1 hour.

You can extend the session duration of a token or a session by setting the `DURATION` clause when creating a new access method with the [`DEFINE ACCESS METHOD`](/docs/reference/query-language/statements/define/access.md) statement, or when defining a new user with the [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md) statement.
Learn more about token and session duration in our [security best practices](/docs/learn/security/best-practices/security-best-practices.md#expiration) documentation.

## Reusing one connection in a server

Opening a connection, selecting a namespace and database, and signing in each take a network round trip, and together they can cost far more than the query that follows. In a web server or API, create one `Surreal` instance when the process starts and reuse it for every request, rather than connecting inside a request handler.

```ts title="db.ts"
import { Surreal } from 'surrealdb';

export const db = new Surreal();

await db.connect('wss://example.surrealdb.cloud', {
    namespace: 'my_namespace',
    database: 'my_database',
    authentication: {
        username: 'root',
        password: 'secret',
    },
});
```

```ts title="A request handler"
import { db } from './db';

export async function listProjects() {
    return db.select('project');
}
```

Passing the namespace, database and credentials to `.connect()` also lets the SDK restore them when it reconnects. If each request has to run as a different user, keep the one connection and give each user a [session](/docs/reference/javascript/concepts/multiple-sessions.md) with `db.newSession()`, rather than opening a new connection per user.

## Selecting a namespace and database

While an initial namespace and database can be specified directly in the `.connect()` method, you can also switch to a specific namespace and database using the `.use()` method. This is particularly useful if you want to switch to a different setup after connecting.
You can also stay in the same namespace but switch to a different database.

```ts
await db.use({
	namespace: 'surrealdb',
	database: 'docs'
});
```

The SDK will emit a `using` event whenever the namespace or database is selected, including during the initial connection. This allows you to subscribe to namespace and database changes and react to them accordingly.

```ts
db.subscribe('using', ({ namespace, database }) => {
	console.log('Now using:', namespace, '/', database);
});
```

## Connection status

The status of the connection is available through the `.status` property. This allows you to check the current connection state and react to changes in the connection status. The possible values are:

- **disconnected** when the SDK is waiting for a connection to be opened
- **connecting** when a connection is currently being opened
- **connected** when the SDK is ready to communicate with the database and execute queries
- **reconnecting** when the connection dropped and the SDK is attempting to reconnect

Additionally the SDK exposes events for each of these states, allowing you to subscribe to connection state changes.

```ts
db.subscribe('connected', () => {
    console.log('Connected to the database');
});
```

## Closing a connection

The `.close()` method closes the persistent connection to the database. You should always call this method when you are done with the connection to free up resources.
Since this method is asynchronous, we highly recommend awaiting it to ensure that the connection is closed properly before proceeding.

```ts
await db.close();
```

## Testing for features

The SDK provides a built in feature testing mechanism to check if a specific feature is supported by the current connection. This is particularly useful to check if a feature is supported before using it, and to avoid errors or unexpected behaviour.

```ts
import { Features } from "surrealdb";

if (db.isFeatureSupported(Features.LiveQueries)) {
	// Execute a live query...
}
```

A complete [list of supported features](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/utils/features.ts) can be found in the source code.

## Learn more

- [Surreal API reference](/docs/reference/javascript/api/core/surreal.md) for the complete connection interface
- [ConnectOptions type reference](/docs/reference/javascript/api/types/#connectoptions) for all connection options
- [RetryOptions type reference](/docs/reference/javascript/api/types/#retryoptions) for retry-on-conflict configuration
- [Authentication](/docs/reference/javascript/concepts/authentication.md) for signing in and managing credentials
- [WebAssembly engine](/docs/reference/javascript/engines/wasm.md) for embedded browser databases
- [Node.js engine](/docs/reference/javascript/engines/node.md) for embedded server-side databases
- [Error handling](/docs/reference/javascript/concepts/error-handling.md) for connection and reconnection errors
- [Security best practices](/docs/learn/security/best-practices/security-best-practices.md) for production deployments

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/diagnostics

# Diagnostics

The JavaScript SDK provides a diagnostics API for intercepting and inspecting protocol-level communication with SurrealDB.

The Diagnostics API allows you to wrap engines and intercept protocol-level communication between the SDK and SurrealDB. This is useful for debugging queries, analysing SDK behaviour, measuring operation timings, and building custom logging or monitoring tools.

Since the API is implemented as an engine wrapper, it adds no overhead to the SDK unless you explicitly enable it.

> [!WARNING]
> The diagnostics events are considered unstable and may change between SDK versions. Avoid relying on this API in production environments as it may negatively affect performance.

## Wrapping engines with diagnostics

Use the `applyDiagnostics` function to wrap your engines with a diagnostic listener. The listener function is called for every protocol-level event.

```ts
import { Surreal, createRemoteEngines, applyDiagnostics } from 'surrealdb';

const db = new Surreal({
    engines: applyDiagnostics(createRemoteEngines(), (event) => {
        console.log(event);
    }),
});

await db.connect('ws://localhost:8000');
```

## Understanding diagnostic events

Each diagnostic event is a plain object with at least three properties:

| Property | Type | Description |
|----------|------|-------------|
| `type` | `string` | The type of operation (e.g. `"open"`, `"query"`, `"signin"`) |
| `key` | `string` | A stable UUID that links related `before` and `after` phases |
| `phase` | `string` | When the event fires: `"before"`, `"progress"`, or `"after"` |

Events in the `after` phase include additional properties:

| Property | Type | Description |
|----------|------|-------------|
| `duration` | `string` | How long the operation took |
| `success` | `boolean` | Whether the operation succeeded |
| `result` | `unknown` | The result or error details |

## Example diagnostic output

The following is an example of the diagnostic events produced during a typical connection, authentication, and query sequence.

```json
{"type":"open","key":"1560a0e1-...","phase":"before"}
{"type":"open","key":"1560a0e1-...","phase":"after","success":true,"duration":"535us"}

{"type":"version","key":"5c9bda29-...","phase":"before"}
{"type":"version","key":"5c9bda29-...","phase":"after","success":true,"duration":"964us","result":{"version":"surrealdb-2.1.0"}}

{"type":"use","key":"4fba2fe5-...","phase":"before"}
{"type":"use","key":"4fba2fe5-...","phase":"after","success":true,"duration":"387us","result":{"requested":{"namespace":"main","database":"main"}}}

{"type":"signin","key":"d61ab7cf-...","phase":"before"}
{"type":"signin","key":"d61ab7cf-...","phase":"after","success":true,"duration":"14ms","result":{"variant":"system_user"}}

{"type":"query","key":"6763817d-...","phase":"before"}
{"type":"query","key":"6763817d-...","phase":"progress","result":{"query":"CREATE ONLY $bind__4 CONTENT $bind__5","params":{"bind__4":"person:1","bind__5":{"firstname":"John","lastname":"Doe"}}}}
{"type":"query","key":"6763817d-...","phase":"after","success":true,"duration":"1ms"}
```

## Use cases

### Debugging queries

Log all queries with their bound parameters and execution times to understand what the SDK sends to the database.

```ts
const db = new Surreal({
    engines: applyDiagnostics(createRemoteEngines(), (event) => {
        if (event.type === 'query' && event.phase === 'after') {
            console.log(`Query completed in ${event.duration}, success: ${event.success}`);
        }
    }),
});
```

### Performance monitoring

Collect timing data for all operations to identify slow queries or connection bottlenecks.

```ts
const timings: Record<string, string>[] = [];

const db = new Surreal({
    engines: applyDiagnostics(createRemoteEngines(), (event) => {
        if (event.phase === 'after') {
            timings.push({
                type: event.type,
                duration: event.duration,
                success: String(event.success),
            });
        }
    }),
});
```

### Custom logging

Forward diagnostic events to your application's logging infrastructure for centralised observability.

```ts
const db = new Surreal({
    engines: applyDiagnostics(createRemoteEngines(), (event) => {
        logger.debug('SurrealDB', {
            type: event.type,
            phase: event.phase,
            key: event.key,
            ...(event.phase === 'after' && {
                duration: event.duration,
                success: event.success,
            }),
        });
    }),
});
```

## Learn more

- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for engine configuration
- [Error handling](/docs/reference/javascript/concepts/error-handling.md) for handling SDK errors

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/embedded-engines

# Embedded engines

Run SurrealDB as an embedded database in the browser or on the server using the WebAssembly and Node.js engine plugins.

The JavaScript SDK supports running SurrealDB as an embedded database through two engine plugins. Choose the one that matches your environment:

| Engine | Package | Environment | Storage options |
|--------|---------|-------------|-----------------|
| WebAssembly | `@surrealdb/wasm` | Browsers | `mem://`, `indxdb://` |
| Node.js | `@surrealdb/node` | Node.js, Bun, Deno | `mem://`, `rocksdb://`, `surrealkv://` |

Both plugins work with ES modules (`import`), not CommonJS (`require`).

> [!NOTE]
> Neither engine runs on Android or iOS. Hermes has no WebAssembly runtime, and the Node.js engine is a native Node addon that React Native cannot load. Apps built with [Expo](/docs/reference/javascript/frameworks/expo.md) or [React Native](/docs/reference/javascript/frameworks/react-native.md) connect to a remote instance over `wss://` or `https://`.

## Engine version

Each plugin runs a SurrealDB engine that ships as its own package: `@surrealdb/wasm-native` for WebAssembly and `@surrealdb/node-native` for Node.js. From plugin version 3.0.4, the engine is a peer dependency, so you choose which SurrealDB release runs embedded in your app, independently of the plugin version. Install a specific release with `npm install --save @surrealdb/node-native@3.3.2`, or let your package manager pick the newest release in the supported range. The [Node.js](/docs/reference/javascript/engines/node.md#choosing-the-engine-version) and [WebAssembly](/docs/reference/javascript/engines/wasm.md#choosing-the-engine-version) engine pages describe pinning and how to check the loaded version.

## WebAssembly engine (browser)

The `@surrealdb/wasm` package runs SurrealDB inside a browser environment. It supports in-memory databases and persistent storage via IndexedDB, and can optionally run inside a Web Worker to keep the main thread responsive.

### Installation

```bash
npm install --save @surrealdb/wasm @surrealdb/wasm-native
```

### Registering the engine

```ts
import { Surreal, createRemoteEngines } from 'surrealdb';
import { createWasmEngines } from '@surrealdb/wasm';

const db = new Surreal({
    engines: {
        ...createRemoteEngines(),
        ...createWasmEngines(),
    },
});

await db.connect('mem://');
// or persist with IndexedDB:
await db.connect('indxdb://myapp');
```

### Running in a web worker

Offload database operations from the main thread to keep your interface responsive:

```ts
import { Surreal, createRemoteEngines } from 'surrealdb';
import { createWasmWorkerEngines } from '@surrealdb/wasm';
import WorkerAgent from '@surrealdb/wasm/worker?worker';

const db = new Surreal({
    engines: {
        ...createRemoteEngines(),
        ...createWasmWorkerEngines({
            createWorker: () => new WorkerAgent(),
        }),
    },
});

await db.connect('mem://');
```

### Bundler configuration

If you are using a bundler like Vite, you may need to exclude the WASM package from dependency optimisation and enable top-level await:

```js title="vite.config.js"
export default {
    optimizeDeps: {
        exclude: ['@surrealdb/wasm'],
        esbuildOptions: {
            target: 'esnext',
        },
    },
    esbuild: {
        supported: {
            'top-level-await': true,
        },
    },
};
```

## Node.js engine (server)

The `@surrealdb/node` package runs SurrealDB within Node.js, Bun, or Deno. It supports in-memory databases and persistent storage via RocksDB and SurrealKV.

### Installation

```bash
npm install --save @surrealdb/node @surrealdb/node-native
```

### Registering the engine

```ts
import { Surreal, createRemoteEngines } from 'surrealdb';
import { createNodeEngines } from '@surrealdb/node';

const db = new Surreal({
    engines: {
        ...createRemoteEngines(),
        ...createNodeEngines(),
    },
});

await db.connect('mem://');
// or persist with SurrealKV:
await db.connect('surrealkv://./data');
```

To enable [versioned storage](/docs/reference/query-language/statements/select.md#the-version-clause) for temporal queries, append `?versioned=true` to the connection string:

```ts
await db.connect('surrealkv://./data?versioned=true');
```

### Closing the connection

When using the Node.js engine, you must close the connection with `.close()` when you are done to ensure the database is properly shut down:

```ts
await db.close();
```

## Learn more

- [WebAssembly engine reference](/docs/reference/javascript/engines/wasm.md)
- [Node.js engine reference](/docs/reference/javascript/engines/node.md)
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for connection options and reconnection behaviour
- [`@surrealdb/wasm` on npm](https://npmjs.com/package/@surrealdb/wasm)
- [`@surrealdb/node` on npm](https://npmjs.com/package/@surrealdb/node)

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/error-handling

# Error handling

The JavaScript SDK provides specific error classes for handling different types of failures when interacting with SurrealDB.

The JavaScript SDK defines specific error classes for different failure scenarios. All SDK errors extend the base `SurrealError` class, making it easy to distinguish SDK errors from other JavaScript errors and to handle specific failure types with `instanceof` checks.

The error kinds, wire codes and structured shape behind these are documented once in [Errors](/docs/reference/rest-api/errors.md), which applies to every SDK and protocol.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Error class</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/javascript/api/errors.md#surrealerror"> <code> SurrealError </code></a></td>
			<td scope="row" data-label="Description">Base class for all SDK errors</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/javascript/api/errors.md#connectionunavailableerror"> <code> ConnectionUnavailableError </code></a></td>
			<td scope="row" data-label="Description">Thrown when operating without an active connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/javascript/api/errors.md#authenticationerror"> <code> AuthenticationError </code></a></td>
			<td scope="row" data-label="Description">Thrown when authentication fails</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/javascript/api/errors.md#responseerror"> <code> ResponseError </code></a></td>
			<td scope="row" data-label="Description">Thrown when a database query returns an error</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/javascript/api/errors.md#unsupportedversionerror"> <code> UnsupportedVersionError </code></a></td>
			<td scope="row" data-label="Description">Thrown when the SurrealDB version is incompatible</td>
		</tr>
	</tbody>
</table>

A complete list of error classes is available in the [Errors API reference](/docs/reference/javascript/api/errors.md).

## Where an error surfaces

A query travels through two layers, and a failure can come from either one.

The first is the request itself: a connection that is unavailable, a rejected sign-in, a query that will not parse. The second is the individual statements inside the query, which can fail while the request as a whole succeeds.

Awaiting a query collapses both into a rejection. It rejects on the first statement that fails, so a query whose earlier statements succeeded resolves to nothing at all: those results are lost along with the error.

Where the per-statement outcome matters, `.responses()` returns every statement instead of rejecting. Each entry carries a `success` flag, and a failing one carries an `error` with its `kind`.

```ts
import { Surreal, createRemoteEngines, ThrownError } from 'surrealdb';

const db = new Surreal({ engines: { ...createRemoteEngines() } });
await db.connect('http://localhost:8000');
await db.signin({ username: 'root', password: 'secret' });
await db.use({ namespace: 'test', database: 'test' });

// Awaiting the query rejects on the first statement that fails, so the
// result of the statement that succeeded is not returned.
try {
    await db.query("RETURN 1; THROW 'second'");
} catch (e) {
    if (e instanceof ThrownError) {
        console.log(`${e.kind}: ${e.message}`);
    }
}

// .responses() reports every statement instead of rejecting.
const responses = await db.query("RETURN 1; THROW 'second'").responses();
for (const [index, statement] of responses.entries()) {
    console.log(index, statement.success, statement.success ? statement.result : statement.error.kind);
}
```

```ts title="Output"
Thrown: An error occurred: second
0 true 1
1 false Thrown
```

## Error kinds

Every server error carries a `kind`, and the SDK throws a dedicated class for each of the kinds below. The meaning of each kind is described in [Errors](/docs/reference/rest-api/errors.md#error-kinds). Match on the kind or the class rather than on the message text, which is free to change between releases.

| Kind | Error class |
| --- | --- |
| `Validation` | `ValidationError` |
| `Configuration` | `ConfigurationError` |
| `Query` | `QueryError` |
| `Serialization` | `SerializationError` |
| `NotAllowed` | `NotAllowedError` |
| `NotFound` | `NotFoundError` |
| `AlreadyExists` | `AlreadyExistsError` |
| `Thrown` | `ThrownError` |
| `Internal` | `InternalError` |

Any kind without a dedicated class, including one added by a newer server, arrives as the base `ServerError` with its `kind` intact. Catching `ServerError` therefore stays correct as the server grows new kinds.

## Catching SDK errors

All errors thrown by the SDK are instances of `SurrealError`. You can use `instanceof` checks to catch SDK errors broadly, or target specific error classes for fine-grained handling.

```ts
import { SurrealError, AuthenticationError, ConnectionUnavailableError } from 'surrealdb';

try {
    await db.signin({ username: 'user', password: 'pass' });
} catch (error) {
    if (error instanceof AuthenticationError) {
        console.error('Invalid credentials');
    } else if (error instanceof ConnectionUnavailableError) {
        console.error('Not connected to a database');
    } else if (error instanceof SurrealError) {
        console.error('SDK error:', error.message);
    }
}
```

## Handling connection errors

Connection errors occur when the SDK cannot establish or maintain a connection to the database. The most common are `ConnectionUnavailableError` (thrown when you attempt an operation without a connection) and `HttpConnectionError` (thrown when an HTTP request fails).

```ts
import { ConnectionUnavailableError, HttpConnectionError } from 'surrealdb';

try {
    await db.connect('ws://localhost:8000');
} catch (error) {
    if (error instanceof HttpConnectionError) {
        console.error(`HTTP ${error.status}: ${error.statusText}`);
    }
}
```

If you attempt to use an engine protocol that has not been registered, the SDK throws an `UnsupportedEngineError` with the name of the unsupported engine.

```ts
import { UnsupportedEngineError } from 'surrealdb';

try {
    await db.connect('mem://');
} catch (error) {
    if (error instanceof UnsupportedEngineError) {
        console.error(`Engine "${error.engine}" is not registered`);
    }
}
```

## Handling authentication errors

An `AuthenticationError` is thrown when a sign-in or sign-up attempt fails. A `MissingNamespaceDatabaseError` is thrown when you attempt an operation that requires a namespace and database without having selected one.

```ts
import { AuthenticationError, MissingNamespaceDatabaseError } from 'surrealdb';

try {
    await db.use({ namespace: 'main', database: 'main' });
    await db.signin({ username: 'admin', password: 'secret' });
} catch (error) {
    if (error instanceof MissingNamespaceDatabaseError) {
        console.error('No namespace or database selected');
    } else if (error instanceof AuthenticationError) {
        console.error('Authentication failed:', error.cause);
    }
}
```

## Handling query errors

When a SurrealQL query fails, the SDK throws a `ResponseError` containing the error code and message from the database.

```ts
import { ResponseError } from 'surrealdb';

try {
    await db.query('INVALID QUERY');
} catch (error) {
    if (error instanceof ResponseError) {
        console.error(`Database error [${error.code}]: ${error.message}`);
    }
}
```

## Handling version mismatches

The SDK performs a version check when connecting to a SurrealDB instance by default. If the connected version is outside the supported range, an `UnsupportedVersionError` is thrown. You can disable this check using the `versionCheck` option on [`.connect()`](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-options).

```ts
import { UnsupportedVersionError } from 'surrealdb';

try {
    await db.connect('ws://localhost:8000');
} catch (error) {
    if (error instanceof UnsupportedVersionError) {
        console.error(
            `Version ${error.version} is not supported. ` +
            `Requires >= ${error.minimum} and < ${error.maximum}`
        );
    }
}
```

## Handling feature availability

Some features are only available with specific engines or SurrealDB versions. An `UnsupportedFeatureError` is thrown when a feature is not supported by the configured engine, while an `UnavailableFeatureError` is thrown when the connected SurrealDB version does not support it.

You can proactively check for feature support using the [`isFeatureSupported()`](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#testing-for-features) method to avoid these errors entirely.

```ts
import { Features, UnsupportedFeatureError } from 'surrealdb';

if (db.isFeatureSupported(Features.LiveQueries)) {
    const live = await db.live(new Table('users'));
}
```

## Listening to connection errors

The SDK emits an `error` event for errors that occur outside of direct method calls, such as reconnection failures. You can subscribe to these events using the [`.subscribe()`](/docs/reference/javascript/api/core/surreal.md#subscribe) method.

```ts
import { ReconnectExhaustionError, UnexpectedConnectionError } from 'surrealdb';

db.subscribe('error', (error) => {
    if (error instanceof ReconnectExhaustionError) {
        console.error('All reconnection attempts failed');
    } else if (error instanceof UnexpectedConnectionError) {
        console.error('Connection error:', error.cause);
    }
});
```

## Recovering from errors

For operations that may fail transiently, you can implement retry logic. Combine specific error checks with a retry loop to handle recoverable failures gracefully.

```ts
import { ConnectionUnavailableError, ResponseError } from 'surrealdb';

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            return await fn();
        } catch (error) {
            if (error instanceof ConnectionUnavailableError) {
                await db.connect('ws://localhost:8000');
                continue;
            }
            if (error instanceof ResponseError && attempt < maxRetries - 1) {
                continue;
            }
            throw error;
        }
    }
    throw new Error('Max retries exceeded');
}

const users = await withRetry(() => db.select(new Table('users')));
```

For the specific case of write conflicts under concurrent load, prefer the SDK's built-in [`.retry()`](/docs/reference/javascript/api/queries/query.md#retry) over a hand-rolled loop - it applies exponential backoff and only replays work known to be safely retryable.

```ts
const [n] = await db
    .query<[number]>('UPDATE counter:c SET n += 1 RETURN n')
    .retry({ attempts: 3 })
    .collect();
```

## Learn more

- [Errors API reference](/docs/reference/javascript/api/errors.md) for a complete list of error classes and their properties
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for connection and reconnection configuration
- [Retrying on write conflict](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#retrying-on-write-conflict) for configuring built-in query retry
- [Authentication](/docs/reference/javascript/concepts/authentication.md) for handling authentication flows

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/executing-queries

# Executing queries

The JavaScript SDK provides query builder methods and a raw query API for interacting with SurrealDB.

The JavaScript SDK provides two ways to execute queries against SurrealDB: raw SurrealQL using the `.query()` method, and structured query builder methods like `.select()`, `.create()`, `.update()`, and `.delete()`. Both approaches support type-safe generics, chainable configuration, and multiple result formats.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#query"> <code> db.query(query, bindings?) </code></a></td>
			<td scope="row" data-label="Description">Executes raw SurrealQL statements</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#select"> <code> db.select(target) </code></a></td>
			<td scope="row" data-label="Description">Selects records from the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#create"> <code> db.create(target) </code></a></td>
			<td scope="row" data-label="Description">Creates new records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#insert"> <code> db.insert(target, data) </code></a></td>
			<td scope="row" data-label="Description">Inserts one or multiple records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#update"> <code> db.update(target) </code></a></td>
			<td scope="row" data-label="Description">Updates existing records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#upsert"> <code> db.upsert(target) </code></a></td>
			<td scope="row" data-label="Description">Inserts or replaces records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#delete"> <code> db.delete(target) </code></a></td>
			<td scope="row" data-label="Description">Deletes records from the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#relate"> <code> db.relate(from, edge, to) </code></a></td>
			<td scope="row" data-label="Description">Creates graph relationships between records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#run"> <code> db.run(name, args?) </code></a></td>
			<td scope="row" data-label="Description">Executes a SurrealDB function or SurrealML model</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#set"> <code> db.set(key, value) </code></a></td>
			<td scope="row" data-label="Description">Defines a parameter on the session</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#unset"> <code> db.unset(key) </code></a></td>
			<td scope="row" data-label="Description">Removes a parameter from the session</td>
		</tr>
	</tbody>
</table>

## Running raw SurrealQL

The `.query()` method executes raw [SurrealQL statements](/docs/reference/query-language/statements/overview.md) against the database. You can pass bindings as a second argument to safely inject variables into the query.

```ts
const [users] = await db.query<[User[]]>(
    'SELECT * FROM users WHERE age > $min_age',
    { min_age: 18 }
);
```

When executing multiple statements, each result maps to a position in the generic type parameter.

```ts
const [users, posts] = await db.query<[User[], Post[]]>(`
    SELECT * FROM users;
    SELECT * FROM posts;
`);
```

You can also pass a [bound query](/docs/reference/javascript/concepts/bound-queries.md) for automatic parameterisation using the `surql` template tag.

```ts
import { surql } from 'surrealdb';

const minAge = 18;
const [users] = await db.query<[User[]]>(
	surql`SELECT * FROM users WHERE age > ${minAge}`
);
```

## Selecting records

The `.select()` method reads records from the database. You can pass a [`Table`](/docs/reference/javascript/api/values/table.md) to select all records, a [`RecordId`](/docs/reference/javascript/api/values/record-id.md) to select a specific record, or a `RecordIdRange` to select a range.

```ts
import { Table, RecordId } from 'surrealdb';

const allUsers = await db.select(new Table('users'));

const user = await db.select(new RecordId('users', 'john'));
```

Query builder methods return chainable promises, allowing you to configure the query before it executes.

```ts
import { gt } from 'surrealdb';

const users = await db.select(new Table('users'))
    .fields('name', 'email', 'age')
    .where(gt('age', 18))
    .limit(10)
    .start(0)
    .fetch('posts');
```

In the above example, we use the `gt()` function to create a greater than condition. This function is part of the [expression utilities](/docs/reference/javascript/api/utilities/expr.md) that are available in the SDK.
If you prefer to write a raw condition, you can use the `raw()` function to insert the condition directly into the query.

```ts
import { raw } from 'surrealdb';

const users = await db.select(new Table('users'))
    .fields('name', 'email', 'age')
    .where(raw('age > 18'))
    .limit(10)
    .start(0)
    .fetch('posts');
```

## Creating records

The `.create()` method creates new records. Use `.content()` to set the record data. When passed a `Table`, SurrealDB generates a random ID. When passed a `RecordId`, the record is created with that specific ID.

```ts
const user = await db.create(new RecordId('users', 'john'))
    .content({
        name: 'John Doe',
        email: 'john@example.com',
    });

const autoId = await db.create(new Table('users'))
    .content({ name: 'Jane Doe' });
```

## Inserting records

The `.insert()` method inserts one or multiple records at once. This is more efficient than calling `.create()` in a loop when working with bulk data.

```ts
const users = await db.insert(new Table('users'), [
    { name: 'Alice', email: 'alice@example.com' },
    { name: 'Bob', email: 'bob@example.com' },
]);
```

## Updating records

The `.update()` and `.upsert()` methods modify existing records. Instead of passing content as a second argument, you choose an update strategy by chaining `.content()`, `.merge()`, `.replace()`, or `.patch()`.

**Replace content**

Replace the entire record with new data. Any existing fields not included in the new data will be removed.

```ts
await db.update(new RecordId('users', 'john'))
    .content({
        name: 'John Smith',
        email: 'john.smith@example.com',
    });
```

**Merge fields**

Merge new fields into the existing record. Existing fields that are not specified remain unchanged.

```ts
await db.update(new RecordId('users', 'john'))
    .merge({ email: 'new@example.com' });
```

**JSON Patch**

Apply [JSON Patch](https://jsonpatch.com) operations for fine-grained modifications.

```ts
await db.update(new RecordId('users', 'john'))
    .patch([
        { op: 'replace', path: '/email', value: 'new@example.com' },
        { op: 'add', path: '/verified', value: true },
    ]);
```

You can also filter which records to update using `.where()`.

```ts
await db.update(new Table('users'))
    .merge({ verified: true })
    .where('age >= 18');
```

## Deleting records

The `.delete()` method removes records from the database. Like other query methods, it accepts a `Table`, `RecordId`, or `RecordIdRange`.

```ts
await db.delete(new RecordId('users', 'john'));

await db.delete(new Table('users'));
```

## Creating graph relationships

The `.relate()` method creates edges between records in SurrealDB's [graph model](/docs/reference/query-language/statements/relate.md). You specify the source record(s), the edge table, and the target record(s).

```ts
await db.relate(
    new RecordId('users', 'john'),
    new Table('likes'),
    new RecordId('posts', '1'),
    { timestamp: new Date() },
);
```

## Running functions

The `.run()` method executes [SurrealDB functions](/docs/reference/query-language/statements/define/function.md) or SurrealML models by name. You can pass arguments and optionally specify a model version.

```ts
const result = await db.run('fn::calculate_total', [100, 0.2]);

const prediction = await db.run('ml::predict', '1.0.0', [inputData]);
```

To check the arguments and the result against a schema, see [Validating calls](/docs/reference/javascript/concepts/validating-calls.md).

## Setting session parameters

You can define parameters on the current session using `.set()` and remove them with `.unset()`. Session parameters are available in all subsequent queries as `$name` variables and persist for the lifetime of the session.

```ts
await db.set('current_user', {
    first: 'Tobie',
    last: 'Morgan Hitchcock',
});

await db.query('CREATE post SET author = $current_user');

await db.unset('current_user');
```

## Collecting specific results

When running multi-statement queries, you can use `.collect()` to pick specific result indexes rather than receiving all results.

```ts
const [foo, bar] = await db.query(`
    LET $a = 1;
    LET $b = 2;
    SELECT * FROM users;
    SELECT * FROM posts;
`).collect<[User[], Post[]]>(2, 3);
```

## Streaming query responses

The `.stream()` method returns an async iterable of response frames, allowing you to process results incrementally. Each frame is either a value, a completion signal with query stats, or an error.

```ts
const stream = db.query('SELECT * FROM large_table').stream();

for await (const frame of stream) {
    if (frame.isValue<User>()) {
        processUser(frame.value);
    } else if (frame.isDone()) {
        console.log('Duration:', frame.stats.duration);
    } else if (frame.isError()) {
        console.error(frame.error);
    }
}
```

On the embedded engines, `@surrealdb/node-native` and `@surrealdb/wasm-native`, results arrive in batches as the query produces them, so the first results are available before the last ones have been read. Over WebSocket and HTTP, the SDK waits for the whole response and then yields it frame by frame, so `.stream()` offers the same interface there without delivering results any earlier.

> [!NOTE]
> The embedded engines stream from SurrealDB 3.3.0. On earlier versions they held the whole result in memory and returned nothing until the last result was read, so `.stream()` gave no benefit over awaiting the query.

## Serialising results

The `.json()` method converts SurrealDB value types in query results to their JSON representations. This is useful when you need to serialise results for APIs or tools that don't understand SurrealDB's custom types.

```ts
const [products] = await db.query<[Product[]]>('SELECT * FROM product').json();
```

Learn more about the `jsonify` utility in the [Utilities](/docs/reference/javascript/concepts/utilities.md) concept page.

## Accessing response metadata

The `.responses()` method returns the full response objects including success status, query stats, and error information for each statement.

```ts
const responses = await db.query('SELECT * FROM users; SELECT * FROM posts').responses();

for (const response of responses) {
    if (response.success) {
        console.log('Result:', response.result);
        console.log('Duration:', response.stats?.duration);
    } else {
        console.error('Error:', response.error.message);
    }
}
```

## Retrying on write conflict

Under concurrent write load, a query can fail with a read/write conflict when another transaction touched the same data. Chain `.retry()` onto `.query()` or any query builder method to replay the operation automatically with exponential backoff.

```ts
const [n] = await db
    .query<[number]>('UPDATE counter:c SET n += 1 RETURN n')
    .retry({ attempts: 3 })
    .collect();

await db.delete(new RecordId('users', 'john')).retry();
```

Retry is off by default, and only applies to `.collect()` (or awaiting the query directly) - not `.responses()` or `.stream()`. For raw queries with multiple statements, only enable it when the statements are safe to apply more than once, since a retried query re-sends the whole thing.

You can also set a connection-wide default with the `retry` option on [`.connect()`](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#retrying-on-write-conflict), which `.retry()` overrides per call.

## Learn more

- [Bound queries](/docs/reference/javascript/concepts/bound-queries.md) for parameterised query building with `surql` and `BoundQuery`
- [Live queries](/docs/reference/javascript/concepts/live-queries.md) for real-time subscriptions
- [Transactions](/docs/reference/javascript/concepts/transactions.md) for atomic multi-query operations
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#retrying-on-write-conflict) for connection-wide retry configuration
- [Query builders API reference](/docs/reference/javascript/api/queries/) for detailed method signatures
- [SurrealQL statements](/docs/reference/query-language/statements/overview.md) for the query language reference

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/invoking-apis

# Invoking APIs

The JavaScript SDK allows you to invoke user-defined API endpoints in SurrealDB with type-safe HTTP-style methods.

SurrealDB allows you to define custom [API endpoints](/docs/reference/query-language/statements/define/api.md) that expose database operations through HTTP-style routes. The JavaScript SDK provides the `.api()` method to invoke these endpoints with full type safety, custom headers, and structured responses.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#api"> <code> db.api(prefix?) </code></a></td>
			<td scope="row" data-label="Description">Creates a SurrealApi instance for invoking user-defined endpoints</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-api.md#get"> <code> api.get(path) </code></a></td>
			<td scope="row" data-label="Description">Invokes a GET endpoint</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-api.md#post"> <code> api.post(path, body?) </code></a></td>
			<td scope="row" data-label="Description">Invokes a POST endpoint</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-api.md#invoke"> <code> api.invoke(path, request?) </code></a></td>
			<td scope="row" data-label="Description">Invokes an endpoint with a custom request object</td>
		</tr>
	</tbody>
</table>

## Accessing API endpoints

To invoke user-defined endpoints, call `.api()` on any `Surreal`, `SurrealSession`, or `SurrealTransaction` instance. The returned [`SurrealApi`](/docs/reference/javascript/api/core/surreal-api.md) object exposes HTTP-style methods like `.get()`, `.post()`, `.put()`, `.delete()`, and `.patch()`.

```ts
// Obtain the API reference
const api = db.api();

// Execute a GET request
const users = await api.get('/users').value();

// Execute a POST request
const newUser = await api.post('/users', {
    name: 'John Doe',
    email: 'john@example.com',
}).value();
```

By default, API calls return a response object containing `body`, `status`, and `headers`. Chaining `.value()` returns only the response body directly.

```ts
const response = await api.get('/users');

console.log(response.status);	// 200
console.log(response.headers);	// { 'content-type': 'application/json' }
console.log(response.body);	// [ { id: RecordId, name: 'John Doe', email: 'john@example.com' } ]

const users = await api.get('/users').value();

console.log(users); // [ { id: RecordId, name: 'John Doe', email: 'john@example.com' } ]
```

## Defining type-safe APIs

You can define TypeScript types for your API paths to get compile-time type checking on both request bodies and responses. Each path maps HTTP methods to a tuple of `[RequestBody, ResponseBody]`.

```ts
// Define API types using an object literal
type MyApi = {
    '/users': {
        get: [void, User[]];
        post: [CreateUserInput, User];
    };
    [K: `/users/${string}`]: {
        get: [void, User];
        put: [UpdateUserInput, User];
        delete: [void, void];
    };
};

// Pass the custom API types to the .api() method
const api = db.api<MyApi>();

// All handlers will now be type-safe
const users: User[] = await api.get('/users').value();
```

## Setting request headers

You can set headers on individual requests by chaining `.header()`, or set default headers on the API instance using `api.header()`. Setting a header value to `null` removes it.

```ts
// Single-request header
const result = await api.get('/protected')
    .header('X-Custom-Header', 'value')
    .value();

// Global header
api.header('Content-Type', 'application/json');

// Remove global header
api.header('Content-Type', null);
```

## Using a path prefix

When working with a group of related endpoints, you can pass a prefix to `.api()`. All subsequent calls will be relative to that prefix.

```ts
const usersApi = db.api<UserPaths>('/users');

const allUsers = await usersApi.get('/').value();
const user = await usersApi.get('/123').value();
```

## Handling API errors

When an API call fails, the SDK throws an [`UnsuccessfulApiError`](/docs/reference/javascript/api/errors.md#unsuccessfulapierror). This error includes the path, HTTP method, and the full response object.

```ts
import { UnsuccessfulApiError } from 'surrealdb';

try {
    await api.get('/users/999').value();
} catch (error) {
    if (error instanceof UnsuccessfulApiError) {
        console.error(`${error.method} ${error.path} failed`);
        console.error('Status:', error.response.status);
    }
}
```

## Validating requests and responses

To check a request body before it is sent, or a response body after it arrives, use `.request()` and `.response()` with any [Standard Schema](https://standardschema.dev). See [Validating calls](/docs/reference/javascript/concepts/validating-calls.md).

## Learn more

- [SurrealApi API reference](/docs/reference/javascript/api/core/surreal-api.md) for the complete list of methods and type parameters
- [ApiPromise API reference](/docs/reference/javascript/api/queries/api-promise.md) for response configuration options
- [DEFINE API](/docs/reference/query-language/statements/define/api.md) for defining custom API endpoints in SurrealQL

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/live-queries

# Live queries

The JavaScript SDK supports real-time live queries that stream changes from the database to your application.

Live queries allow your application to receive real-time notifications whenever records in the database are created, updated, or deleted. The JavaScript SDK provides two approaches: managed live queries that the SDK controls and automatically restarts on reconnect, and unmanaged live queries that you create via SurrealQL and subscribe to manually.

> [!NOTE]
> Live queries require a WebSocket connection. They are not supported over HTTP. You can check for support using the [feature testing](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#testing-for-features) mechanism.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#live"> <code> db.live(target) </code></a></td>
			<td scope="row" data-label="Description">Creates a managed live query subscription</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-queryable.md#liveof"> <code> db.liveOf(id) </code></a></td>
			<td scope="row" data-label="Description">Subscribes to an existing live query by its ID</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/queries/live-promise.md"> <code> live.subscribe(callback) </code></a></td>
			<td scope="row" data-label="Description">Registers a callback for live query updates</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/queries/live-promise.md"> <code> live.kill() </code></a></td>
			<td scope="row" data-label="Description">Stops the live query and unsubscribes all listeners</td>
		</tr>
	</tbody>
</table>

## Creating a managed live query

The `.live()` method creates a managed live query subscription. The SDK handles the lifecycle of the query, including automatically restarting it when the connection reconnects. The provided [`Table`](/docs/reference/javascript/api/values/table.md) determines the records to listen to.

```ts
import { Table } from 'surrealdb';

const live = await db.live(new Table('users'));
```

Much like the `.select()` method, you can chain multiple methods to configure the live query before executing it. This includes enabling diff results, selecting specific fields, and filtering the records to listen to.

```ts
import { gt } from 'surrealdb';

const live = await db.live(new Table('users'))
    .diff()
    .fields('name', 'email', 'age')
    .where(gt('age', 18));
```

### Listening with a callback

Use `.subscribe()` on the returned live query to register a callback. The callback receives the action, the result record, and the record ID.

```ts
live.subscribe((action, result, record) => {
    switch (action) {
        case 'CREATE':
            console.log('New user:', result);
            break;
        case 'UPDATE':
            console.log('Updated user:', record, result);
            break;
        case 'DELETE':
            console.log('Deleted user:', record);
            break;
    }
});
```

### Iterating messages with async iteration

Live queries also support the `for await...of` pattern, which yields each message as an object with `action` and `value` properties.

```ts
for await (const { action, value } of live) {
    console.log(`${action}:`, value);
}
```

## Creating an unmanaged live query

If you need to start a live query using SurrealQL (for example, with a custom `WHERE` clause), you can execute a `LIVE SELECT` statement and then subscribe to it using `.liveOf()`. Unmanaged queries are not automatically restarted on reconnect.

```ts
const [id] = await db.query('LIVE SELECT * FROM users WHERE active = true');
const live = db.liveOf(id);

live.subscribe((action, result, record) => {
    console.log(action, result);
});
```

## Handling live query actions

Every live query message has an action that indicates what happened to the record:

| Action | Description |
|--------|-------------|
| `CREATE` | A new record was created that matches the subscription |
| `UPDATE` | An existing record within the subscription was modified |
| `DELETE` | A record within the subscription was deleted |

To receive only some actions, filter on the server rather than in the handler. A live query started from SurrealQL, as in [Creating an unmanaged live query](#creating-an-unmanaged-live-query), can filter on `$event`, so `LIVE SELECT * FROM users WHERE $event = 'CREATE'` sends create notifications only. [Filtering with WHERE](/docs/learn/querying/real-time/live-queries.md#filtering-with-where) lists the other parameters a filter can use.

## Stopping a live query

Call `.kill()` to stop a live query and unsubscribe all listeners. This releases resources on both the client and the server.

```ts
await live.kill();
```

## Automatic restart on reconnect

Managed live queries created with `.live()` are automatically restarted when the SDK reconnects to the database after a connection drop. This means your application will continue receiving updates without any manual intervention.

Unmanaged live queries created via `LIVE SELECT` and `.liveOf()` are not automatically restarted. If you need reconnection resilience with custom queries, prefer using a managed live query or re-subscribing manually by listening to the [`connected`](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-status) event.

## Checking for live query support

Live queries require a WebSocket-based engine. Before creating a live query, you can verify that the current connection supports them.

```ts
import { Features } from 'surrealdb';

if (db.isFeatureSupported(Features.LiveQueries)) {
    const live = await db.live(new Table('users'));
}
```

## Learn more

- [Live query API reference](/docs/reference/javascript/api/queries/live-promise.md) for detailed method signatures
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for WebSocket connection setup
- [LIVE SELECT](/docs/reference/query-language/statements/live-select.md) for the SurrealQL live query syntax

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/multiple-sessions

# Multiple sessions

The JavaScript SDK supports multiple isolated sessions within a single connection, each with their own authentication and context.

The JavaScript SDK allows you to create multiple isolated sessions within a single connection. Each session maintains its own namespace, database, variables, and authentication state, while sharing the underlying connection to SurrealDB. This is useful when different parts of your application need to operate under different credentials or contexts simultaneously.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal.md#newsession"> <code> db.newSession() </code></a></td>
			<td scope="row" data-label="Description">Creates a new isolated session on the connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#forksession"> <code> session.forkSession() </code></a></td>
			<td scope="row" data-label="Description">Creates a copy of a session, inheriting its state</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#closesession"> <code> session.closeSession() </code></a></td>
			<td scope="row" data-label="Description">Destroys a session and releases its resources</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#reset"> <code> session.reset() </code></a></td>
			<td scope="row" data-label="Description">Resets a session's state without destroying it</td>
		</tr>
	</tbody>
</table>

## Creating isolated sessions

Call `.newSession()` on a `Surreal` instance to create a new session. The new session starts with no namespace, database, or authentication, and must be configured independently.

```ts
const session = await db.newSession();

await session.use({ namespace: 'production', database: 'main' });

await session.signin({
    namespace: 'production',
    database: 'main',
    access: 'user_access',
    variables: {
        email: 'user@example.com',
        password: 'secure_password',
    },
});

const users = await session.select(new Table('users'));
```

Sessions support all the same query methods as the main `Surreal` instance, including `.query()`, `.select()`, `.create()`, `.update()`, `.delete()`, and [the rest of the query methods](/docs/reference/javascript/concepts/executing-queries.md).

## Forking an existing session

The `.forkSession()` method creates a new session that inherits the namespace, database, variables, and authentication state from the parent session. This is useful when you need a temporary context that starts with the same setup.

```ts
const primary = await db.newSession();
await primary.use({ namespace: 'app', database: 'main' });
await primary.signin({ username: 'admin', password: 'secret' });

const forked = await primary.forkSession();
await forked.use({ database: 'analytics' });
```

The forked session operates independently after creation. Changes to the parent session do not affect the fork, and vice versa.

## Automatic cleanup with await using

Sessions support the [await using](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/await_using) declaration. When you declare a session with `await using`, JavaScript automatically closes the session when execution leaves the current scope.

```ts
{
    await using session = await db.newSession();
    await session.use({ namespace: 'main', database: 'main' });
    const data = await session.select(new Table('users'));
}
```

This is equivalent to manually calling `session.closeSession()` in a `finally` block, but with cleaner syntax.

## Closing and resetting sessions

When you are done with a session, call `.closeSession()` to destroy it and release server-side resources.

```ts
const session = await db.newSession();

await session.select(new Table('users'));

await session.closeSession();
```

If you want to clear a session's state without destroying it, use `.reset()`. This removes all variables and invalidates authentication, but keeps the session alive for reuse.

```ts
await session.reset();
```

> [!NOTE]
> Using a session after it has been closed throws an [`InvalidSessionError`](/docs/reference/javascript/api/errors.md#invalidsessionerror).

## Reconnection behaviour

Sessions are automatically restored when the underlying connection reconnects after a drop. The SDK re-establishes each session's namespace, database, variables, and authentication state on the server. If a session used the `authentication` property from the original `.connect()` call, it will be re-authenticated automatically.

Sessions that were authenticated via `.signin()` or `.signup()` rely on the [`auth`](/docs/reference/javascript/api/core/surreal-session.md#event-auth) event for re-authentication. See [Authentication](/docs/reference/javascript/concepts/authentication.md#listening-to-authentication-events) for details.

## Subscribing to session events

Each session emits its own events, independent of other sessions and the main `Surreal` instance. You can subscribe to the `auth` and `using` events on any session.

```ts
session.subscribe('auth', (tokens) => {
    if (tokens) {
        console.log('Session authenticated');
    } else {
        console.log('Session signed out');
    }
});

session.subscribe('using', (using) => {
    console.log('Now using:', using.namespace, '/', using.database);
});
```

## Learn more

- [SurrealSession API reference](/docs/reference/javascript/api/core/surreal-session.md) for the full session interface
- [Surreal.newSession()](/docs/reference/javascript/api/core/surreal.md#newsession) for session creation details
- [Authentication](/docs/reference/javascript/concepts/authentication.md) for session-level authentication
- [Transactions](/docs/reference/javascript/concepts/transactions.md) for atomic operations within sessions

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/transactions

# Transactions

The JavaScript SDK supports atomic transactions for executing multiple queries that succeed or fail together.

Transactions allow you to execute a group of queries atomically, meaning either all changes are applied or none are. This is essential for maintaining data consistency when performing related operations that must not be partially applied.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-session.md#begintransaction"> <code> db.beginTransaction() </code></a></td>
			<td scope="row" data-label="Description">Starts a new transaction</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-transaction.md#commit"> <code> txn.commit() </code></a></td>
			<td scope="row" data-label="Description">Commits the transaction, applying all changes</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/javascript/api/core/surreal-transaction.md#cancel"> <code> txn.cancel() </code></a></td>
			<td scope="row" data-label="Description">Cancels the transaction, discarding all changes</td>
		</tr>
	</tbody>
</table>

## Starting a transaction

Call `.beginTransaction()` on any [`Surreal`](/docs/reference/javascript/api/core/surreal.md) or [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md) instance to start a new transaction. The returned [`SurrealTransaction`](/docs/reference/javascript/api/core/surreal-transaction.md) object provides all the same [query methods](/docs/reference/javascript/concepts/executing-queries.md) as a regular session, but every operation is executed within the transaction scope.

```ts
const txn = await db.beginTransaction();
```

## Executing queries within a transaction

Use the transaction object to execute queries just as you would on the `Surreal` instance. All operations are held in a pending state until you commit or cancel.

```ts
const txn = await db.beginTransaction();

await txn.create(new RecordId('users', 'alice'))
    .content({ name: 'Alice', email: 'alice@example.com' });

await txn.create(new RecordId('users', 'bob'))
    .content({ name: 'Bob', email: 'bob@example.com' });
```

## Committing changes

Call `.commit()` to apply all pending changes to the database. After committing, the transaction cannot be used again.

```ts
await txn.commit();
```

## Cancelling and rolling back

Call `.cancel()` to discard all pending changes. This is typically done when an error occurs during the transaction. After cancelling, the transaction cannot be used again.

```ts
await txn.cancel();
```

## Retrying on write conflict

Queries executed within a transaction can fail with a write conflict under concurrent load, just like queries outside a transaction. Since `SurrealTransaction` exposes the same query methods as a regular session, you can chain [`.retry()`](/docs/reference/javascript/api/queries/query.md#retry) onto any statement inside the transaction to replay it with exponential backoff.

```ts
const txn = await db.beginTransaction();

await txn.update(new RecordId('accounts', 'alice'))
    .merge({ balance: from.balance - 100 })
    .retry({ attempts: 3 });

await txn.commit();
```

See [Retrying on write conflict](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#retrying-on-write-conflict) for how to configure a connection-wide default.

## Handling errors in transactions

Always wrap transaction logic in a try-catch block to ensure the transaction is cancelled if any operation fails. This prevents partial changes from being committed.

```ts
const txn = await db.beginTransaction();

try {
    const from = await txn.select(new RecordId('accounts', 'alice'));
    const to = await txn.select(new RecordId('accounts', 'bob'));

    if (from.balance < 100) {
        throw new Error('Insufficient funds');
    }

    await txn.update(new RecordId('accounts', 'alice'))
        .merge({ balance: from.balance - 100 });

    await txn.update(new RecordId('accounts', 'bob'))
        .merge({ balance: to.balance + 100 });

    await txn.commit();
} catch (error) {
    await txn.cancel();
    throw error;
}
```

### Finding the statement that failed

A transaction can also be written in SurrealQL with `BEGIN` and `COMMIT` inside one `.query()` call. When one statement fails, every statement in the transaction reports a failure: those before it with `The query was not executed due to a failed transaction`, the failing one with its own error, and `COMMIT` with `Cannot COMMIT: the transaction was aborted due to a prior error`.

Awaiting the query, directly or through [`.collect()`](/docs/reference/javascript/concepts/executing-queries.md#collecting-specific-results), rejects with the first failure. That is the generic message of the first statement, not the cause. In the example below, `user:1` already holds the email `a@example.com`, and a `UNIQUE` index named `unique_email` covers the `email` field:

```ts
const query = `
    BEGIN;
    CREATE user:2 SET email = 'b@example.com';
    CREATE user:3 SET email = 'a@example.com';
    COMMIT;
`;

try {
    await db.query(query);
} catch (error) {
    // The query was not executed due to a failed transaction
    console.log(error.message);
}
```

To find the cause, use `.responses()`, which returns every statement's result instead of rejecting:

```ts
const responses = await db.query(query).responses();

for (const response of responses) {
    if (!response.success) console.log(response.error.message);
}
```

```text title="Output"
The query was not executed due to a failed transaction
Database index `unique_email` already contains 'a@example.com', with record `user:1`
Cannot COMMIT: the transaction was aborted due to a prior error
```

The first entry is `BEGIN`, which succeeds and returns nothing.

## Best practices

### Keep transactions short

Execute transactions quickly to avoid holding resources longer than necessary. Perform any validation or external API calls before starting the transaction.

```ts
if (!isValidEmail(email)) {
    throw new Error('Invalid email');
}

const txn = await db.beginTransaction();
await txn.create(new RecordId('users', id)).content({ email });
await txn.commit();
```

### Do not reuse transactions

Once a transaction is committed or cancelled, create a new one for subsequent operations.

```ts
const txn1 = await db.beginTransaction();
await txn1.create(record1).content(data1);
await txn1.commit();

const txn2 = await db.beginTransaction();
await txn2.create(record2).content(data2);
await txn2.commit();
```

On a remote WebSocket server, each open client-managed transaction counts toward [`SURREAL_MAX_TRANSACTIONS_PER_CONNECTION`](/docs/reference/cli/db/environment-variables.md#websocket-config) or [`SURREAL_MAX_TRANSACTIONS_PER_SESSION`](/docs/reference/cli/db/environment-variables.md#websocket-config) (default 64 each). Exceeding the limit fails with `Too many open transactions`.

## Learn more

- [SurrealTransaction API reference](/docs/reference/javascript/api/core/surreal-transaction.md) for the complete transaction interface
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) for the query methods available on transactions
- [Multiple sessions](/docs/reference/javascript/concepts/multiple-sessions.md) for running transactions on isolated sessions

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/utilities

# Utilities

SQON provides value comparison, conversion, and escaping utilities. The SDK adds query-building helpers on top.

Helpers are split across two packages. [`@surrealdb/sqon`](https://www.npmjs.com/package/@surrealdb/sqon) covers value comparison, conversion, and escaping. The `surrealdb` driver adds query-building tools for parameterised SurrealQL. Import from `surrealdb` when you use the driver and want both layers in one place.

```ts
// SQON (either package)
import { equals, jsonify, escapeIdent } from '@surrealdb/sqon';

// SDK only
import { surql, expr, BoundQuery, s, d, r, u } from 'surrealdb';
```

## SQON utilities

These live in `@surrealdb/sqon` alongside the [value types](/docs/reference/javascript/concepts/value-types.md). You do not need the database client to use them.

<table>
	<thead>
		<tr>
			<th scope="col">Utility</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/equals.md"> <code> equals(a, b) </code></a></td>
			<td scope="row" data-label="Description">Deep equality comparison for all value types</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><code> jsonify(value) </code></td>
			<td scope="row" data-label="Description">Converts value types to JSON-safe string representations</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/escape.md"> <code> escapeIdent(name) </code></a></td>
			<td scope="row" data-label="Description">Escapes table and field names for use in SurrealQL</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/escape.md"> <code> escapeKey(key) </code></a></td>
			<td scope="row" data-label="Description">Escapes object keys for use in queries</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/escape.md"> <code> escapeRid(value) </code></a></td>
			<td scope="row" data-label="Description">Escapes record ID components</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/escape.md"> <code> escapeValue(value) </code></a></td>
			<td scope="row" data-label="Description">Escapes arbitrary values for embedding in queries</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><code> toSurqlString(value) </code></td>
			<td scope="row" data-label="Description">Converts a value tree to a SurrealQL string representation</td>
		</tr>
	</tbody>
</table>

### Comparing values with deep equality

`===` compares object references, so two `RecordId` instances with the same table and ID still fail an identity check. `equals()` walks the structure instead, including nested objects, arrays, SurrealDB value classes, `Date`, `RegExp`, and mixed `bigint`/`number` values.

```ts
import { equals, RecordId } from '@surrealdb/sqon';

const id1 = new RecordId('users', 'john');
const id2 = new RecordId('users', 'john');

console.log(id1 === id2);        // false (different references)
console.log(equals(id1, id2));    // true (same value)
```

This is useful when you want to know whether a fetched record changed between two reads:

```ts
const user1 = await db.select(userId);
const user2 = await db.select(userId);

if (!equals(user1, user2)) {
    console.log('Record was modified');
}
```

> [!NOTE]
> Individual value classes also expose an `.equals()` instance method for single-type comparisons (e.g. `recordId.equals(other)`). Use the standalone `equals()` function for generic or cross-type comparisons.

### Jsonifying query results

`jsonify()` turns SurrealDB value classes in a result into the JSON string forms SurrealDB would use. Plain numbers, strings, and objects are left as they are. The return type reflects the conversion.

```ts
import { jsonify, RecordId, Decimal, Duration } from '@surrealdb/sqon';

const result = jsonify({
    rid: new RecordId('person', 'tobie'),
    dec: new Decimal('3.333333'),
    dur: new Duration('1d2h'),
    num: 123,
    str: 'hello',
});
```

```json
{
    "rid": "person:tobie",
    "dec": "3.333333",
    "dur": "1d2h",
    "num": 123,
    "str": "hello"
}
```

You can also call `.json()` on [query methods](/docs/reference/javascript/concepts/executing-queries.md#converting-results-to-json) to jsonify the result in one step.

For JSON with explicit type wrappers (for example `{ "$recordId": ... }`), use [`JsonCodec`](/docs/reference/javascript/concepts/codecs.md#json-codec) instead.

### Escaping identifiers and values

The escape helpers quote identifiers and values for hand-built SurrealQL. Prefer [bound queries](/docs/reference/javascript/concepts/bound-queries.md) or [value classes](/docs/reference/javascript/concepts/value-types.md) when you can.

```ts
import { escapeIdent, escapeKey, escapeRid, escapeValue } from '@surrealdb/sqon';

escapeIdent('users');           // 'users'
escapeIdent('user-table');      // '`user-table`'
escapeIdent('select');          // '`select`'

escapeKey('user-property');     // properly escaped for object notation

escapeRid('john');              // 'john'
escapeRid('user@email.com');    // '`user@email.com`'

escapeValue('hello');           // "'hello'"
escapeValue(42);                // '42'
escapeValue(null);              // 'null'
escapeValue(undefined);         // 'none'
```

> [!WARNING]
> Escaping alone does not stop injection. Use [`surql`](/docs/reference/javascript/concepts/bound-queries.md) or [`BoundQuery`](/docs/reference/javascript/api/utilities/bound-query.md) for dynamic values.

### Converting to SurrealQL strings

`toSurqlString()` walks a value tree (objects, arrays, SQON classes) and returns a SurrealQL literal string. It is useful for logs, debugging, or SurrealQL snippets outside bound queries.

```ts
import { toSurqlString, RecordId, Decimal } from '@surrealdb/sqon';

toSurqlString(new RecordId('person', 'tobie')); // r"person:tobie"
toSurqlString(new Decimal('3.14'));             // 3.14dec
```

## SDK utilities

These ship with the `surrealdb` driver and tie into its query engine. They are not exported from `@surrealdb/sqon`.

<table>
	<thead>
		<tr>
			<th scope="col">Utility</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/expr.md"> <code> expr() </code></a></td>
			<td scope="row" data-label="Description">Composes type-safe expressions for WHERE clauses and conditions</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/surql.md"> <code> surql </code></a></td>
			<td scope="row" data-label="Description">Tagged template for composing parameterised queries</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/bound-query.md"> <code> BoundQuery </code></a></td>
			<td scope="row" data-label="Description">Parameterised query class with manual control</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><code> s, d, r, u </code></td>
			<td scope="row" data-label="Description">Tagged templates for SurrealQL string, datetime, record, and UUID prefixes</td>
		</tr>
		<tr>
			<td scope="row" data-label="Utility"><a href="/docs/reference/javascript/api/utilities/is-retryable-conflict.md"> <code> isRetryableConflict(error) </code></a></td>
			<td scope="row" data-label="Description">Default predicate used by <code>.retry()</code> to detect retryable write conflicts</td>
		</tr>
	</tbody>
</table>

### Building expressions

`expr()` and its operators build conditions for `.where()` and for `surql` templates. See [Bound queries](/docs/reference/javascript/concepts/bound-queries.md#composing-expressions) for the full list.

```ts
import { expr, surql, eq, gte, and, Table } from 'surrealdb';

const premiumAdults = expr(and(
    eq('tier', 'premium'),
    gte('age', 18),
));

const users = await db.select(new Table('users')).where(premiumAdults);
```

Operators cover comparisons (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`), logic (`and`, `or`, `not`), strings and arrays (`contains`, `containsAny`, `containsAll`), geometry (`inside`, `outside`, `intersects`), and search (`matches`, `knn`).

### Composing parameterised queries

`surql` and `BoundQuery` bind parameters so you do not splice user input into query strings. See [Bound queries](/docs/reference/javascript/concepts/bound-queries.md).

```ts
import { surql, BoundQuery } from 'surrealdb';

const minAge = 18;
const query = surql`SELECT * FROM users WHERE age >= ${minAge}`;
const [users] = await db.query(query);

const bound = new BoundQuery(
    'SELECT * FROM users WHERE status = $status',
    { status: 'active' },
);
bound.bind('tier', 'premium');
const [results] = await db.query(bound);
```

### String prefix templates

The `s`, `d`, `r`, and `u` templates build typed literals with SurrealQL's string prefixes. See [Value types](/docs/reference/javascript/concepts/value-types.md#string-prefixes).

## Best practices

### Use surql for parameterisation

```ts
const query = surql`SELECT * FROM users WHERE name = ${userName}`;

// Avoid string concatenation (injection risk)
const query = `SELECT * FROM users WHERE name = '${userName}'`;
```

### Use expr for complex conditions

```ts
const condition = expr(and(
    eq('status', 'active'),
    gte('age', 18),
));

await db.select(new Table('users')).where(condition);
```

### Use equals for deep comparison

```ts
if (equals(recordId1, recordId2)) {
    // Same value
}

// Avoid reference comparison (only true if same instance)
if (recordId1 === recordId2) { ... }
```

## Learn more

- [equals API reference](/docs/reference/javascript/api/utilities/equals.md) for deep comparison details
- [expr API reference](/docs/reference/javascript/api/utilities/expr.md) for expression builder operators
- [surql API reference](/docs/reference/javascript/api/utilities/surql.md) for template tag details
- [BoundQuery API reference](/docs/reference/javascript/api/utilities/bound-query.md) for manual query building
- [Escape functions API reference](/docs/reference/javascript/api/utilities/escape.md) for all escape utilities
- [Codecs](/docs/reference/javascript/concepts/codecs.md) for serialising value types over CBOR and JSON
- [Bound queries](/docs/reference/javascript/concepts/bound-queries.md) for safe query composition
- [Value types](/docs/reference/javascript/concepts/value-types.md) for SurrealDB-specific data classes

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/validating-calls

# Validating calls

Check the arguments, request bodies, results and responses of function and API calls against a Standard Schema in the JavaScript SDK.

TypeScript checks the values you pass to the SDK at compile time, but data from a form, a request or `JSON.parse` is only checked when you check it. The SDK can validate the calls that [run functions](/docs/reference/javascript/api/queries/run-promise.md) and [invoke APIs](/docs/reference/javascript/concepts/invoking-apis.md) against a schema, before the call is sent and after the answer arrives.

The SDK accepts any [Standard Schema](https://standardschema.dev), the interface implemented by Zod, Valibot, ArkType and other validation libraries. It does not depend on any of them, so use the one you already have.

## Functions

`.args()` checks the arguments before the call is sent, and `.returns()` checks the result and types the promise as what the schema produces. The schema for `.args()` validates the whole array of arguments, so use a tuple.

```ts
import { z } from 'zod';

const sum = await db
    .run('fn::add', [1, 2])
    .args(z.tuple([z.number(), z.number()]))
    .returns(z.number());
// sum: number
```

## APIs

`.request()` checks the request body before the request is sent, and `.response()` checks the body of the response.

```ts
const User = z.object({ id: z.string(), name: z.string() });

const user = await db
    .api()
    .post('/users', { name: 'Ada' })
    .request(z.object({ name: z.string() }))
    .response(User)
    .value();
// user: { id: string; name: string }
```

Only the body of a successful response (status 200 to 299) is checked against `.response()`, because an error response carries a different body. With `.value()`, an unsuccessful response still throws an [`UnsuccessfulApiError`](/docs/reference/javascript/api/errors.md#unsuccessfulapierror).

## When a value is invalid

The promise rejects with a `SchemaValidationError`. It lists every issue the schema reported, each with the path of the failing value. When the arguments or the request body are invalid, nothing is sent to the database.

```ts
import { SchemaValidationError } from 'surrealdb';

try {
    await db.run('fn::add', ['1', 2]).args(z.tuple([z.number(), z.number()]));
} catch (error) {
    if (error instanceof SchemaValidationError) {
        console.error(error.subject); // "arguments for fn::add"
        console.error(error.issues);  // [{ message: "...", path: [0] }]
    }
}
```

`SchemaValidationError` is a client-side check. The server reports its own failures with a different [`ValidationError`](/docs/reference/javascript/api/errors.md).

## Things to know

- A schema which transforms or coerces its input changes what is sent: the SDK sends the value the schema produced, not the one you passed.
- Validation runs when the promise is awaited. It does not apply to `.compile()` or `.stream()`.
- With `.json()`, a result schema sees the JSON-compatible result, not the SurrealDB value types.
- Validation is opt-in per call. Calls without a schema behave as before.

---

Source: https://surrealdb.com/docs/reference/javascript/concepts/value-types

# Value types

The SQON library provides custom classes for SurrealDB-specific data types, re-exported by the JavaScript SDK for convenience.

SurrealDB has types that JavaScript does not: record IDs, nanosecond datetimes, arbitrary-precision decimals, and others. They live in [`@surrealdb/sqon`](https://www.npmjs.com/package/@surrealdb/sqon), which also ships codecs and a few core utilities. The `surrealdb` package re-exports all of it, so either import path works:

```ts
// With the driver (usual choice)
import { RecordId, Table, DateTime } from 'surrealdb';

// SQON only (no database client)
import { RecordId, Table, DateTime } from '@surrealdb/sqon';
```

The classes validate input, keep database precision, and plug into query methods and [codecs](/docs/reference/javascript/concepts/codecs.md).

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Class</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/javascript/api/values/record-id.md"> <code> RecordId </code></a></td>
			<td scope="row" data-label="Description">Type-safe record identifiers with table and ID components</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/javascript/api/values/table.md"> <code> Table </code></a></td>
			<td scope="row" data-label="Description">Type-safe table references for query methods</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/javascript/api/values/datetime.md"> <code> DateTime </code></a></td>
			<td scope="row" data-label="Description">Datetime values with nanosecond precision</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/javascript/api/values/duration.md"> <code> Duration </code></a></td>
			<td scope="row" data-label="Description">Time duration values with multiple unit support</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/javascript/api/values/decimal.md"> <code> Decimal </code></a></td>
			<td scope="row" data-label="Description">Arbitrary precision decimal numbers</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/javascript/api/values/uuid.md"> <code> Uuid </code></a></td>
			<td scope="row" data-label="Description">Universally unique identifiers (v4 and v7)</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/javascript/api/values/range.md"> <code> Range </code></a></td>
			<td scope="row" data-label="Description">Bounded or unbounded range values</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/javascript/api/values/file-ref.md"> <code> FileRef </code></a></td>
			<td scope="row" data-label="Description">References to files stored in SurrealDB</td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/javascript/api/values/geometry.md"> <code> Geometry* </code></a></td>
			<td scope="row" data-label="Description">GeoJSON geometry types (Point, Line, Polygon, etc.)</td>
		</tr>
	</tbody>
</table>

## Type mapping

SurrealQL types map to JavaScript types as follows:

<table>
	<thead>
		<tr>
			<th scope="col">SurrealQL type</th>
			<th scope="col">JavaScript type</th>
			<th scope="col">Example</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td><code>bool</code></td>
			<td><code>boolean</code></td>
			<td><code>true</code>, <code>false</code></td>
		</tr>
		<tr>
			<td><code>int</code>, <code>float</code></td>
			<td><code>number</code></td>
			<td><code>42</code>, <code>3.14</code></td>
		</tr>
		<tr>
			<td><code>string</code></td>
			<td><code>string</code></td>
			<td><code>"hello"</code></td>
		</tr>
		<tr>
			<td><code>null</code></td>
			<td><code>null</code></td>
			<td><code>null</code></td>
		</tr>
		<tr>
			<td><code>none</code></td>
			<td><code>undefined</code></td>
			<td><code>undefined</code></td>
		</tr>
		<tr>
			<td><code>array</code></td>
			<td><code>Array</code></td>
			<td><code>[1, 2, 3]</code></td>
		</tr>
		<tr>
			<td><code>object</code></td>
			<td><code>Object</code></td>
			<td><code>{`{ key: "value" }`}</code></td>
		</tr>
		<tr>
			<td><code>set</code></td>
			<td><code>Set</code></td>
			<td><code>new Set([1, 2, 3])</code></td>
		</tr>
		<tr>
			<td><code>bytes</code></td>
			<td><code>Uint8Array</code></td>
			<td><code>new Uint8Array([...])</code></td>
		</tr>
		<tr>
			<td><code>record</code></td>
			<td><a href="/docs/reference/javascript/api/values/record-id.md"><code>RecordId</code></a></td>
			<td><code>new RecordId('users', 'john')</code></td>
		</tr>
		<tr>
			<td>-</td>
			<td><a href="/docs/reference/javascript/api/values/table.md"><code>Table</code></a></td>
			<td><code>new Table('users')</code></td>
		</tr>
		<tr>
			<td><code>datetime</code></td>
			<td><a href="/docs/reference/javascript/api/values/datetime.md"><code>DateTime</code></a></td>
			<td><code>DateTime.now()</code></td>
		</tr>
		<tr>
			<td><code>duration</code></td>
			<td><a href="/docs/reference/javascript/api/values/duration.md"><code>Duration</code></a></td>
			<td><code>Duration.parse('1h30m')</code></td>
		</tr>
		<tr>
			<td><code>decimal</code></td>
			<td><a href="/docs/reference/javascript/api/values/decimal.md"><code>Decimal</code></a></td>
			<td><code>new Decimal('19.99')</code></td>
		</tr>
		<tr>
			<td><code>uuid</code></td>
			<td><a href="/docs/reference/javascript/api/values/uuid.md"><code>Uuid</code></a></td>
			<td><code>Uuid.v7()</code></td>
		</tr>
		<tr>
			<td><code>geometry</code></td>
			<td><a href="/docs/reference/javascript/api/values/geometry.md"><code>Geometry*</code></a></td>
			<td><code>new GeometryPoint([1, 2])</code></td>
		</tr>
		<tr>
			<td><code>range</code></td>
			<td><a href="/docs/reference/javascript/api/values/range.md"><code>Range</code></a></td>
			<td><code>new Range(1, 10)</code></td>
		</tr>
		<tr>
			<td><code>file</code></td>
			<td><a href="/docs/reference/javascript/api/values/file-ref.md"><code>FileRef</code></a></td>
			<td><code>record.avatar</code></td>
		</tr>
	</tbody>
</table>

## RecordId and Table

A [`RecordId`](/docs/reference/javascript/api/values/record-id.md) is a table name plus an ID. A [`Table`](/docs/reference/javascript/api/values/table.md) is a table reference on its own. From v2 onward, query methods expect a `Table` instance for table names, not a bare string, so table names are not confused with record IDs.

```ts
import { RecordId, Table } from 'surrealdb';

const userId = new RecordId('users', 'john');
const user = await db.select(userId);

const usersTable = new Table('users');
const allUsers = await db.select(usersTable);

const parsed = RecordId.parse('users:john');
```

The ID component of a `RecordId` can be a `string`, `number`, `bigint`, `Uuid`, array, or object. The `Table` class also supports a type parameter for type-safe query results.

```ts
const users = new Table<User>('users');
const results: User[] = await db.select(users);
```

## DateTime and Duration

A [`DateTime`](/docs/reference/javascript/api/values/datetime.md) is a timestamp with nanosecond precision. JavaScript's `Date` stops at milliseconds, so `DateTime` keeps what SurrealDB actually stored. A [`Duration`](/docs/reference/javascript/api/values/duration.md) follows SurrealQL duration syntax.

```ts
import { DateTime, Duration } from 'surrealdb';

const now = DateTime.now();
const parsed = DateTime.parse('2024-01-15T12:00:00.123456789Z');
const jsDate = now.toDate();
const iso = now.toString();

const duration = Duration.parse('1h30m45s');
const ms = duration.toMilliseconds();
const seconds = duration.toSeconds();
```

## Decimal

A [`Decimal`](/docs/reference/javascript/api/values/decimal.md) holds a decimal without floating-point rounding. Construct it from a string when precision matters.

```ts
import { Decimal } from 'surrealdb';

const price = new Decimal('19.99');
const display = price.toString();
const number = price.toNumber();
```

> [!NOTE]
> Converting a `Decimal` to a `number` with `.toNumber()` may lose precision. Use `.toString()` when precision matters.

## Uuid

A [`Uuid`](/docs/reference/javascript/api/values/uuid.md) represents a universally unique identifier. The class supports generating both v4 (random) and v7 (time-ordered) UUIDs.

```ts
import { Uuid } from 'surrealdb';

const random = Uuid.v4();
const timeOrdered = Uuid.v7();
const parsed = Uuid.parse('550e8400-e29b-41d4-a716-446655440000');
```

## Range

A [`Range`](/docs/reference/javascript/api/values/range.md) is a bounded or open-ended span of values. In SurrealQL you use ranges to slice record IDs or filter numbers and times. `RecordIdRange` is the variant for a table ID range.

```ts
import { Range, RecordIdRange } from 'surrealdb';

const numericRange = new Range(1, 10);

const idRange = new RecordIdRange('users', { begin: 'a', end: 'f' });
const slice = await db.select(idRange);
```

## FileRef

A `FileRef` points at a file stored in SurrealDB. You see one when working with [file uploads](/docs/reference/query-language/language-primitives/data-types/files.md); it carries bucket and key metadata.

```ts
const [record] = await db.query('SELECT avatar FROM user:john');

if (record.avatar instanceof FileRef) {
    console.log(record.avatar.bucket);
    console.log(record.avatar.key);
}
```

## Geometry types

The SDK provides classes for all [GeoJSON geometry types](/docs/reference/query-language/language-primitives/data-types/geometries.md): `GeometryPoint`, `GeometryLine`, `GeometryPolygon`, `GeometryMultiPoint`, `GeometryMultiLine`, `GeometryMultiPolygon`, and `GeometryCollection`.

```ts
import { GeometryPoint, GeometryLine, GeometryPolygon } from 'surrealdb';

const point = new GeometryPoint([longitude, latitude]);
const line = new GeometryLine([
    new GeometryPoint([1, 2]),
    new GeometryPoint([3, 4]),
]);
```

## Parsing from strings

Most classes accept the same string forms as SurrealQL. Invalid input throws.

```ts
const recordId = RecordId.parse('users:john');
const datetime = DateTime.parse('2024-01-15T12:00:00Z');
const duration = Duration.parse('1h30m45s');
const uuid = Uuid.parse('550e8400-e29b-41d4-a716-446655440000');
```

## Using native dates

By default, datetimes come back as `DateTime`. Set `useNativeDates` in [codec options](/docs/reference/javascript/concepts/codecs.md#codec-options) if you would rather work with `Date` and can accept millisecond precision.

```ts
const db = new Surreal({
    codecOptions: {
        useNativeDates: true,
    },
});
```

## String prefixes

The `surrealdb` package includes tagged templates that mirror SurrealQL's `s`, `d`, `r`, and `u` prefixes. They are not part of `@surrealdb/sqon`.

```ts
import { s, d, r, u } from 'surrealdb';

const string = s`I am a string`;
const date = d`2024-05-06T17:44:57.085Z`;
const record = r`person:tobie`;
const uuid = u`92b84bde-39c8-4b4b-92f7-626096d6c4d9`;
```

## Best practices

### Use type parameters for type-safe queries

```ts
const users = new Table<User>('users');
const results: User[] = await db.select(users);

const userId = new RecordId<'users', string>('users', 'john');
```

### Prefer value classes over raw strings

```ts
await db.select(new RecordId('users', 'john'));

// Avoid string-based queries when possible
await db.query('SELECT * FROM users:john');
```

### Validate parsed input

```ts
try {
    const uuid = Uuid.parse(userInput);
} catch (error) {
    console.error('Invalid UUID format');
}
```

## Learn more

- [Data types API reference](/docs/reference/javascript/api/values/) for the full list of value class documentation
- [Codecs](/docs/reference/javascript/concepts/codecs.md) for serialising and deserialising value types
- [SurrealQL data model](/docs/reference/query-language/language-primitives/data-types.md) for the database-level type system
- [Utilities](/docs/reference/javascript/concepts/utilities.md) for comparing and converting values

---

Source: https://surrealdb.com/docs/reference/javascript/engines/node

# Node.js

The SurrealDB SDK for JavaScript using the Node.js engine.

The `@surrealdb/node` package is a plugin for the [JavaScript SDK](/docs/reference/javascript/installation.md) that runs SurrealDB as an embedded database within Node.js, Bun, or Deno. It supports in-memory databases and persistent storage via RocksDB and SurrealKV.

> [!IMPORTANT]
> This package works with ES modules (`import`), not CommonJS (`require`).

## Installation

First, [install the JavaScript SDK](/docs/reference/javascript/installation.md) if you haven't already. Then add the Node.js engine plugin and the SurrealDB engine it runs:

**npm**

```bash
npm install --save @surrealdb/node @surrealdb/node-native
```

**yarn**

```bash
yarn add @surrealdb/node @surrealdb/node-native
```

**pnpm**

```bash
pnpm install @surrealdb/node @surrealdb/node-native
```

### Choosing the engine version

From `@surrealdb/node` 3.0.4, the SurrealDB engine is a separate package, `@surrealdb/node-native`, which the plugin declares as a peer dependency. The engine version is independent of the plugin version, so you choose which SurrealDB release runs in your process.

To run a specific release, install it by version:

```bash
npm install --save @surrealdb/node @surrealdb/node-native@3.3.2
```

The result in `package.json` looks like this:

```json title="package.json"
{
    "dependencies": {
        "surrealdb": "^2.0.1",
        "@surrealdb/node": "^3.0.4",
        "@surrealdb/node-native": "3.3.2"
    }
}
```

An exact version gives the same engine on every install. A range such as `^3.3.2` follows new engine releases when you update your dependencies.

If you do not install the engine yourself, npm 7 or later, pnpm and Bun add the newest release that satisfies the peer range. Two installs of the same `@surrealdb/node` version can then run different engines. To make every install match, pin the engine version or commit your lockfile. Yarn does not install peer dependencies, so add `@surrealdb/node-native` explicitly.

Only engine versions inside the peer range of `@surrealdb/node` are supported. A version outside the range causes a peer dependency conflict in your package manager.

To confirm which engine is loaded, for example when you report a bug, call `engineVersion()`:

```ts
import { engineVersion } from '@surrealdb/node';

console.log(engineVersion());
```

```text title="Sample output"
3.3.2
```

## Quick start

```ts
import { Surreal, createRemoteEngines } from 'surrealdb';
import { createNodeEngines } from '@surrealdb/node';

const db = new Surreal({
    engines: {
        ...createRemoteEngines(),
        ...createNodeEngines(),
    },
});

await db.connect('mem://');

// Always close the connection when done
await db.close();
```

## Learn more

- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for engine registration, embedded protocols, and connection options
- [`@surrealdb/node` on npm](https://npmjs.com/package/@surrealdb/node)

---

Source: https://surrealdb.com/docs/reference/javascript/engines/wasm

# WebAssembly

The SurrealDB SDK for JavaScript using the WebAssembly engine.

The `@surrealdb/wasm` package is a plugin for the [JavaScript SDK](/docs/reference/javascript/installation.md) that runs SurrealDB as an embedded database within a browser environment. It supports in-memory databases and persistent storage via IndexedDB, and can optionally run inside a Web Worker.

> [!IMPORTANT]
> This package works with ES modules (`import`), not CommonJS (`require`).

## Installation

First, [install the JavaScript SDK](/docs/reference/javascript/installation.md) if you haven't already. Then add the WASM engine plugin and the SurrealDB engine it runs:

**npm**

```bash
npm install --save @surrealdb/wasm @surrealdb/wasm-native
```

**yarn**

```bash
yarn add @surrealdb/wasm @surrealdb/wasm-native
```

**pnpm**

```bash
pnpm install @surrealdb/wasm @surrealdb/wasm-native
```

### Choosing the engine version

From `@surrealdb/wasm` 3.0.4, the SurrealDB engine is a separate package, `@surrealdb/wasm-native`, which the plugin declares as a peer dependency. The engine version is independent of the plugin version, so you choose which SurrealDB release runs in your process.

To run a specific release, install it by version:

```bash
npm install --save @surrealdb/wasm @surrealdb/wasm-native@3.3.2
```

The result in `package.json` looks like this:

```json title="package.json"
{
    "dependencies": {
        "surrealdb": "^2.0.1",
        "@surrealdb/wasm": "^3.0.4",
        "@surrealdb/wasm-native": "3.3.2"
    }
}
```

An exact version gives the same engine on every install. A range such as `^3.3.2` follows new engine releases when you update your dependencies.

If you do not install the engine yourself, npm 7 or later, pnpm and Bun add the newest release that satisfies the peer range. Two installs of the same `@surrealdb/wasm` version can then run different engines. To make every install match, pin the engine version or commit your lockfile. Yarn does not install peer dependencies, so add `@surrealdb/wasm-native` explicitly.

Only engine versions inside the peer range of `@surrealdb/wasm` are supported. A version outside the range causes a peer dependency conflict in your package manager.

To confirm which engine is installed, for example when you report a bug, list the package:

```bash
npm ls @surrealdb/wasm-native
```

The WebAssembly plugin has no `engineVersion()` function, because the engine must be initialised before it can report its version.

## Quick start

```ts
import { Surreal, createRemoteEngines } from 'surrealdb';
import { createWasmEngines } from '@surrealdb/wasm';

const db = new Surreal({
    engines: {
        ...createRemoteEngines(),
        ...createWasmEngines(),
    },
});

await db.connect('mem://');
```

## Learn more

- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for engine registration, embedded protocols, and connection options
- [`@surrealdb/wasm` on npm](https://npmjs.com/package/@surrealdb/wasm)

---

Source: https://surrealdb.com/docs/reference/javascript/frameworks/expo

# Expo

The SurrealDB SDK for JavaScript can be used in Expo applications to connect to a remote SurrealDB instance from Android and iOS.

[Expo](https://expo.dev/) is a framework for building React Native applications for Android, iOS, and the web. The SurrealDB SDK for JavaScript runs inside Expo apps and connects to a remote SurrealDB instance over WebSocket or HTTP.

This guide walks you through setting up a connection provider, storing session tokens securely, and handling the mobile app lifecycle in an Expo project.

## What is different on mobile

The SDK surface is the same one used in [React](/docs/reference/javascript/frameworks/react.md) applications, but three constraints apply on Android and iOS that do not apply in a browser.

- **Embedded engines are not supported.** The [WebAssembly engine](/docs/reference/javascript/engines/wasm.md) needs a WebAssembly runtime, which Hermes does not provide. The [Node.js engine](/docs/reference/javascript/engines/node.md) is a native Node addon and cannot be loaded by React Native. Every connection from an Expo app is a remote connection.
- **Your JavaScript bundle ships to the device.** Anyone who installs the app can read the values you compile into it. Use [record access](/docs/reference/query-language/statements/define/access/record.md) for end users, and keep system user credentials on a server.
- **The connection does not survive backgrounding.** Both platforms suspend your process shortly after the user leaves the app, which closes the socket. You reconnect when the app returns to the foreground.

## Prerequisites

- A basic understanding of React and Expo
- An Expo project on SDK 54 or newer, using [Expo Router](https://docs.expo.dev/router/introduction/)
- A running [SurrealDB instance](/docs/running/overview.md) that the device can reach
- The [JavaScript SDK installed](/docs/reference/javascript/installation.md) in your project

## Supported connection protocols

| Protocol | Supported | Notes |
|----------|-----------|-------|
| `wss://` | Yes | Long-lived connection. Required for [live queries](/docs/reference/javascript/concepts/live-queries.md). |
| `https://` | Yes | Stateless requests. No live queries. |
| `ws://`, `http://` | Development only | Blocked by default on both platforms. See [reaching your database from a device](#reaching-your-database-from-a-device). |

Use `wss://` unless you only need occasional one-off requests. A WebSocket connection keeps the session authenticated between calls and is the only protocol that supports live queries.

> [!NOTE]
> The SDK needs `TextEncoder`, `TextDecoder`, `URL`, and `URLSearchParams` at runtime. The `expo` package installs all of them as globals on Android and iOS, so no polyfills are required. A bare React Native project has to add them itself - see the [React Native guide](/docs/reference/javascript/frameworks/react-native.md#installing-the-required-polyfills).

## Installing dependencies

In addition to `surrealdb`, this guide uses [@tanstack/react-query](https://tanstack.com/query/latest) to manage the asynchronous connection state, and [expo-secure-store](https://docs.expo.dev/versions/latest/sdk/securestore/) to keep session tokens in the platform keystore.

```bash
npx expo install surrealdb @tanstack/react-query expo-secure-store
```

Use `npx expo install` rather than your package manager directly. It picks dependency versions that match your Expo SDK. Follow the [installation guide](/docs/reference/javascript/installation.md) for more information on how to install the SDK in your project.

## Reaching your database from a device

On a device or emulator, `localhost` points at the device itself, not at your development machine. Set the endpoint according to where the app runs.

| Where the app runs | Host to use |
|--------------------|-------------|
| iOS simulator | `127.0.0.1` |
| Android emulator | `10.0.2.2` |
| Physical device | Your machine's LAN address, for example `192.168.1.24` |
| Production | Your deployed hostname over `wss://` |

Android blocks cleartext traffic from API level 28, and iOS blocks it through App Transport Security. To connect to a plain `ws://` endpoint during development, add the following to **app.json** and rebuild.

```json title="app.json"
{
    "expo": {
        "ios": {
            "infoPlist": {
                "NSAppTransportSecurity": {
                    "NSAllowsLocalNetworking": true
                }
            }
        },
        "plugins": [
            [
                "expo-build-properties",
                {
                    "android": {
                        "usesCleartextTraffic": true
                    }
                }
            ]
        ]
    }
}
```

> [!WARNING]
> Both settings weaken transport security for the whole app. Apply them to a development build only, and connect over `wss://` in the builds you ship.

## Creating the connection provider

Initialise the SDK in a [Context Provider](https://react.dev/learn/passing-data-deeply-with-context) so the `Surreal` client is available anywhere in your component tree. The provider below manages the connection lifecycle, tracks connection status through TanStack Query, closes the socket when the app moves to the background, and reconnects when it becomes active again.

The `params` prop accepts the same options as [`.connect()`](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-options), including `namespace`, `database`, and [`authentication`](/docs/reference/javascript/concepts/authentication.md#providing-credentials-on-connect).

```tsx title="surreal-provider.tsx"
import { Surreal } from "surrealdb";
import { useMutation } from "@tanstack/react-query";
import { AppState } from "react-native";
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";

interface SurrealProviderProps {
    children: React.ReactNode;
    endpoint: string;
    client?: Surreal;
    params?: Parameters<Surreal["connect"]>[1];
}

interface SurrealProviderState {
    client: Surreal;
    isConnecting: boolean;
    isSuccess: boolean;
    isError: boolean;
    error: unknown;
    connect: () => Promise<true>;
    close: () => Promise<true>;
}

const SurrealContext = createContext<SurrealProviderState | undefined>(undefined);

export function SurrealProvider({ children, client, endpoint, params }: SurrealProviderProps) {
    const [instance] = useState(() => client ?? new Surreal());

    const {
        mutateAsync: connectMutation,
        isPending,
        isSuccess,
        isError,
        error,
        reset,
    } = useMutation({
        mutationFn: () => instance.connect(endpoint, params),
    });

    const connect = useCallback(() => connectMutation(), [connectMutation]);
    const close = useCallback(() => instance.close(), [instance]);

    useEffect(() => {
        connect();

        return () => {
            reset();
            instance.close();
        };
    }, [connect, reset, instance]);

    useEffect(() => {
        const subscription = AppState.addEventListener("change", (state) => {
            if (state === "active" && instance.status === "disconnected") {
                connect();
            } else if (state === "background") {
                instance.close();
            }
        });

        return () => subscription.remove();
    }, [instance, connect]);

    const value: SurrealProviderState = useMemo(
        () => ({ client: instance, isConnecting: isPending, isSuccess, isError, error, connect, close }),
        [instance, isPending, isSuccess, isError, error, connect, close],
    );

    return <SurrealContext.Provider value={value}>{children}</SurrealContext.Provider>;
}

export function useSurreal() {
    const context = useContext(SurrealContext);
    if (!context) throw new Error("useSurreal must be used within a SurrealProvider");
    return context;
}

export function useSurrealClient() {
    return useSurreal().client;
}
```

The handler closes the connection on `background` but ignores `inactive`. On iOS, `inactive` also fires for the app switcher and for incoming calls, which are usually too short to be worth dropping the socket.

## Wrapping your application

Expo Router renders `app/_layout.tsx` around every route, which makes it the place to mount providers. Wrap the navigation stack with `QueryClientProvider` and `SurrealProvider`.

```tsx title="app/_layout.tsx"
import { Stack } from "expo-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { SurrealProvider } from "../surreal-provider";

const queryClient = new QueryClient();

export default function RootLayout() {
    return (
        <QueryClientProvider client={queryClient}>
            <SurrealProvider
                endpoint={process.env.EXPO_PUBLIC_SURREAL_ENDPOINT!}
                params={{
                    namespace: "surrealdb",
                    database: "docs",
                }}
            >
                <Stack />
            </SurrealProvider>
        </QueryClientProvider>
    );
}
```

Environment variables prefixed with `EXPO_PUBLIC_` are inlined into the bundle at build time, so use them for the endpoint and never for credentials.

```bash title=".env"
EXPO_PUBLIC_SURREAL_ENDPOINT=ws://10.0.2.2:8000
```

## Executing queries

Use the `useSurrealClient()` hook to reach the `Surreal` instance from any component. All [query methods](/docs/reference/javascript/concepts/executing-queries.md) are available on the client, including `.query()`, `.select()`, and `.create()`.

Gate the query on `isSuccess` so it runs once the connection is open, and again after each reconnect.

```tsx title="app/index.tsx"
import { useEffect, useState } from "react";
import { ActivityIndicator, FlatList, Text, View } from "react-native";
import { Table } from "surrealdb";
import { useSurreal } from "../surreal-provider";

interface User {
    id: string;
    name: string;
    email: string;
}

export default function UserList() {
    const { client, isConnecting, isSuccess, isError, error } = useSurreal();
    const [users, setUsers] = useState<User[]>([]);

    useEffect(() => {
        if (!isSuccess) return;

        client.select<User>(new Table("users"))
            .then(setUsers)
            .catch(console.error);
    }, [client, isSuccess]);

    if (isConnecting) return <ActivityIndicator />;
    if (isError) return <Text>Connection failed: {String(error)}</Text>;

    return (
        <FlatList
            data={users}
            keyExtractor={(user) => String(user.id)}
            renderItem={({ item }) => (
                <View>
                    <Text>{item.name}</Text>
                    <Text>{item.email}</Text>
                </View>
            )}
        />
    );
}
```

## Subscribing to live queries

[Live queries](/docs/reference/javascript/concepts/live-queries.md) push changes to the device as they happen, which removes the need to poll on a metered connection. They require a WebSocket connection.

Because the provider closes the socket on background, tie the subscription to `isSuccess` as well. The effect then recreates the subscription every time the connection reopens.

```tsx
import { useEffect, useState } from "react";
import { Table } from "surrealdb";
import { useSurreal } from "../surreal-provider";

interface Message {
    id: string;
    body: string;
}

export function useLiveMessages() {
    const { client, isSuccess } = useSurreal();
    const [messages, setMessages] = useState<Message[]>([]);

    useEffect(() => {
        if (!isSuccess) return;

        const pending = client.live<Message>(new Table("messages"));

        pending
            .then((live) => {
                live.subscribe((action, result) => {
                    if (action === "CREATE") {
                        setMessages((current) => [...current, result]);
                    }
                });
            })
            .catch(console.error);

        return () => {
            pending.then((live) => live.kill()).catch(() => {});
        };
    }, [client, isSuccess]);

    return messages;
}
```

## Handling authentication

Sign users in with [record access](/docs/reference/query-language/statements/define/access/record.md) and keep the resulting tokens in the keystore, so the session survives an app restart.

```tsx title="use-auth.ts"
import * as SecureStore from "expo-secure-store";
import { useEffect } from "react";
import { useSurreal } from "./surreal-provider";

export const ACCESS_KEY = "surreal.access";
export const REFRESH_KEY = "surreal.refresh";

export function useAuth() {
    const { client } = useSurreal();

    useEffect(() => {
        return client.subscribe("auth", async (tokens) => {
            if (tokens) {
                await SecureStore.setItemAsync(ACCESS_KEY, tokens.access);
                if (tokens.refresh) await SecureStore.setItemAsync(REFRESH_KEY, tokens.refresh);
            } else {
                await SecureStore.deleteItemAsync(ACCESS_KEY);
                await SecureStore.deleteItemAsync(REFRESH_KEY);
            }
        });
    }, [client]);

    async function login(email: string, password: string) {
        return client.signin({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function register(email: string, password: string) {
        return client.signup({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function logout() {
        return client.invalidate();
    }

    return { login, register, logout };
}
```

The `auth` event fires on sign in, sign up, token refresh, and invalidation, and `.subscribe()` returns the function that removes the listener.

### Restoring a session on launch

Pass a function to the `authentication` connection option. The SDK calls it while opening the connection and again whenever it needs to re-authenticate after a reconnect. `SecureStore.getItemAsync()` returns `string | null`, which is exactly what the option expects.

```tsx title="app/_layout.tsx"
import * as SecureStore from "expo-secure-store";
import { ACCESS_KEY } from "../use-auth";

<SurrealProvider
    endpoint={process.env.EXPO_PUBLIC_SURREAL_ENDPOINT!}
    params={{
        namespace: "surrealdb",
        database: "docs",
        authentication: () => SecureStore.getItemAsync(ACCESS_KEY),
    }}
>
```

If the stored access token has expired and you also hold a refresh token, exchange the pair with [`.authenticate()`](/docs/reference/javascript/concepts/authentication.md#authenticating-with-an-existing-token) instead.

```ts
const access = await SecureStore.getItemAsync(ACCESS_KEY);
const refresh = await SecureStore.getItemAsync(REFRESH_KEY);

if (access) {
    await client.authenticate(refresh ? { access, refresh } : access);
}
```

> [!IMPORTANT]
> Once you call `.signin()`, `.signup()`, or `.authenticate()`, the `authentication` connection option is ignored for the rest of that session. Choose one of the two approaches per session rather than mixing them.

> [!NOTE]
> `expo-secure-store` is backed by the iOS keychain and by Android's keystore. iOS rejects values above roughly 2048 bytes, so store the access and refresh tokens under separate keys rather than as one JSON object.

## Troubleshooting

| Symptom | Cause |
|---------|-------|
| `Network request failed` on Android, works in the browser | The endpoint uses `localhost`. Use `10.0.2.2` on the emulator or the LAN address on a device. |
| Connection hangs, then fails with no server log entry | Cleartext traffic is blocked. Switch to `wss://` or apply the [development configuration](#reaching-your-database-from-a-device). |
| `Unable to resolve module node:util` | Metro resolved the SDK's server build. Remove `node` from `unstable_conditionNames` in **metro.config.js**. |
| Queries fail after the app returns from the background | The query ran before the socket reopened. Gate it on `isSuccess` from the provider. |
| Live query stops delivering after backgrounding | The subscription was created against the closed connection. Recreate it when `isSuccess` becomes true again. |

## Learn more

- [React Native](/docs/reference/javascript/frameworks/react-native.md) for the same setup without Expo, including the polyfills you must add yourself
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for connection protocols and reconnection behaviour
- [Authentication](/docs/reference/javascript/concepts/authentication.md) for signing in, signing up, and token management
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) for query builders and raw SurrealQL
- [Live queries](/docs/reference/javascript/concepts/live-queries.md) for real-time subscriptions
- [JavaScript SDK API reference](/docs/reference/javascript/api/) for the complete method reference

---

Source: https://surrealdb.com/docs/reference/javascript/frameworks/react

# React

The SurrealDB SDK for JavaScript can be used in React applications to interact with your SurrealDB instance.

[React](https://react.dev/) is a popular JavaScript library for building user interfaces. The SurrealDB SDK for JavaScript can be used in your React applications to interact with your SurrealDB instance.

This guide walks you through setting up a connection provider and executing queries in a React project.

## Prerequisites

- A basic understanding of React
- A running [SurrealDB instance](/docs/running/overview.md)
- The [JavaScript SDK installed](/docs/reference/javascript/installation.md) in your project

## Installing dependencies

In addition to `surrealdb`, this guide uses [@tanstack/react-query](https://tanstack.com/query/latest) to manage the asynchronous connection state. Install it alongside the SDK:

**npm**

```bash
npm install --save surrealdb @tanstack/react-query
```

**yarn**

```bash
yarn add surrealdb @tanstack/react-query
```

**pnpm**

```bash
pnpm install surrealdb @tanstack/react-query
```

Follow the [installation guide](/docs/reference/javascript/installation.md) for more information on how to install the SDK in your project.

## Creating the connection provider

We recommend initialising the SDK in a [Context Provider](https://react.dev/learn/passing-data-deeply-with-context) so the `Surreal` client is accessible anywhere in your component tree. The provider below manages the connection lifecycle, tracks connection status via TanStack Query, and cleans up on unmount.

The `params` prop accepts the same options as [`.connect()`](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-options), including `namespace`, `database`, and [`authentication`](/docs/reference/javascript/concepts/authentication.md#providing-credentials-on-connect).

```tsx
import { Surreal } from "surrealdb";
import { useMutation } from "@tanstack/react-query";
import React, { createContext, useContext, useEffect, useMemo, useCallback, useState } from "react";

interface SurrealProviderProps {
    children: React.ReactNode;
    endpoint: string;
    client?: Surreal;
    params?: Parameters<Surreal["connect"]>[1];
    autoConnect?: boolean;
}

interface SurrealProviderState {
    client: Surreal;
    isConnecting: boolean;
    isSuccess: boolean;
    isError: boolean;
    error: unknown;
    connect: () => Promise<true>;
    close: () => Promise<true>;
}

const SurrealContext = createContext<SurrealProviderState | undefined>(undefined);

export function SurrealProvider({
    children,
    client,
    endpoint,
    params,
    autoConnect = true,
}: SurrealProviderProps) {
    const [instance] = useState(() => client ?? new Surreal());

    const {
        mutateAsync: connectMutation,
        isPending,
        isSuccess,
        isError,
        error,
        reset,
    } = useMutation({
        mutationFn: () => instance.connect(endpoint, params),
    });

    const connect = useCallback(() => connectMutation(), [connectMutation]);
    const close = useCallback(() => instance.close(), [instance]);

    useEffect(() => {
        if (autoConnect) connect();

        return () => {
            reset();
            instance.close();
        };
    }, [autoConnect, connect, reset, instance]);

    const value: SurrealProviderState = useMemo(
        () => ({ client: instance, isConnecting: isPending, isSuccess, isError, error, connect, close }),
        [instance, isPending, isSuccess, isError, error, connect, close],
    );

    return <SurrealContext.Provider value={value}>{children}</SurrealContext.Provider>;
}

export function useSurreal() {
    const context = useContext(SurrealContext);
    if (!context) throw new Error("useSurreal must be used within a SurrealProvider");
    return context;
}

export function useSurrealClient() {
    return useSurreal().client;
}
```

## Wrapping your application

In your top-level component, wrap the root with `QueryClientProvider` and `SurrealProvider`. Pass the endpoint and any [connection options](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-options) through the `params` prop.

```tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { SurrealProvider } from "./SurrealProvider";
import App from "./App";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")!).render(
    <React.StrictMode>
        <QueryClientProvider client={queryClient}>
            <SurrealProvider
                endpoint="ws://127.0.0.1:8000"
                params={{
                    namespace: "surrealdb",
                    database: "docs",
                    authentication: {
                        username: "root",
                        password: "secret",
                    },
                }}
            >
                <App />
            </SurrealProvider>
        </QueryClientProvider>
    </React.StrictMode>,
);
```

## Executing queries

Use the `useSurrealClient()` hook to access the `Surreal` instance from any component. All [query methods](/docs/reference/javascript/concepts/executing-queries.md) are available on the client, including `.query()`, `.select()`, `.create()`, and more.

```tsx
import { useState, useEffect } from "react";
import { Table } from "surrealdb";
import { useSurreal, useSurrealClient } from "./SurrealProvider";

interface User {
    id: string;
    name: string;
    email: string;
}

export function UserList() {
    const { isConnecting, isError, error } = useSurreal();
    const client = useSurrealClient();
    const [users, setUsers] = useState<User[]>([]);

    useEffect(() => {
        client.select<User>(new Table("users"))
            .then(setUsers)
            .catch(console.error);
    }, [client]);

    if (isConnecting) return <p>Connecting...</p>;
    if (isError) return <p>Connection failed: {String(error)}</p>;

    return (
        <ul>
            {users.map((user) => (
                <li key={String(user.id)}>{user.name} ({user.email})</li>
            ))}
        </ul>
    );
}
```

## Handling authentication

You can build an authentication layer on top of the provider using the SDK's [`.signin()`](/docs/reference/javascript/concepts/authentication.md#signing-in-users) and [`.signup()`](/docs/reference/javascript/concepts/authentication.md#signing-up-users) methods. The example below shows a minimal hook for record access authentication.

```tsx
import { useSurrealClient } from "./SurrealProvider";

export function useAuth() {
    const client = useSurrealClient();

    async function login(email: string, password: string) {
        return client.signin({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function register(email: string, password: string) {
        return client.signup({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function logout() {
        return client.invalidate();
    }

    return { login, register, logout };
}
```

## Learn more

- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for connection protocols and reconnection behaviour
- [Authentication](/docs/reference/javascript/concepts/authentication.md) for signing in, signing up, and token management
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) for query builders and raw SurrealQL
- [Live queries](/docs/reference/javascript/concepts/live-queries.md) for real-time subscriptions
- [JavaScript SDK API reference](/docs/reference/javascript/api/) for the complete method reference

---

Source: https://surrealdb.com/docs/reference/javascript/frameworks/react-native

# React Native

The SurrealDB SDK for JavaScript can be used in React Native applications to connect to a remote SurrealDB instance from Android and iOS.

[React Native](https://reactnative.dev/) builds native Android and iOS applications from React components. The SurrealDB SDK for JavaScript runs inside React Native apps and connects to a remote SurrealDB instance over WebSocket or HTTP.

This guide covers a bare React Native project, created with the React Native Community CLI. If your project uses [Expo](/docs/reference/javascript/frameworks/expo.md), follow that guide instead: Expo supplies several of the runtime APIs that you otherwise have to install yourself.

## What is different on mobile

The SDK surface is the same one used in [React](/docs/reference/javascript/frameworks/react.md) applications, but four constraints apply on Android and iOS that do not apply in a browser.

- **Embedded engines are not supported.** The [WebAssembly engine](/docs/reference/javascript/engines/wasm.md) needs a WebAssembly runtime, which Hermes does not provide. The [Node.js engine](/docs/reference/javascript/engines/node.md) is a native Node addon and cannot be loaded by React Native. Every connection from a React Native app is a remote connection.
- **The JavaScript engine is not a browser.** Hermes leaves out several web APIs that the SDK depends on. You install them as polyfills before the SDK loads.
- **Your JavaScript bundle ships to the device.** Anyone who installs the app can read the values you compile into it. Use [record access](/docs/reference/query-language/statements/define/access/record.md) for end users, and keep system user credentials on a server.
- **The connection does not survive backgrounding.** Both platforms suspend your process shortly after the user leaves the app, which closes the socket. You reconnect when the app returns to the foreground.

## Prerequisites

- A basic understanding of React and React Native
- A React Native project on 0.79 or newer
- A running [SurrealDB instance](/docs/running/overview.md) that the device can reach
- The [JavaScript SDK installed](/docs/reference/javascript/installation.md) in your project

## Supported connection protocols

| Protocol | Supported | Notes |
|----------|-----------|-------|
| `wss://` | Yes | Long-lived connection. Required for [live queries](/docs/reference/javascript/concepts/live-queries.md). |
| `https://` | Yes | Stateless requests. No live queries. Needs the `ReadableStream` global. |
| `ws://`, `http://` | Development only | Blocked by default on both platforms. See [reaching your database from a device](#reaching-your-database-from-a-device). |

Use `wss://` unless you only need occasional one-off requests. A WebSocket connection keeps the session authenticated between calls and is the only protocol that supports live queries.

## Installing the required polyfills

Hermes ships `TextEncoder`, but not `TextDecoder`. React Native ships a reduced `URL` implementation that omits properties the SDK reads, such as `protocol` and `pathname`. Install both, along with a secure random source.

```bash
npm install --save surrealdb react-native-url-polyfill @bacons/text-decoder react-native-get-random-values
```

Import them at the very top of your entry file, before any other import. Metro evaluates imports in order, and the SDK constructs a `TextDecoder` when its module is first loaded.

```js title="index.js"
import "react-native-get-random-values";
import "react-native-url-polyfill/auto";
import "@bacons/text-decoder/install";

import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";

AppRegistry.registerComponent(appName, () => App);
```

`react-native-get-random-values` is a native module, so run `npx pod-install` and rebuild the app after installing it.

| Global | Available by default | Polyfill | Needed for |
|--------|----------------------|----------|------------|
| `TextEncoder` | Yes, from Hermes | - | CBOR encoding |
| `TextDecoder` | No | `@bacons/text-decoder` | CBOR decoding |
| `URL`, `URLSearchParams` | Partly, from React Native | `react-native-url-polyfill` | Parsing the endpoint |
| `crypto.getRandomValues` | No | `react-native-get-random-values` | `Uuid.v4()` and `Uuid.v7()` |
| `ReadableStream` | No | `web-streams-polyfill` | HTTP connections only |

The SDK falls back to `Math.random()` when `crypto.getRandomValues` is missing, so UUIDs are still generated but are not cryptographically random. Install the polyfill if your app creates `Uuid` values on the device.

To confirm the polyfills are in place, log the globals once during startup:

```ts
for (const name of ["TextEncoder", "TextDecoder", "URL", "URLSearchParams"]) {
    if (!(name in globalThis)) console.warn(`Missing global: ${name}`);
}
```

> [!NOTE]
> If you connect over `https://` rather than `wss://`, add `web-streams-polyfill` as well and import `web-streams-polyfill/polyfill` alongside the others. The HTTP engine checks the request body against `ReadableStream`, which throws a `ReferenceError` when the global is undefined.

## Installing the remaining dependencies

This guide uses [@tanstack/react-query](https://tanstack.com/query/latest) to manage the asynchronous connection state, and [react-native-keychain](https://github.com/oblador/react-native-keychain) to keep session tokens in the platform keystore.

```bash
npm install --save @tanstack/react-query react-native-keychain
```

Follow the [installation guide](/docs/reference/javascript/installation.md) for more information on how to install the SDK in your project.

## Reaching your database from a device

On a device or emulator, `localhost` points at the device itself, not at your development machine. Set the endpoint according to where the app runs.

| Where the app runs | Host to use |
|--------------------|-------------|
| iOS simulator | `127.0.0.1` |
| Android emulator | `10.0.2.2` |
| Physical device | Your machine's LAN address, for example `192.168.1.24` |
| Production | Your deployed hostname over `wss://` |

Android blocks cleartext traffic from API level 28, and iOS blocks it through App Transport Security. A project created from the React Native template already carries the exceptions needed to develop against a local endpoint, so `ws://` works in a debug build without further setup.

**iOS**

The template's `Info.plist` sets `NSAllowsLocalNetworking`, which permits connections to local addresses:

```xml title="ios/<YourApp>/Info.plist"
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>
```

For a host that is not on the local network, add an entry under `NSExceptionDomains` with `NSExceptionAllowsInsecureHTTPLoads` rather than widening the policy.

**Android**

The template's manifest resolves `usesCleartextTraffic` from a build-type placeholder, which the React Native Gradle plugin sets to `true` for debug builds and `false` for release builds:

```xml title="android/app/src/main/AndroidManifest.xml"
<application
    android:usesCleartextTraffic="${usesCleartextTraffic}"
    ...>
```

> [!WARNING]
> Neither exception applies to a release build, and neither should be widened to cover one. Connect over `wss://` in the builds you ship.

## Creating the connection provider

Initialise the SDK in a [Context Provider](https://react.dev/learn/passing-data-deeply-with-context) so the `Surreal` client is available anywhere in your component tree. The provider below manages the connection lifecycle, tracks connection status through TanStack Query, closes the socket when the app moves to the background, and reconnects when it becomes active again.

The `params` prop accepts the same options as [`.connect()`](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-options), including `namespace`, `database`, and [`authentication`](/docs/reference/javascript/concepts/authentication.md#providing-credentials-on-connect).

```tsx title="SurrealProvider.tsx"
import { Surreal } from "surrealdb";
import { useMutation } from "@tanstack/react-query";
import { AppState } from "react-native";
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";

interface SurrealProviderProps {
    children: React.ReactNode;
    endpoint: string;
    client?: Surreal;
    params?: Parameters<Surreal["connect"]>[1];
}

interface SurrealProviderState {
    client: Surreal;
    isConnecting: boolean;
    isSuccess: boolean;
    isError: boolean;
    error: unknown;
    connect: () => Promise<true>;
    close: () => Promise<true>;
}

const SurrealContext = createContext<SurrealProviderState | undefined>(undefined);

export function SurrealProvider({ children, client, endpoint, params }: SurrealProviderProps) {
    const [instance] = useState(() => client ?? new Surreal());

    const {
        mutateAsync: connectMutation,
        isPending,
        isSuccess,
        isError,
        error,
        reset,
    } = useMutation({
        mutationFn: () => instance.connect(endpoint, params),
    });

    const connect = useCallback(() => connectMutation(), [connectMutation]);
    const close = useCallback(() => instance.close(), [instance]);

    useEffect(() => {
        connect();

        return () => {
            reset();
            instance.close();
        };
    }, [connect, reset, instance]);

    useEffect(() => {
        const subscription = AppState.addEventListener("change", (state) => {
            if (state === "active" && instance.status === "disconnected") {
                connect();
            } else if (state === "background") {
                instance.close();
            }
        });

        return () => subscription.remove();
    }, [instance, connect]);

    const value: SurrealProviderState = useMemo(
        () => ({ client: instance, isConnecting: isPending, isSuccess, isError, error, connect, close }),
        [instance, isPending, isSuccess, isError, error, connect, close],
    );

    return <SurrealContext.Provider value={value}>{children}</SurrealContext.Provider>;
}

export function useSurreal() {
    const context = useContext(SurrealContext);
    if (!context) throw new Error("useSurreal must be used within a SurrealProvider");
    return context;
}

export function useSurrealClient() {
    return useSurreal().client;
}
```

The handler closes the connection on `background` but ignores `inactive`. On iOS, `inactive` also fires for the app switcher and for incoming calls, which are usually too short to be worth dropping the socket.

## Wrapping your application

Mount the providers in `App.tsx`, above your navigation container.

```tsx title="App.tsx"
import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { SurrealProvider } from "./SurrealProvider";
import { UserList } from "./UserList";

const queryClient = new QueryClient();

export default function App() {
    return (
        <QueryClientProvider client={queryClient}>
            <SurrealProvider
                endpoint="ws://10.0.2.2:8000"
                params={{
                    namespace: "surrealdb",
                    database: "docs",
                }}
            >
                <UserList />
            </SurrealProvider>
        </QueryClientProvider>
    );
}
```

## Executing queries

Use the `useSurreal()` hook to reach the `Surreal` instance from any component. All [query methods](/docs/reference/javascript/concepts/executing-queries.md) are available on the client, including `.query()`, `.select()`, and `.create()`.

Gate the query on `isSuccess` so it runs once the connection is open, and again after each reconnect.

```tsx title="UserList.tsx"
import React, { useEffect, useState } from "react";
import { ActivityIndicator, FlatList, Text, View } from "react-native";
import { Table } from "surrealdb";
import { useSurreal } from "./SurrealProvider";

interface User {
    id: string;
    name: string;
    email: string;
}

export function UserList() {
    const { client, isConnecting, isSuccess, isError, error } = useSurreal();
    const [users, setUsers] = useState<User[]>([]);

    useEffect(() => {
        if (!isSuccess) return;

        client.select<User>(new Table("users"))
            .then(setUsers)
            .catch(console.error);
    }, [client, isSuccess]);

    if (isConnecting) return <ActivityIndicator />;
    if (isError) return <Text>Connection failed: {String(error)}</Text>;

    return (
        <FlatList
            data={users}
            keyExtractor={(user) => String(user.id)}
            renderItem={({ item }) => (
                <View>
                    <Text>{item.name}</Text>
                    <Text>{item.email}</Text>
                </View>
            )}
        />
    );
}
```

## Subscribing to live queries

[Live queries](/docs/reference/javascript/concepts/live-queries.md) push changes to the device as they happen, which removes the need to poll on a metered connection. They require a WebSocket connection.

Because the provider closes the socket on background, tie the subscription to `isSuccess` as well. The effect then recreates the subscription every time the connection reopens.

```tsx
import { useEffect, useState } from "react";
import { Table } from "surrealdb";
import { useSurreal } from "./SurrealProvider";

interface Message {
    id: string;
    body: string;
}

export function useLiveMessages() {
    const { client, isSuccess } = useSurreal();
    const [messages, setMessages] = useState<Message[]>([]);

    useEffect(() => {
        if (!isSuccess) return;

        const pending = client.live<Message>(new Table("messages"));

        pending
            .then((live) => {
                live.subscribe((action, result) => {
                    if (action === "CREATE") {
                        setMessages((current) => [...current, result]);
                    }
                });
            })
            .catch(console.error);

        return () => {
            pending.then((live) => live.kill()).catch(() => {});
        };
    }, [client, isSuccess]);

    return messages;
}
```

## Handling authentication

Sign users in with [record access](/docs/reference/query-language/statements/define/access/record.md) and keep the resulting tokens in the keystore, so the session survives an app restart.

```tsx title="useAuth.ts"
import * as Keychain from "react-native-keychain";
import { useEffect } from "react";
import { useSurreal } from "./SurrealProvider";

const ACCESS_SERVICE = "surreal.access";
const REFRESH_SERVICE = "surreal.refresh";

export async function readToken(service: string) {
    const entry = await Keychain.getGenericPassword({ service });
    return entry ? entry.password : null;
}

export function useAuth() {
    const { client } = useSurreal();

    useEffect(() => {
        return client.subscribe("auth", async (tokens) => {
            if (tokens) {
                await Keychain.setGenericPassword("surreal", tokens.access, { service: ACCESS_SERVICE });
                if (tokens.refresh) {
                    await Keychain.setGenericPassword("surreal", tokens.refresh, { service: REFRESH_SERVICE });
                }
            } else {
                await Keychain.resetGenericPassword({ service: ACCESS_SERVICE });
                await Keychain.resetGenericPassword({ service: REFRESH_SERVICE });
            }
        });
    }, [client]);

    async function login(email: string, password: string) {
        return client.signin({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function register(email: string, password: string) {
        return client.signup({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function logout() {
        return client.invalidate();
    }

    return { login, register, logout };
}
```

The `auth` event fires on sign in, sign up, token refresh, and invalidation, and `.subscribe()` returns the function that removes the listener.

### Restoring a session on launch

Pass a function to the `authentication` connection option. The SDK calls it while opening the connection and again whenever it needs to re-authenticate after a reconnect. The option accepts a token string or `null`, which is what `readToken()` returns.

```tsx title="App.tsx"
import { readToken } from "./useAuth";

<SurrealProvider
    endpoint="ws://10.0.2.2:8000"
    params={{
        namespace: "surrealdb",
        database: "docs",
        authentication: () => readToken("surreal.access"),
    }}
>
```

If the stored access token has expired and you also hold a refresh token, exchange the pair with [`.authenticate()`](/docs/reference/javascript/concepts/authentication.md#authenticating-with-an-existing-token) instead.

```ts
const access = await readToken("surreal.access");
const refresh = await readToken("surreal.refresh");

if (access) {
    await client.authenticate(refresh ? { access, refresh } : access);
}
```

> [!IMPORTANT]
> Once you call `.signin()`, `.signup()`, or `.authenticate()`, the `authentication` connection option is ignored for the rest of that session. Choose one of the two approaches per session rather than mixing them.

## Troubleshooting

| Symptom | Cause |
|---------|-------|
| `ReferenceError: Property 'TextDecoder' doesn't exist` | The polyfill imports are missing, or they sit below the SDK import in the entry file. |
| The endpoint fails to parse, or `URL` throws on construction | `react-native-url-polyfill/auto` is not imported. |
| `Network request failed` on Android, works in the browser | The endpoint uses `localhost`. Use `10.0.2.2` on the emulator or the LAN address on a device. |
| Connection hangs, then fails with no server log entry | Cleartext traffic is blocked. Switch to `wss://` or apply the [development configuration](#reaching-your-database-from-a-device). |
| `Unable to resolve module node:util` | Metro resolved the SDK's server build. Remove `node` from `unstable_conditionNames` in **metro.config.js**. |
| Queries fail after the app returns from the background | The query ran before the socket reopened. Gate it on `isSuccess` from the provider. |

## Learn more

- [Expo](/docs/reference/javascript/frameworks/expo.md) for the same setup in an Expo project, where the polyfills are already provided
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for connection protocols and reconnection behaviour
- [Authentication](/docs/reference/javascript/concepts/authentication.md) for signing in, signing up, and token management
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) for query builders and raw SurrealQL
- [Live queries](/docs/reference/javascript/concepts/live-queries.md) for real-time subscriptions
- [JavaScript SDK API reference](/docs/reference/javascript/api/) for the complete method reference

---

Source: https://surrealdb.com/docs/reference/javascript/frameworks/solidjs

# SolidJS

The SurrealDB SDK for JavaScript can be used in SolidJS applications to interact with your SurrealDB instance.

[SolidJS](https://docs.solidjs.com/) is a JavaScript framework for building user interfaces. The SurrealDB SDK for JavaScript can be used in your SolidJS applications to interact with your SurrealDB instance.

This guide walks you through setting up a connection provider and executing queries in a SolidJS project.

## Prerequisites

- A basic understanding of SolidJS
- A running [SurrealDB instance](/docs/running/overview.md)
- The [JavaScript SDK installed](/docs/reference/javascript/installation.md) in your project

## Installing dependencies

In addition to `surrealdb`, this guide uses [@tanstack/solid-query](https://tanstack.com/query/latest/docs/framework/solid) to manage the asynchronous connection state. Install it alongside the SDK:

**npm**

```bash
npm install --save surrealdb @tanstack/solid-query
```

**yarn**

```bash
yarn add surrealdb @tanstack/solid-query
```

**pnpm**

```bash
pnpm install surrealdb @tanstack/solid-query
```

Follow the [installation guide](/docs/reference/javascript/installation.md) for more information on how to install the SDK in your project.

## Creating the connection provider

We recommend initialising the SDK in a [Context Provider](https://docs.solidjs.com/concepts/context) so the `Surreal` client is accessible anywhere in your component tree. The provider below manages the connection lifecycle, tracks connection status via TanStack Query, and cleans up automatically.

The `params` prop accepts the same options as [`.connect()`](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-options), including `namespace`, `database`, and [`authentication`](/docs/reference/javascript/concepts/authentication.md#providing-credentials-on-connect).

```tsx
import { Surreal } from "surrealdb";
import { useContext, createContext, JSX, createEffect, onCleanup, Accessor, onMount } from "solid-js";
import { createMutation } from "@tanstack/solid-query";
import { createStore } from "solid-js/store";

interface SurrealProviderProps {
    children: JSX.Element;
    endpoint: string;
    client?: Surreal;
    params?: Parameters<Surreal["connect"]>[1];
    autoConnect?: boolean;
}

interface SurrealProviderState {
    client: Accessor<Surreal>;
    isConnecting: Accessor<boolean>;
    isSuccess: Accessor<boolean>;
    isError: Accessor<boolean>;
    error: Accessor<unknown | null>;
    connect: () => Promise<void>;
    close: () => Promise<true>;
}

interface SurrealProviderStore {
    instance: Surreal;
    status: "connecting" | "connected" | "disconnected";
}

const SurrealContext = createContext<SurrealProviderState>();

export function SurrealProvider(props: SurrealProviderProps) {
    const [store, setStore] = createStore<SurrealProviderStore>({
        instance: props.client ?? new Surreal(),
        status: "disconnected",
    });

    const { mutateAsync, isError, error, reset } = createMutation(() => ({
        mutationFn: async () => {
            setStore("status", "connecting");
            await store.instance.connect(props.endpoint, props.params);
        },
    }));

    createEffect(() => {
        if (props.autoConnect !== false) mutateAsync();

        onCleanup(() => {
            reset();
            store.instance.close();
        });
    });

    onMount(() => {
        store.instance.subscribe("connected", () => {
            setStore("status", "connected");
        });

        store.instance.subscribe("disconnected", () => {
            setStore("status", "disconnected");
        });
    });

    const value: SurrealProviderState = {
        client: () => store.instance,
        close: () => store.instance.close(),
        connect: mutateAsync,
        error: () => error,
        isConnecting: () => store.status === "connecting",
        isError: () => isError,
        isSuccess: () => store.status === "connected",
    };

    return (
        <SurrealContext.Provider value={value}>
            {props.children}
        </SurrealContext.Provider>
    );
}

export function useSurreal(): SurrealProviderState {
    const context = useContext(SurrealContext);
    if (!context) throw new Error("useSurreal must be used within a SurrealProvider");
    return context;
}

export function useSurrealClient() {
    return useSurreal().client;
}
```

## Wrapping your application

In your top-level component, wrap the root with `QueryClientProvider` and `SurrealProvider`. Pass the endpoint and any [connection options](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-options) through the `params` prop.

```tsx
import type { Component } from "solid-js";
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query";
import { SurrealProvider } from "./SurrealProvider";
import App from "./App";

const queryClient = new QueryClient();

const Root: Component = () => {
    return (
        <QueryClientProvider client={queryClient}>
            <SurrealProvider
                endpoint="ws://127.0.0.1:8000"
                params={{
                    namespace: "surrealdb",
                    database: "docs",
                    authentication: {
                        username: "root",
                        password: "secret",
                    },
                }}
            >
                <App />
            </SurrealProvider>
        </QueryClientProvider>
    );
};

export default Root;
```

## Executing queries

Use the `useSurrealClient()` hook to access the `Surreal` instance from any component. All [query methods](/docs/reference/javascript/concepts/executing-queries.md) are available on the client, including `.query()`, `.select()`, `.create()`, and more.

```tsx
import { createResource, For, Show } from "solid-js";
import { Table } from "surrealdb";
import { useSurreal, useSurrealClient } from "./SurrealProvider";

interface User {
    id: string;
    name: string;
    email: string;
}

export function UserList() {
    const { isConnecting, isError, error } = useSurreal();
    const client = useSurrealClient();

    const [users] = createResource(async () => {
        return client().select<User>(new Table("users"));
    });

    return (
        <Show when={!isConnecting()} fallback={<p>Connecting...</p>}>
            <Show when={!isError()} fallback={<p>Connection failed: {String(error())}</p>}>
                <ul>
                    <For each={users()}>
                        {(user) => <li>{user.name} ({user.email})</li>}
                    </For>
                </ul>
            </Show>
        </Show>
    );
}
```

## Handling authentication

You can build an authentication layer on top of the provider using the SDK's [`.signin()`](/docs/reference/javascript/concepts/authentication.md#signing-in-users) and [`.signup()`](/docs/reference/javascript/concepts/authentication.md#signing-up-users) methods. The example below shows a minimal hook for record access authentication.

```tsx
import { useSurrealClient } from "./SurrealProvider";

export function useAuth() {
    const client = useSurrealClient();

    async function login(email: string, password: string) {
        return client().signin({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function register(email: string, password: string) {
        return client().signup({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function logout() {
        return client().invalidate();
    }

    return { login, register, logout };
}
```

## Learn more

- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for connection protocols and reconnection behaviour
- [Authentication](/docs/reference/javascript/concepts/authentication.md) for signing in, signing up, and token management
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) for query builders and raw SurrealQL
- [Live queries](/docs/reference/javascript/concepts/live-queries.md) for real-time subscriptions
- [JavaScript SDK API reference](/docs/reference/javascript/api/) for the complete method reference

---

Source: https://surrealdb.com/docs/reference/javascript/frameworks/vuejs

# Vue.js

The SurrealDB SDK for JavaScript can be used in Vue.js applications to interact with your SurrealDB instance.

[Vue.js](https://vuejs.org/) is a progressive JavaScript framework for building user interfaces. The SurrealDB SDK for JavaScript can be used in your Vue applications to interact with your SurrealDB instance.

This guide walks you through setting up a connection plugin and executing queries in a Vue 3 project using the Composition API.

## Prerequisites

- A basic understanding of Vue 3 and the Composition API
- A running [SurrealDB instance](/docs/running/overview.md)
- The [JavaScript SDK installed](/docs/reference/javascript/installation.md) in your project

## Installing dependencies

In addition to `surrealdb`, this guide uses [@tanstack/vue-query](https://tanstack.com/query/latest/docs/framework/vue) to manage the asynchronous connection state. Install both packages:

**npm**

```bash
npm install --save surrealdb @tanstack/vue-query
```

**yarn**

```bash
yarn add surrealdb @tanstack/vue-query
```

**pnpm**

```bash
pnpm install surrealdb @tanstack/vue-query
```

Follow the [installation guide](/docs/reference/javascript/installation.md) for more information on how to install the SDK in your project.

## Value classes

Before you can use the SDK in your Vue application, you need to configure the codec options to mark all returned [value classes](/docs/reference/javascript/concepts/value-types.md) as raw. This prevents Vue's reactivity system from being applied to them, and allows you to use them in your components without any issues.

```ts
import { Surreal, Value } from "surrealdb";
import { markRaw } from "vue";

const db = new Surreal({
	codecOptions: {
		valueDecodeVisitor: (value) => value instanceof Value ? markRaw(value) : value,
	},
});
```

The `valueDecodeVisitor` function is called for each value returned from the database. If the value is an instance of a value class, it is marked as raw so Vue's reactivity system is not triggered.

## Creating the connection plugin

We recommend providing the `Surreal` client through Vue's [dependency injection](https://vuejs.org/guide/components/provide-inject) system so it is accessible in any component. The composable below manages the connection lifecycle, tracks status via TanStack Query, and cleans up when the component unmounts.

The `params` option accepts the same values as [`.connect()`](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-options), including `namespace`, `database`, and [`authentication`](/docs/reference/javascript/concepts/authentication.md#providing-credentials-on-connect).

```ts
import { Surreal } from "surrealdb";
import { inject, onUnmounted, provide, type InjectionKey, type Ref } from "vue";
import { useMutation } from "@tanstack/vue-query";

interface SurrealOptions {
    endpoint: string;
    client?: Surreal;
    params?: Parameters<Surreal["connect"]>[1];
    autoConnect?: boolean;
}

interface SurrealState {
    client: Surreal;
    isConnecting: Ref<boolean>;
    isSuccess: Ref<boolean>;
    isError: Ref<boolean>;
    error: Ref<unknown>;
    connect: () => Promise<true>;
    close: () => Promise<true>;
}

const SurrealKey: InjectionKey<SurrealState> = Symbol("surreal");

export function provideSurreal(options: SurrealOptions) {
    const instance = options.client ?? new Surreal();

    const { mutateAsync, isPending, isSuccess, isError, error, reset } = useMutation({
        mutationFn: () => instance.connect(options.endpoint, options.params),
    });

    if (options.autoConnect !== false) {
        mutateAsync();
    }

    onUnmounted(() => {
        reset();
        instance.close();
    });

    const state: SurrealState = {
        client: instance,
        isConnecting: isPending,
        isSuccess,
        isError,
        error,
        connect: () => mutateAsync(),
        close: () => instance.close(),
    };

    provide(SurrealKey, state);
    return state;
}

export function useSurreal(): SurrealState {
    const state = inject(SurrealKey);
    if (!state) throw new Error("useSurreal() requires provideSurreal() in a parent component");
    return state;
}

export function useSurrealClient(): Surreal {
    return useSurreal().client;
}
```

## Initialising the plugin

Register the TanStack Query plugin in your application entry point, then call `provideSurreal()` in your root component. Pass the endpoint and any [connection options](/docs/reference/javascript/concepts/connecting-to-surrealdb.md#connection-options) through the `params` field.

```ts
import { createApp } from "vue";
import { VueQueryPlugin } from "@tanstack/vue-query";
import App from "./App.vue";

const app = createApp(App);
app.use(VueQueryPlugin);
app.mount("#app");
```

```vue
<script setup lang="ts">
import { provideSurreal } from "./surreal";

provideSurreal({
    endpoint: "ws://127.0.0.1:8000",
    params: {
        namespace: "surrealdb",
        database: "docs",
        authentication: {
            username: "root",
            password: "secret",
        },
    },
});
</script>

<template>
    <router-view />
</template>
```

## Executing queries

Use the `useSurrealClient()` composable to access the `Surreal` instance from any descendant component. All [query methods](/docs/reference/javascript/concepts/executing-queries.md) are available on the client, including `.query()`, `.select()`, `.create()`, and more.

```vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { Table } from "surrealdb";
import { useSurreal, useSurrealClient } from "./surreal";

interface User {
    id: string;
    name: string;
    email: string;
}

const { isConnecting, isError, error } = useSurreal();
const client = useSurrealClient();
const users = ref<User[]>([]);

onMounted(async () => {
    users.value = await client.select<User>(new Table("users"));
});
</script>

<template>
    <p v-if="isConnecting">Connecting...</p>
    <p v-else-if="isError">Connection failed: {{ String(error) }}</p>
    <ul v-else>
        <li v-for="user in users" :key="String(user.id)">
            {{ user.name }} ({{ user.email }})
        </li>
    </ul>
</template>
```

## Handling authentication

You can build an authentication layer on top of the composable using the SDK's [`.signin()`](/docs/reference/javascript/concepts/authentication.md#signing-in-users) and [`.signup()`](/docs/reference/javascript/concepts/authentication.md#signing-up-users) methods. The example below shows a minimal composable for record access authentication.

```ts
import { useSurrealClient } from "./surreal";

export function useAuth() {
    const client = useSurrealClient();

    async function login(email: string, password: string) {
        return client.signin({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function register(email: string, password: string) {
        return client.signup({
            namespace: "surrealdb",
            database: "docs",
            access: "account",
            variables: { email, password },
        });
    }

    async function logout() {
        return client.invalidate();
    }

    return { login, register, logout };
}
```

## Learn more

- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) for connection protocols and reconnection behaviour
- [Authentication](/docs/reference/javascript/concepts/authentication.md) for signing in, signing up, and token management
- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) for query builders and raw SurrealQL
- [Live queries](/docs/reference/javascript/concepts/live-queries.md) for real-time subscriptions
- [JavaScript SDK API reference](/docs/reference/javascript/api/) for the complete method reference

---

Source: https://surrealdb.com/docs/reference/javascript/installation

# Installation

In this section, you will learn how to install the JavaScript SDK in your project.

This page shows how to install the JavaScript SDK and import it into your project.

## Install the SDK

First, install the [SurrealDB SDK](https://npmjs.com/package/surrealdb) using your preferred package manager:

**bun**

```bash
bun install surrealdb
```

**npm**

```bash
npm install --save surrealdb
```

**yarn**

```bash
yarn add surrealdb
```

**pnpm**

```bash
pnpm install surrealdb
```

> [!NOTE]
> The SurrealDB SDK for JavaScript is also available in the JSR registry as [`@surrealdb/surrealdb`](https://jsr.io/@surrealdb/surrealdb).

## Import the SDK into your project

After installing the SDK as a dependency, you can import the SDK into your project. The SDK supports several import options, depending on your setup and environment.

**ESM**

```ts
import { Surreal } from 'surrealdb';
```

**CommonJS**

```ts
const { Surreal } = require('surrealdb');
```

**Deno**

```ts
//Importing from Deno
import { Surreal } from "https://deno.land/x/surrealdb/mod.ts";

// Import with version 
import { Surreal } from "https://deno.land/x/surrealdb/mod.ts";
```

**CDN**

```ts
import { Surreal } from "https://unpkg.com/surrealdb";
// or
import { Surreal } from "https://cdn.jsdelivr.net/npm/surrealdb";
```

## Next steps

After installing the SDK, see the quick start guide to build a simple application with the SDK. You can also learn more about carrying out common tasks with the SDK in the following sections:
- [Getting started](/docs/languages/javascript.md)
- [Connecting to SurrealDB](/docs/reference/javascript/concepts/connecting-to-surrealdb.md)
- [Embedded engines](/docs/reference/javascript/concepts/embedded-engines.md) - run SurrealDB inside your application, in a browser with the WebAssembly engine or in Node.js, Bun or Deno with the Node.js engine
- [Authentication](/docs/reference/javascript/concepts/authentication.md)

---

Source: https://surrealdb.com/docs/reference/kotlin

# Kotlin SDK

The official SurrealDB SDK for Kotlin. A coroutine-based, Kotlin Multiplatform client for querying a remote instance.

The SurrealDB SDK for Kotlin lets you connect to [SurrealDB](/docs) from any Kotlin application. It is a [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library targeting the JVM, Android, and iOS, with a coroutine-based, `suspend`-friendly API. The SDK connects to remote instances over WebSocket or HTTP and provides methods for querying with [SurrealQL](/docs/reference/query-language.md), managing data, [authentication](/docs/learn/security/authentication/users.md), live queries, transactions, and multiple sessions. Its design closely mirrors the [JavaScript SDK](/docs/languages/javascript.md).

> [!NOTE]
> The Kotlin SDK is currently in early development. The latest version is `0.1.0-SNAPSHOT`, and it is not yet published to Maven Central. The coordinates and APIs documented here are provisional and may change before the first stable release.

## Getting started

- [Installation](/docs/reference/kotlin/installation.md) - Install the SDK and add it to your project.

- [Getting started guide](/docs/languages/kotlin.md) - Connect to SurrealDB and run your first queries.

## Learn

- [Concepts](/docs/reference/kotlin/concepts/connecting-to-surrealdb.md) - Guides for connecting, authenticating, querying, and working with data.

- [API Reference](/docs/reference/kotlin/api/core/surreal-client.md) - Complete reference for the SDK's methods, types, and errors.

## Contributing

To contribute to the SDK code, submit an Issue or Pull Request in the [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin) repository. To contribute to this documentation, submit an Issue or Pull Request in the [docs.surrealdb.com](https://github.com/surrealdb/docs.surrealdb.com) repository.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.kotlin)

---

Source: https://surrealdb.com/docs/reference/kotlin/api/core/client-config

# SurrealClientConfig

Configuration options for the SurrealDB Kotlin client, including reconnection and authentication.

`SurrealClientConfig` configures a [`SurrealClient`](/docs/reference/kotlin/api/core/surreal-client.md). The only required field is `url`; everything else has a sensible default.

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.SurrealClientConfig
```

---

## Fields

<table>
    <thead>
        <tr><th>Field</th><th>Type</th><th>Default</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>url</code> _(required)_</td>
            <td><code>String</code></td>
            <td>-</td>
            <td>The connection URL. The scheme (<code>ws</code>/<code>wss</code>/<code>http</code>/<code>https</code>) selects the transport.</td>
        </tr>
        <tr>
            <td id="json"><code>json</code></td>
            <td><code>Json</code></td>
            <td>lenient</td>
            <td>The <code>kotlinx.serialization</code> instance used for encoding and decoding.</td>
        </tr>
        <tr>
            <td><code>autoConnect</code></td>
            <td><code>Boolean</code></td>
            <td><code>true</code></td>
            <td>Connect lazily on the first request.</td>
        </tr>
        <tr>
            <td><code>autoAuthenticate</code></td>
            <td><code>Boolean</code></td>
            <td><code>false</code></td>
            <td>Authenticate automatically using <code>credentialProvider</code> on connect and reconnect.</td>
        </tr>
        <tr>
            <td><code>credentialProvider</code></td>
            <td><code>(suspend () -&gt; SurrealAuthInput?)?</code></td>
            <td><code>null</code></td>
            <td>Supplies credentials for automatic authentication and token renewal.</td>
        </tr>
        <tr>
            <td><code>requestTimeoutMillis</code></td>
            <td><code>Long</code></td>
            <td><code>30_000</code></td>
            <td>Per-request timeout in milliseconds.</td>
        </tr>
        <tr>
            <td><code>reconnect</code></td>
            <td><code>ReconnectConfig</code></td>
            <td><code>ReconnectConfig()</code></td>
            <td>WebSocket reconnection behaviour.</td>
        </tr>
        <tr>
            <td><code>tokenRenewalLeadMillis</code></td>
            <td><code>Long</code></td>
            <td><code>60_000</code></td>
            <td>How long before token expiry to renew, in milliseconds.</td>
        </tr>
        <tr>
            <td><code>httpClientFactory</code></td>
            <td><code>((SurrealClientConfig) -&gt; HttpClient)?</code></td>
            <td><code>null</code></td>
            <td>Supplies a custom Ktor <code>HttpClient</code>.</td>
        </tr>
    </tbody>
</table>

```kotlin title="Example"
import com.surrealdb.kotlin.SurrealAuthInput
import com.surrealdb.kotlin.SurrealClientConfig
import com.surrealdb.kotlin.engine.ReconnectConfig
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

val config = SurrealClientConfig(
    url = "wss://example.com",
    requestTimeoutMillis = 30_000,
    autoAuthenticate = true,
    tokenRenewalLeadMillis = 60_000,
    reconnect = ReconnectConfig(enabled = true, multiplier = 1.5),
    credentialProvider = {
        SurrealAuthInput.SignIn(buildJsonObject {
            put("user", "root")
            put("pass", "root")
        })
    },
)
```

---

## `ReconnectConfig` {#reconnect-config}

Controls exponential-backoff reconnection on the WebSocket transport.

```kotlin title="Import"
import com.surrealdb.kotlin.engine.ReconnectConfig
```

<table>
    <thead>
        <tr><th>Field</th><th>Type</th><th>Default</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>enabled</code></td><td><code>Boolean</code></td><td><code>true</code></td><td>Whether reconnection is attempted.</td></tr>
        <tr><td><code>initialDelayMillis</code></td><td><code>Long</code></td><td><code>250</code></td><td>Delay before the first retry.</td></tr>
        <tr><td><code>maxDelayMillis</code></td><td><code>Long</code></td><td><code>30_000</code></td><td>Maximum delay between retries.</td></tr>
        <tr><td><code>multiplier</code></td><td><code>Double</code></td><td><code>1.5</code></td><td>Backoff multiplier applied each attempt.</td></tr>
        <tr><td><code>maxAttempts</code></td><td><code>Int?</code></td><td><code>null</code></td><td>Maximum attempts; <code>null</code> retries indefinitely.</td></tr>
    </tbody>
</table>

---

## `SurrealAuthInput` {#surreal-auth-input}

A sealed interface describing how `credentialProvider` should authenticate.

```kotlin title="Import"
import com.surrealdb.kotlin.SurrealAuthInput
```

<table>
    <thead>
        <tr><th>Variant</th><th>Payload</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>SurrealAuthInput.SignIn</code></td><td><code>params: JsonObject</code></td><td>Sign in with credentials.</td></tr>
        <tr><td><code>SurrealAuthInput.Token</code></td><td><code>token: String</code></td><td>Authenticate with an existing token.</td></tr>
    </tbody>
</table>

## Learn more

- [SurrealClient API reference](/docs/reference/kotlin/api/core/surreal-client.md)
- [Connecting to SurrealDB](/docs/reference/kotlin/concepts/connecting-to-surrealdb.md)
- [Authentication](/docs/reference/kotlin/concepts/authentication.md)

---

Source: https://surrealdb.com/docs/reference/kotlin/api/core/live-subscription

# LiveQuerySubscription

The subscription handle returned by live queries in the SurrealDB Kotlin SDK.

`LiveQuerySubscription` is returned by [`.live()`](/docs/reference/kotlin/api/core/surreal-client.md#live). It exposes a coroutine [`Flow`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/) of notifications and a method to stop the subscription. See [Live queries](/docs/reference/kotlin/concepts/live-queries.md) for the concept.

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.live.LiveQuerySubscription
```

---

## Members

### `id` {#id}

The server-assigned live query ID.

**Type:** `String`

### `events` {#events}

A flow of [`SurrealLiveNotification`](#notification) events for this subscription.

**Type:** `Flow<SurrealLiveNotification>`

```kotlin title="Example"
subscription.events.collect { event ->
    println("${event.action}: ${event.result}")
}
```

### `.cancel()` {#cancel}

Stops the subscription and kills the underlying live query on the server.

```kotlin title="Method Syntax"
subscription.cancel()
```

**Returns:** `Unit`

---

## `SurrealLiveNotification` {#notification}

The payload emitted on the [`events`](#events) flow.

```kotlin title="Import"
import com.surrealdb.kotlin.model.SurrealLiveNotification
```

<table>
    <thead>
        <tr><th>Field</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>action</code></td><td><code>String</code></td><td>The change kind: <code>"CREATE"</code>, <code>"UPDATE"</code>, or <code>"DELETE"</code>.</td></tr>
        <tr><td><code>liveQueryId</code></td><td><code>String</code></td><td>The ID of the originating live query.</td></tr>
        <tr><td><code>result</code></td><td><code>JsonElement</code></td><td>The changed record or diff.</td></tr>
    </tbody>
</table>

## Learn more

- [Live queries](/docs/reference/kotlin/concepts/live-queries.md)
- [SurrealClient API reference](/docs/reference/kotlin/api/core/surreal-client.md) for `.live()` and `.kill()`

---

Source: https://surrealdb.com/docs/reference/kotlin/api/core/query-builder

# Query builders

The fluent query builders, expression helpers, and surql DSL of the SurrealDB Kotlin SDK.

The CRUD methods on [`SurrealClient`](/docs/reference/kotlin/api/core/surreal-client.md) return fluent builders that compile to [SurrealQL](/docs/reference/query-language.md). Each builder is refined with chainable methods and terminated with [`await()`](#await) (raw [`JsonElement`](/docs/reference/kotlin/api/values/value.md)) or the typed [`awaitAs<T>()`](#await-as) extension.

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.query.awaitAs
import com.surrealdb.kotlin.query.field
import com.surrealdb.kotlin.query.gte
```

---

## Terminal methods

### `.await()` {#await}

Executes the builder and returns the unwrapped result of the first statement as a [`JsonElement`](/docs/reference/kotlin/api/values/value.md).

```kotlin title="Method Syntax"
builder.await()
```

**Returns:** `JsonElement`

### `.awaitRaw()` {#await-raw}

Executes the builder and returns the raw `[{ status, result, time, type }]` response envelope.

```kotlin title="Method Syntax"
builder.awaitRaw()
```

**Returns:** `JsonElement`

### `.awaitAs<T>()` {#await-as}

An inline extension that executes the builder and decodes the result into `T` using [`kotlinx.serialization`](/docs/reference/kotlin/concepts/serialization.md). Available on every builder.

```kotlin title="Method Syntax"
builder.awaitAs<T>()
```

**Returns:** `T`

```kotlin title="Example"
val people: List<Person> = client.select(Table("person")).awaitAs()
```

### `.compile()` {#compile}

Compiles the builder to a `BoundQuery` without executing it - useful for inspection or composing with [`.query()`](/docs/reference/kotlin/api/core/surreal-client.md#query).

```kotlin title="Method Syntax"
builder.compile()
```

**Returns:** `BoundQuery`

---

## `SelectQuery` {#select-query}

Returned by [`.select(what)`](/docs/reference/kotlin/api/core/surreal-client.md#select).

<table>
    <thead>
        <tr><th>Method</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td id="fields"><code>.fields(vararg names)</code></td><td>Selects specific fields.</td></tr>
        <tr><td><code>.value(field)</code></td><td>Selects a single field's value.</td></tr>
        <tr><td id="where"><code>.where(expr)</code></td><td>Filters with an <a href="#expressions">expression</a>.</td></tr>
        <tr><td id="start"><code>.start(n)</code></td><td>Skips the first <code>n</code> records.</td></tr>
        <tr><td id="limit"><code>.limit(n)</code></td><td>Limits the number of records.</td></tr>
        <tr><td id="fetch"><code>.fetch(vararg fields)</code></td><td>Fetches related records.</td></tr>
        <tr><td><code>.timeout(seconds)</code></td><td>Sets a query timeout.</td></tr>
        <tr><td><code>.version(literal)</code></td><td>Reads at a specific version.</td></tr>
    </tbody>
</table>

```kotlin title="Example"
val adults: List<Person> = client
    .select(Table("person"))
    .where(field("age") gte 18)
    .limit(50)
    .awaitAs()
```

## Content builders

[`CreateQuery`](/docs/reference/kotlin/api/core/surreal-client.md#create), [`UpdateQuery`](/docs/reference/kotlin/api/core/surreal-client.md#update), [`UpsertQuery`](/docs/reference/kotlin/api/core/surreal-client.md#upsert), and [`RelateQuery`](/docs/reference/kotlin/api/core/surreal-client.md#relate) accept content and a return mode.

<table>
    <thead>
        <tr><th>Method</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>.content(data)</code></td><td>Sets the record content (a <code>JsonElement</code>).</td></tr>
        <tr><td><code>.where(expr)</code></td><td>Filters affected records (update, upsert, merge, patch, delete).</td></tr>
        <tr><td id="return-mode"><code>.returnMode(mode)</code></td><td>Controls the returned payload (see <a href="#return-mode-type">ReturnMode</a>).</td></tr>
    </tbody>
</table>

## `ReturnMode` {#return-mode-type}

```kotlin title="Import"
import com.surrealdb.kotlin.query.ReturnMode
```

<table>
    <thead>
        <tr><th>Value</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>ReturnMode.None</code></td><td>Returns nothing.</td></tr>
        <tr><td><code>ReturnMode.Before</code></td><td>Returns records as they were before the change.</td></tr>
        <tr><td><code>ReturnMode.After</code></td><td>Returns records after the change.</td></tr>
        <tr><td><code>ReturnMode.Diff</code></td><td>Returns a JSON Patch diff.</td></tr>
        <tr><td><code>ReturnMode.Fields(names)</code></td><td>Returns only the named fields.</td></tr>
    </tbody>
</table>

## `RunQuery` {#run-query}

Returned by [`.run(function)`](/docs/reference/kotlin/api/core/surreal-client.md#run).

<table>
    <thead>
        <tr><th>Method</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>.args(vararg args)</code></td><td>Sets the function arguments.</td></tr>
        <tr><td><code>.version(version)</code></td><td>Selects a function version.</td></tr>
    </tbody>
</table>

---

## Expression helpers {#expressions}

Build `WHERE` expressions with the helper functions and infix operators from `com.surrealdb.kotlin.query`.

```kotlin title="Import"
import com.surrealdb.kotlin.query.field
import com.surrealdb.kotlin.query.value
import com.surrealdb.kotlin.query.gte
import com.surrealdb.kotlin.query.and
```

<table>
    <thead>
        <tr><th>Helper</th><th>SurrealQL</th></tr>
    </thead>
    <tbody>
        <tr><td><code>field(name)</code></td><td>A validated field reference.</td></tr>
        <tr><td><code>value(any)</code></td><td>A literal value.</td></tr>
        <tr><td><code>raw(boundQuery)</code></td><td>A raw fragment.</td></tr>
        <tr><td><code>a eq b</code></td><td><code>=</code></td></tr>
        <tr><td><code>a neq b</code></td><td><code>!=</code></td></tr>
        <tr><td><code>a lt b</code></td><td><code>&lt;</code></td></tr>
        <tr><td><code>a lte b</code></td><td><code>&lt;=</code></td></tr>
        <tr><td><code>a gt b</code></td><td><code>&gt;</code></td></tr>
        <tr><td><code>a gte b</code></td><td><code>&gt;=</code></td></tr>
        <tr><td><code>a contains b</code></td><td><code>CONTAINS</code></td></tr>
        <tr><td><code>a inside b</code></td><td><code>IN</code></td></tr>
        <tr><td><code>a and b</code></td><td><code>AND</code></td></tr>
        <tr><td><code>a or b</code></td><td><code>OR</code></td></tr>
        <tr><td><code>not(expr)</code></td><td><code>!</code></td></tr>
    </tbody>
</table>

> [!NOTE]
> Inequality is `neq` (not `ne`) and membership is `inside` (not `in`, which is a reserved Kotlin keyword).

```kotlin title="Example"
import com.surrealdb.kotlin.query.field
import com.surrealdb.kotlin.query.gte
import com.surrealdb.kotlin.query.eq
import com.surrealdb.kotlin.query.and

val expr = (field("age") gte 18) and (field("active") eq true)
val results = client.select(Table("person")).where(expr).awaitAs<List<Person>>()
```

---

## `surql` DSL {#surql}

The `surql` helpers build a parameterised `BoundQuery` with automatic, injection-safe binding of values and record identifiers.

```kotlin title="Import"
import com.surrealdb.kotlin.query.surql
```

```kotlin title="Example"
// Vararg bindings
val a = surql("SELECT * FROM person WHERE age > \$min", "min" to 25)

// Builder block
val b = surql {
    +"SELECT * FROM person WHERE age > "
    param("min", 25)
}

val result = client.query(a)
```

## Learn more

- [SurrealClient API reference](/docs/reference/kotlin/api/core/surreal-client.md) for the CRUD methods
- [Data manipulation](/docs/reference/kotlin/concepts/data-manipulation.md) for the concepts
- [Serialisation](/docs/reference/kotlin/concepts/serialization.md) for `awaitAs`

---

Source: https://surrealdb.com/docs/reference/kotlin/api/core/session

# SurrealSession

The SurrealSession class exposes querying and authentication with isolated per-session state.

`SurrealSession` is the base class of [`SurrealClient`](/docs/reference/kotlin/api/core/surreal-client.md) and the type returned by [`.newSession()`](/docs/reference/kotlin/api/core/surreal-client.md#new-session). It exposes the full querying, authentication, and CRUD API with state - authentication, namespace, database, and parameters - isolated to that session. See [Multiple sessions](/docs/reference/kotlin/concepts/multiple-sessions.md) for the concept.

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

---

## Shared API

Because [`SurrealClient`](/docs/reference/kotlin/api/core/surreal-client.md) extends `SurrealSession`, every session - root or child - provides the same methods documented on the client:

- Connection: [`.use()`](/docs/reference/kotlin/api/core/surreal-client.md#use), [`.ping()`](/docs/reference/kotlin/api/core/surreal-client.md#ping), [`.version()`](/docs/reference/kotlin/api/core/surreal-client.md#version)
- Authentication: [`.signin()`](/docs/reference/kotlin/api/core/surreal-client.md#signin), [`.signup()`](/docs/reference/kotlin/api/core/surreal-client.md#signup), [`.authenticate()`](/docs/reference/kotlin/api/core/surreal-client.md#authenticate), [`.auth()`](/docs/reference/kotlin/api/core/surreal-client.md#auth), [`.invalidate()`](/docs/reference/kotlin/api/core/surreal-client.md#invalidate), [`.reset()`](/docs/reference/kotlin/api/core/surreal-client.md#reset)
- Queries: [`.query()`](/docs/reference/kotlin/api/core/surreal-client.md#query), [`.queryAs<T>()`](/docs/reference/kotlin/api/core/surreal-client.md#query-as), [`.let()`](/docs/reference/kotlin/api/core/surreal-client.md#let), [`.unset()`](/docs/reference/kotlin/api/core/surreal-client.md#unset)
- CRUD builders: [`.select()`](/docs/reference/kotlin/api/core/surreal-client.md#select), [`.create()`](/docs/reference/kotlin/api/core/surreal-client.md#create), [`.update()`](/docs/reference/kotlin/api/core/surreal-client.md#update), [`.upsert()`](/docs/reference/kotlin/api/core/surreal-client.md#upsert), [`.merge()`](/docs/reference/kotlin/api/core/surreal-client.md#merge), [`.patch()`](/docs/reference/kotlin/api/core/surreal-client.md#patch), [`.delete()`](/docs/reference/kotlin/api/core/surreal-client.md#delete), [`.relate()`](/docs/reference/kotlin/api/core/surreal-client.md#relate), [`.insert()`](/docs/reference/kotlin/api/core/surreal-client.md#insert)
- Live queries: [`.live()`](/docs/reference/kotlin/api/core/surreal-client.md#live), [`.kill()`](/docs/reference/kotlin/api/core/surreal-client.md#kill)

## Session state accessors

<table>
    <thead>
        <tr><th>Method</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>.namespace()</code></td><td><code>String?</code></td><td>The session's current namespace.</td></tr>
        <tr><td><code>.database()</code></td><td><code>String?</code></td><td>The session's current database.</td></tr>
        <tr><td><code>.accessToken()</code></td><td><code>String?</code></td><td>The session's current access token, if authenticated.</td></tr>
    </tbody>
</table>

## Creating and closing sessions

```kotlin title="Example"
val session = client.newSession()
session.signin(buildJsonObject { put("user", "a"); put("pass", "a") })
session.use("acme", "main")

// ... use the session ...

client.closeSession(session)
```

## Transactions

Transactions are started on a session via the [`transaction { }`](/docs/reference/kotlin/api/core/transaction.md#transaction-block) and [`beginTransaction()`](/docs/reference/kotlin/api/core/transaction.md#begin-transaction) extension functions. See the [Transaction reference](/docs/reference/kotlin/api/core/transaction.md).

## Learn more

- [SurrealClient API reference](/docs/reference/kotlin/api/core/surreal-client.md)
- [Multiple sessions](/docs/reference/kotlin/concepts/multiple-sessions.md)
- [Transaction reference](/docs/reference/kotlin/api/core/transaction.md)

---

Source: https://surrealdb.com/docs/reference/kotlin/api/core/surreal-client

# SurrealClient

The SurrealClient class is the main entry point for connecting to and interacting with a SurrealDB instance from Kotlin.

The `SurrealClient` class is the main entry point for the Kotlin SDK. It connects to a SurrealDB instance, authenticates, queries, and manages data. It extends [`SurrealSession`](/docs/reference/kotlin/api/core/session.md) and implements `AutoCloseable`. Almost every networked method is a `suspend` function and has a `...Result` companion that returns a [`Result`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-result/) instead of throwing.

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.SurrealClient
import com.surrealdb.kotlin.SurrealClientConfig
```

---

## Connection methods

### `SurrealClient(config)` {#constructor}

Creates a new client from a [`SurrealClientConfig`](/docs/reference/kotlin/api/core/client-config.md). With `autoConnect = true` (the default) the client connects lazily on the first request.

```kotlin title="Method Syntax"
SurrealClient(config)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>config</code> _(required)_</td>
            <td><code>SurrealClientConfig</code></td>
            <td>The client configuration, including the connection <code>url</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `SurrealClient`

```kotlin title="Example"
val client = SurrealClient(SurrealClientConfig(url = "ws://localhost:8000"))
```

### `.connect()` {#connect}

Establishes the connection explicitly. Rarely needed when `autoConnect` is enabled.

```kotlin title="Method Syntax"
client.connect()
```

**Returns:** `Unit`

```kotlin title="Example"
client.connect()
```

### `.close()` {#close}

Closes the active connection and releases all associated resources.

```kotlin title="Method Syntax"
client.close()
```

**Returns:** `Unit`

```kotlin title="Example"
client.close()
```

### `.use(namespace, database)` {#use}

Selects the namespace and database for subsequent operations.

```kotlin title="Method Syntax"
client.use(namespace, database)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>namespace</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The namespace to select.</td>
        </tr>
        <tr>
            <td><code>database</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The database to select.</td>
        </tr>
    </tbody>
</table>

**Returns:** `JsonElement`

```kotlin title="Example"
client.use("surrealdb", "docs")
```

### `.ping()` {#ping}

Pings the server to verify connectivity.

```kotlin title="Method Syntax"
client.ping()
```

**Returns:** `JsonElement`

### `.version()` {#version}

Returns the version of the connected SurrealDB server.

```kotlin title="Method Syntax"
client.version()
```

**Returns:** `JsonElement`

```kotlin title="Example"
val version = client.version()
```

### `.supports(feature)` {#supports}

Returns whether the current transport supports the given [`SurrealFeature`](/docs/reference/kotlin/api/features.md#feature).

```kotlin title="Method Syntax"
client.supports(feature)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>feature</code> _(required)_</td>
            <td><code>SurrealFeature</code></td>
            <td>The feature to check.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Boolean`

```kotlin title="Example"
if (client.supports(SurrealFeature.LiveQueries)) { /* ... */ }
```

---

## Authentication methods

### `.signup(params)` {#signup}

Signs up against a [record access](/docs/reference/query-language/statements/define/access/record.md) method and returns a token.

```kotlin title="Method Syntax"
client.signup(params)
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>params</code> _(required)_</td>
            <td><code>JsonObject</code></td>
            <td>The sign-up parameters (namespace, database, access, and any record variables).</td>
        </tr>
    </tbody>
</table>

**Returns:** `JsonElement`

### `.signin(params)` {#signin}

Signs in with the given credentials and returns a token.

```kotlin title="Method Syntax"
client.signin(params)
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>params</code> _(required)_</td>
            <td><code>JsonObject</code></td>
            <td>The sign-in credentials.</td>
        </tr>
    </tbody>
</table>

**Returns:** `JsonElement`

```kotlin title="Example"
client.signin(buildJsonObject {
    put("user", "root")
    put("pass", "root")
})
```

### `.authenticate(token)` {#authenticate}

Authenticates the session with an existing token.

```kotlin title="Method Syntax"
client.authenticate(token)
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>token</code> _(required)_</td>
            <td><code>String</code></td>
            <td>A JWT previously issued by SurrealDB.</td>
        </tr>
    </tbody>
</table>

**Returns:** `JsonElement`

### `.auth()` {#auth}

Returns the record of the currently authenticated user.

```kotlin title="Method Syntax"
client.auth()
```

**Returns:** `JsonElement`

### `.invalidate()` {#invalidate}

Invalidates the current authentication for the session.

```kotlin title="Method Syntax"
client.invalidate()
```

**Returns:** `JsonElement`

### `.reset()` {#reset}

Resets the session to its initial, unauthenticated state.

```kotlin title="Method Syntax"
client.reset()
```

**Returns:** `JsonElement`

---

## Query methods

### `.query(sql, vars)` {#query}

Runs a raw [SurrealQL](/docs/reference/query-language.md) statement with optional bound parameters.

```kotlin title="Method Syntax"
client.query(sql, vars)
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>sql</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The SurrealQL to execute.</td>
        </tr>
        <tr>
            <td><code>vars</code></td>
            <td><code>JsonObject?</code></td>
            <td>Optional parameters bound as <code>$name</code> in the query.</td>
        </tr>
    </tbody>
</table>

**Returns:** `JsonElement`

```kotlin title="Example"
val result = client.query(
    "SELECT * FROM person WHERE age > \$min",
    buildJsonObject { put("min", 18) },
)
```

There is also an overload that accepts a `BoundQuery` built with the [`surql`](/docs/reference/kotlin/api/core/query-builder.md#surql) DSL.

### `.queryAs<T>(sql, vars)` {#query-as}

Runs SurrealQL and decodes the result into `T` using [`kotlinx.serialization`](/docs/reference/kotlin/concepts/serialization.md).

```kotlin title="Method Syntax"
client.queryAs<T>(sql, vars)
```

**Returns:** `T`

```kotlin title="Example"
val people: List<Person> = client.queryAs("SELECT * FROM person")
```

### `.decode<T>(element)` {#decode}

Decodes a [`JsonElement`](/docs/reference/kotlin/api/values/value.md) into `T` using the client's configured `Json` instance.

```kotlin title="Method Syntax"
client.decode<T>(element)
```

**Returns:** `T`

### `.let(key, value)` {#let}

Defines a session parameter referenced as `$key` in subsequent queries.

```kotlin title="Method Syntax"
client.let(key, value)
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>key</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The parameter name.</td>
        </tr>
        <tr>
            <td><code>value</code> _(required)_</td>
            <td><code>JsonElement</code></td>
            <td>The parameter value.</td>
        </tr>
    </tbody>
</table>

**Returns:** `JsonElement`

### `.unset(key)` {#unset}

Removes a previously defined session parameter.

```kotlin title="Method Syntax"
client.unset(key)
```

**Returns:** `JsonElement`

---

## CRUD methods

These methods return a [query builder](/docs/reference/kotlin/api/core/query-builder.md) that you refine and terminate with `await()` or [`awaitAs<T>()`](/docs/reference/kotlin/api/core/query-builder.md#await-as). The `what` argument accepts a [`Table`](/docs/reference/kotlin/api/values/table.md), [`RecordId`](/docs/reference/kotlin/api/values/record-id.md), or [`RecordIdRange`](/docs/reference/kotlin/api/values/record-id-range.md).

### `.select(what)` {#select}

Returns a [`SelectQuery`](/docs/reference/kotlin/api/core/query-builder.md#select-query) for reading records.

```kotlin title="Method Syntax"
client.select(what)
```

**Returns:** `SelectQuery`

```kotlin title="Example"
val all: List<Person> = client.select(Table("person")).awaitAs()
```

### `.create(what)` {#create}

Returns a [`CreateQuery`](/docs/reference/kotlin/api/core/query-builder.md#content-builders) for inserting a record.

```kotlin title="Method Syntax"
client.create(what)
```

**Returns:** `CreateQuery`

### `.update(what)` {#update}

Returns an `UpdateQuery` for replacing record content.

```kotlin title="Method Syntax"
client.update(what)
```

**Returns:** `UpdateQuery`

### `.upsert(what)` {#upsert}

Returns an `UpsertQuery` for creating or updating records.

```kotlin title="Method Syntax"
client.upsert(what)
```

**Returns:** `UpsertQuery`

### `.merge(what, data)` {#merge}

Returns a `MergeQuery` that merges `data` into the matched records.

```kotlin title="Method Syntax"
client.merge(what, data)
```

**Returns:** `MergeQuery`

### `.patch(what, patches, diff)` {#patch}

Returns a `PatchQuery` that applies [JSON Patch](https://jsonpatch.com/) operations.

```kotlin title="Method Syntax"
client.patch(what, patches, diff = false)
```

**Returns:** `PatchQuery`

### `.delete(what)` {#delete}

Returns a `DeleteQuery` for removing records.

```kotlin title="Method Syntax"
client.delete(what)
```

**Returns:** `DeleteQuery`

### `.relate(in, relation, out)` {#relate}

Returns a `RelateQuery` that creates a graph edge between two records.

```kotlin title="Method Syntax"
client.relate(`in`, relation, out)
```

**Returns:** `RelateQuery`

### `.insert(into, data)` {#insert}

Returns an `InsertQuery` that inserts one or more records into a [`Table`](/docs/reference/kotlin/api/values/table.md).

```kotlin title="Method Syntax"
client.insert(into, data)
```

**Returns:** `InsertQuery`

### `.insertRelation(into, data)` {#insert-relation}

Returns an `InsertRelationQuery` that inserts relation records into a [`Table`](/docs/reference/kotlin/api/values/table.md).

```kotlin title="Method Syntax"
client.insertRelation(into, data)
```

**Returns:** `InsertRelationQuery`

### `.run(function)` {#run}

Returns a `RunQuery` that invokes a built-in or custom [function](/docs/reference/query-language/functions/database-functions.md).

```kotlin title="Method Syntax"
client.run(function)
```

**Returns:** `RunQuery`

---

## Live query methods

### `.live(table, diff)` {#live}

Starts a [live query](/docs/reference/kotlin/concepts/live-queries.md) and returns a [`LiveQuerySubscription`](/docs/reference/kotlin/api/core/live-subscription.md). **WebSocket only.**

```kotlin title="Method Syntax"
client.live(table, diff = null)
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>String</code></td>
            <td>The table to watch.</td>
        </tr>
        <tr>
            <td><code>diff</code></td>
            <td><code>Boolean?</code></td>
            <td>When <code>true</code>, emit JSON Patch diffs instead of full records.</td>
        </tr>
    </tbody>
</table>

**Returns:** `LiveQuerySubscription`

### `.kill(liveQueryId)` {#kill}

Kills a live query by its ID. **WebSocket only.**

```kotlin title="Method Syntax"
client.kill(liveQueryId)
```

**Returns:** `JsonElement`

---

## Sessions

### `.newSession()` {#new-session}

Creates a new isolated [`SurrealSession`](/docs/reference/kotlin/api/core/session.md) over the same connection. **WebSocket only.**

```kotlin title="Method Syntax"
client.newSession()
```

**Returns:** `SurrealSession`

### `.closeSession(session)` {#close-session}

Closes a session previously created with [`.newSession()`](#new-session).

```kotlin title="Method Syntax"
client.closeSession(session)
```

**Returns:** `Unit`

---

## Properties

<table>
    <thead>
        <tr><th>Property</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>config</code></td>
            <td><code>SurrealClientConfig</code></td>
            <td>The configuration the client was created with.</td>
        </tr>
        <tr>
            <td><code>features</code></td>
            <td><code>Set&lt;SurrealFeature&gt;</code></td>
            <td>The features supported by the active transport.</td>
        </tr>
        <tr>
            <td><code>connectionEvents</code></td>
            <td><code>SharedFlow&lt;SurrealConnectionEvent&gt;</code></td>
            <td>A flow of connection lifecycle events.</td>
        </tr>
        <tr>
            <td id="json"><code>json</code></td>
            <td><code>Json</code></td>
            <td>The <code>kotlinx.serialization</code> instance used for encoding and decoding.</td>
        </tr>
    </tbody>
</table>

## Learn more

- [Client configuration](/docs/reference/kotlin/api/core/client-config.md)
- [Session](/docs/reference/kotlin/api/core/session.md)
- [Query builder](/docs/reference/kotlin/api/core/query-builder.md)
- [Features and events](/docs/reference/kotlin/api/features.md)
- [Errors](/docs/reference/kotlin/api/errors.md)

---

Source: https://surrealdb.com/docs/reference/kotlin/api/core/transaction

# SurrealTransaction

The transaction handle and helpers for atomic operations in the SurrealDB Kotlin SDK.

`SurrealTransaction` represents an in-progress [transaction](/docs/reference/kotlin/concepts/transactions.md). It is itself a queryable, so the [CRUD builders](/docs/reference/kotlin/api/core/query-builder.md) are available scoped to the transaction. Transactions are started with the extension functions on a [session](/docs/reference/kotlin/api/core/session.md) and require the **WebSocket** transport.

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.beginTransaction
import com.surrealdb.kotlin.transaction
```

---

## `session.transaction { }` {#transaction-block}

Runs the block against a new `SurrealTransaction`, committing it if the block returns normally and cancelling it if the block throws.

```kotlin title="Method Syntax"
session.transaction { /* ... */ }
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>block</code> _(required)_</td>
            <td><code>suspend SurrealTransaction.() -&gt; Unit</code></td>
            <td>The operations to run inside the transaction.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Unit`

```kotlin title="Example"
client.transaction {
    create(RecordId("person", "tx"))
        .content(buildJsonObject { put("name", "Tx") })
        .await()
}
```

## `session.beginTransaction()` {#begin-transaction}

Begins a transaction explicitly and returns a `SurrealTransaction` for manual commit or cancel.

```kotlin title="Method Syntax"
session.beginTransaction()
```

**Returns:** `SurrealTransaction`

```kotlin title="Example"
val tx = client.beginTransaction()
try {
    tx.create(Table("person")).content(buildJsonObject { put("name", "Ada") }).await()
    tx.commit()
} catch (cause: Throwable) {
    tx.cancel()
    throw cause
}
```

---

## Methods

### `.commit()` {#commit}

Commits the transaction, persisting all its operations.

```kotlin title="Method Syntax"
tx.commit()
```

**Returns:** `Unit`

### `.cancel()` {#cancel}

Cancels the transaction, discarding all its operations.

```kotlin title="Method Syntax"
tx.cancel()
```

**Returns:** `Unit`

## Properties

<table>
    <thead>
        <tr><th>Property</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>txnId</code></td><td><code>String</code></td><td>The transaction's identifier.</td></tr>
    </tbody>
</table>

## Learn more

- [Transactions](/docs/reference/kotlin/concepts/transactions.md)
- [Session](/docs/reference/kotlin/api/core/session.md)
- [Query builders](/docs/reference/kotlin/api/core/query-builder.md)

---

Source: https://surrealdb.com/docs/reference/kotlin/api/errors

# Errors

The exception hierarchy raised by the SurrealDB Kotlin SDK.

The Kotlin SDK raises exceptions that all extend the sealed base class `SurrealException`. Because the hierarchy is sealed, you can branch over it exhaustively with `when`. See [Error handling](/docs/reference/kotlin/concepts/error-handling.md) for usage patterns and the [`Result` variants](/docs/reference/kotlin/concepts/executing-queries.md#result-variants) that avoid exceptions altogether.

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.error.SurrealException
```

---

## `SurrealException` {#surreal-exception}

The sealed base class of all SDK exceptions. Extends `RuntimeException`.

## `SurrealTransportException` {#transport}

Raised when the underlying connection fails, drops, or cannot be established.

## `SurrealProtocolException` {#protocol}

Raised when a malformed or unexpected message is received from the server.

## `SurrealRpcException` {#rpc}

Raised when the server returns an RPC error.

<table>
    <thead>
        <tr><th>Property</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>code</code></td><td><code>Int?</code></td><td>The RPC error code.</td></tr>
        <tr><td><code>data</code></td><td><code>JsonElement?</code></td><td>Additional error data from the server.</td></tr>
    </tbody>
</table>

## `SurrealAuthenticationException` {#authentication}

A subclass of [`SurrealRpcException`](#rpc) raised when authentication fails (for example, invalid credentials or an expired token).

## `SurrealFeatureNotSupportedException` {#feature-not-supported}

Raised when a feature is invoked that the current transport does not support - for example a [live query](/docs/reference/kotlin/concepts/live-queries.md) or [transaction](/docs/reference/kotlin/concepts/transactions.md) over HTTP. Extends the base [`SurrealException`](#surreal-exception). Guard against it with [`.supports()`](/docs/reference/kotlin/api/core/surreal-client.md#supports).

```kotlin title="Example"
import com.surrealdb.kotlin.error.SurrealAuthenticationException
import com.surrealdb.kotlin.error.SurrealException

try {
    client.signin(buildJsonObject { put("user", "root"); put("pass", "wrong") })
} catch (e: SurrealAuthenticationException) {
    println("authentication failed: ${e.message}")
} catch (e: SurrealException) {
    println("error: ${e.message}")
}
```

## Learn more

- [Error handling](/docs/reference/kotlin/concepts/error-handling.md)
- [Features and events](/docs/reference/kotlin/api/features.md)

---

Source: https://surrealdb.com/docs/reference/kotlin/api/features

# Features & Events

Transport feature flags and connection lifecycle events in the SurrealDB Kotlin SDK.

The SDK exposes the capabilities of the active transport as a set of [`SurrealFeature`](#feature) values, and the lifecycle of the connection as a stream of [`SurrealConnectionEvent`](#connection-events).

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.SurrealFeature
import com.surrealdb.kotlin.engine.SurrealConnectionEvent
```

---

## `SurrealFeature` {#feature}

An enum of the features a transport may support. Inspect the active set via the [`features`](/docs/reference/kotlin/api/core/surreal-client.md#properties) property, or check a single feature with [`.supports()`](/docs/reference/kotlin/api/core/surreal-client.md#supports).

<table>
    <thead>
        <tr><th>Value</th><th>Available on</th></tr>
    </thead>
    <tbody>
        <tr><td><code>LiveQueries</code></td><td>WebSocket</td></tr>
        <tr><td><code>Transactions</code></td><td>WebSocket</td></tr>
        <tr><td><code>Sessions</code></td><td>WebSocket</td></tr>
        <tr><td><code>RefreshTokens</code></td><td>WebSocket</td></tr>
        <tr><td><code>ExportImport</code></td><td>WebSocket, HTTP</td></tr>
        <tr><td><code>SurrealML</code></td><td>WebSocket, HTTP</td></tr>
    </tbody>
</table>

```kotlin title="Example"
if (client.supports(SurrealFeature.LiveQueries)) {
    val subscription = client.live("person")
}
```

---

## `SurrealConnectionEvent` {#connection-events}

A sealed class emitted on the client's [`connectionEvents`](/docs/reference/kotlin/api/core/surreal-client.md#properties) [`SharedFlow`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/).

<table>
    <thead>
        <tr><th>Variant</th><th>Payload</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>Connecting</code></td><td>-</td><td>A connection attempt has started.</td></tr>
        <tr><td><code>Connected</code></td><td>-</td><td>The connection is established.</td></tr>
        <tr><td><code>Disconnected</code></td><td>-</td><td>The connection has dropped.</td></tr>
        <tr><td><code>Reconnecting</code></td><td><code>attempt: Int</code>, <code>delayMillis: Long</code></td><td>A reconnect is scheduled.</td></tr>
        <tr><td><code>Error</code></td><td><code>cause: Throwable</code></td><td>A connection error occurred.</td></tr>
    </tbody>
</table>

```kotlin title="Example"
client.connectionEvents.collect { event ->
    when (event) {
        is SurrealConnectionEvent.Connected -> println("connected")
        is SurrealConnectionEvent.Reconnecting -> println("attempt ${event.attempt}")
        is SurrealConnectionEvent.Error -> println("error: ${event.cause.message}")
        else -> {}
    }
}
```

## Learn more

- [Connecting to SurrealDB](/docs/reference/kotlin/concepts/connecting-to-surrealdb.md)
- [SurrealClient API reference](/docs/reference/kotlin/api/core/surreal-client.md)

---

Source: https://surrealdb.com/docs/reference/kotlin/api/values/record-id

# RecordId

The RecordId type identifies a single record in the SurrealDB Kotlin SDK.

`RecordId` identifies a single record by its table and ID, rendered as `table:id`. Pass it to the [CRUD builders](/docs/reference/kotlin/concepts/data-manipulation.md) to target one record.

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.query.RecordId
```

---

## Constructor

```kotlin title="Method Syntax"
RecordId(table, id)
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>table</code> _(required)_</td><td><code>String</code></td><td>The table name.</td></tr>
        <tr><td><code>id</code> _(required)_</td><td><code>String</code></td><td>The record identifier within the table.</td></tr>
    </tbody>
</table>

```kotlin title="Example"
val ada = RecordId("person", "ada")

client.select(ada).awaitAs<Person>()
```

When bound into a query, a `RecordId` is rendered with `type::record(table, id)` so the value is always passed safely.

## Learn more

- [Value types](/docs/reference/kotlin/concepts/value-types.md)
- [`Table`](/docs/reference/kotlin/api/values/table.md) and [`RecordIdRange`](/docs/reference/kotlin/api/values/record-id-range.md)

---

Source: https://surrealdb.com/docs/reference/kotlin/api/values/record-id-range

# RecordIdRange

The RecordIdRange type targets a range of records within a table in the SurrealDB Kotlin SDK.

`RecordIdRange` targets a contiguous range of records within a table by their IDs. Pass it to the [CRUD builders](/docs/reference/kotlin/concepts/data-manipulation.md) to operate on a [range of records](/docs/reference/query-language/language-primitives/data-types/record-ids.md#record-ranges).

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.query.RecordIdRange
```

---

## Constructor

```kotlin title="Method Syntax"
RecordIdRange(table, start, end, includeEnd)
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Default</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>table</code> _(required)_</td><td><code>String</code></td><td>-</td><td>The table name.</td></tr>
        <tr><td><code>start</code></td><td><code>String?</code></td><td><code>null</code></td><td>The inclusive start ID, or <code>null</code> for unbounded.</td></tr>
        <tr><td><code>end</code></td><td><code>String?</code></td><td><code>null</code></td><td>The end ID, or <code>null</code> for unbounded.</td></tr>
        <tr><td><code>includeEnd</code></td><td><code>Boolean</code></td><td><code>false</code></td><td>Whether the <code>end</code> ID is inclusive.</td></tr>
    </tbody>
</table>

```kotlin title="Example"
val range = RecordIdRange("person", start = "a", end = "m", includeEnd = true)

client.select(range).awaitAs<List<Person>>()
```

## Learn more

- [Value types](/docs/reference/kotlin/concepts/value-types.md)
- [`RecordId`](/docs/reference/kotlin/api/values/record-id.md) and [`Table`](/docs/reference/kotlin/api/values/table.md)
- [Record ranges](/docs/reference/query-language/language-primitives/data-types/record-ids.md#record-ranges) in SurrealQL

---

Source: https://surrealdb.com/docs/reference/kotlin/api/values/table

# Table

The Table type refers to a table by name in the SurrealDB Kotlin SDK.

`Table` refers to a table by name. Pass it to the [CRUD builders](/docs/reference/kotlin/concepts/data-manipulation.md) to target every record in the table.

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import com.surrealdb.kotlin.query.Table
```

---

## Constructor

```kotlin title="Method Syntax"
Table(name)
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr><td><code>name</code> _(required)_</td><td><code>String</code></td><td>The table name.</td></tr>
    </tbody>
</table>

```kotlin title="Example"
val person = Table("person")

client.select(person).awaitAs<List<Person>>()
```

When bound into a query, a `Table` is rendered with `type::table(name)` so the value is always passed safely.

## Learn more

- [Value types](/docs/reference/kotlin/concepts/value-types.md)
- [`RecordId`](/docs/reference/kotlin/api/values/record-id.md) and [`RecordIdRange`](/docs/reference/kotlin/api/values/record-id-range.md)

---

Source: https://surrealdb.com/docs/reference/kotlin/api/values/value

# Value (JSON model)

How the SurrealDB Kotlin SDK models values using kotlinx.serialisation JSON types.

The Kotlin SDK models all data - beyond the dedicated [record types](/docs/reference/kotlin/api/values/record-id.md) - using [`kotlinx.serialization`](/docs/reference/kotlin/concepts/serialization.md) JSON types. Read methods return a [`JsonElement`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/); write methods accept a [`JsonObject`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-object/).

**Source:** [surrealdb.kotlin](https://github.com/surrealdb/surrealdb.kotlin)

```kotlin title="Import"
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
```

---

## Building values

Construct objects with [`buildJsonObject`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html).

```kotlin title="Example"
val data = buildJsonObject {
    put("name", "Ada")
    put("age", 36)
}
```

## Decoding values

Rather than navigating raw `JsonElement` trees, decode results into your own [`@Serializable`](/docs/reference/kotlin/concepts/serialization.md) types with [`.queryAs<T>()`](/docs/reference/kotlin/api/core/surreal-client.md#query-as), [`.awaitAs<T>()`](/docs/reference/kotlin/api/core/query-builder.md#await-as), or [`.decode<T>()`](/docs/reference/kotlin/api/core/surreal-client.md#decode).

```kotlin title="Example"
val people: List<Person> = client.queryAs("SELECT * FROM person")
```

> [!NOTE]
> The SDK does not provide dedicated wrapper classes for datetimes, durations, or geometries. Express these as SurrealQL literals or the JSON values SurrealDB expects, and decode results into your own types.

## Learn more

- [Value types](/docs/reference/kotlin/concepts/value-types.md)
- [Serialisation](/docs/reference/kotlin/concepts/serialization.md)

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/authentication

# Authentication

Sign up, sign in, and manage authentication tokens with the SurrealDB Kotlin SDK.

The Kotlin SDK supports the full range of SurrealDB [authentication](/docs/learn/security/authentication/users.md) methods: root, namespace, and database users, as well as record (scoped) access. Credentials are supplied as a [`JsonObject`](/docs/reference/kotlin/concepts/value-types.md), built with [`buildJsonObject`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html).

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#signup"><code>client.signup(params)</code></a></td>
			<td scope="row" data-label="Description">Signs up against a record access method</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#signin"><code>client.signin(params)</code></a></td>
			<td scope="row" data-label="Description">Signs in with credentials</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#authenticate"><code>client.authenticate(token)</code></a></td>
			<td scope="row" data-label="Description">Authenticates with an existing token</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#auth"><code>client.auth()</code></a></td>
			<td scope="row" data-label="Description">Returns the current authentication record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#invalidate"><code>client.invalidate()</code></a></td>
			<td scope="row" data-label="Description">Invalidates the current session authentication</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#reset"><code>client.reset()</code></a></td>
			<td scope="row" data-label="Description">Resets the session to an unauthenticated state</td>
		</tr>
	</tbody>
</table>

## Signing in

Use [`.signin()`](/docs/reference/kotlin/api/core/surreal-client.md#signin) with the credentials appropriate to the [level of access](/docs/learn/security/authentication/users.md) you need. It returns the authentication token as a [`JsonElement`](/docs/reference/kotlin/concepts/value-types.md).

```kotlin
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

// Root user
client.signin(buildJsonObject {
    put("user", "root")
    put("pass", "root")
})

// Database user
client.signin(buildJsonObject {
    put("namespace", "surrealdb")
    put("database", "docs")
    put("user", "admin")
    put("pass", "secret")
})
```

## Signing up

Use [`.signup()`](/docs/reference/kotlin/api/core/surreal-client.md#signup) to register against a [record access](/docs/reference/query-language/statements/define/access/record.md) method.

```kotlin
val token = client.signup(buildJsonObject {
    put("namespace", "surrealdb")
    put("database", "docs")
    put("access", "user")
    put("email", "ada@example.com")
    put("password", "hunter2")
})
```

## Authenticating with a token

If you already hold a token (for example from a previous session), authenticate the connection with [`.authenticate()`](/docs/reference/kotlin/api/core/surreal-client.md#authenticate).

```kotlin
client.authenticate("eyJ0eXAiOiJKV1Qi...")
```

## Inspecting and clearing authentication

Retrieve the currently authenticated record with [`.auth()`](/docs/reference/kotlin/api/core/surreal-client.md#auth), drop the authentication while keeping the connection open with [`.invalidate()`](/docs/reference/kotlin/api/core/surreal-client.md#invalidate), or fully reset the session state with [`.reset()`](/docs/reference/kotlin/api/core/surreal-client.md#reset).

```kotlin
val me = client.auth()
client.invalidate()
client.reset()
```

## Automatic authentication and token renewal

The client can authenticate automatically on connect and after reconnects, and renew tokens shortly before they expire. Provide a `credentialProvider` and enable `autoAuthenticate` on your [`SurrealClientConfig`](/docs/reference/kotlin/api/core/client-config.md). The provider returns a [`SurrealAuthInput`](/docs/reference/kotlin/api/core/client-config.md#surreal-auth-input) - either a `SignIn` with credentials or an existing `Token`.

```kotlin
import com.surrealdb.kotlin.SurrealAuthInput
import com.surrealdb.kotlin.SurrealClientConfig
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

val client = SurrealClient(
    SurrealClientConfig(
        url = "wss://example.com",
        autoAuthenticate = true,
        tokenRenewalLeadMillis = 60_000, // renew 60s before expiry
        credentialProvider = {
            SurrealAuthInput.SignIn(buildJsonObject {
                put("user", "root")
                put("pass", "root")
            })
        },
    ),
)
```

## Learn more

- [SurrealClient API reference](/docs/reference/kotlin/api/core/surreal-client.md) for complete method signatures
- [Client configuration](/docs/reference/kotlin/api/core/client-config.md) for `credentialProvider` and renewal options
- [Multiple sessions](/docs/reference/kotlin/concepts/multiple-sessions.md) for per-session authentication
- [SurrealDB authentication](/docs/learn/security/authentication/users.md) for an overview of authentication concepts

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/connecting-to-surrealdb

# Connecting to SurrealDB

The Kotlin SDK connects to SurrealDB over WebSocket or HTTP, with automatic transport selection and reconnection.

The first step towards interacting with [SurrealDB](/docs) is to create a connection to a database instance. This involves constructing a [`SurrealClient`](/docs/reference/kotlin/api/core/surreal-client.md) with a [`SurrealClientConfig`](/docs/reference/kotlin/api/core/client-config.md), then selecting a namespace and database. The SDK supports remote connections over WebSocket and HTTP.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#constructor"><code>SurrealClient(config)</code></a></td>
			<td scope="row" data-label="Description">Creates a new client from a configuration</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#connect"><code>client.connect()</code></a></td>
			<td scope="row" data-label="Description">Establishes the connection explicitly</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#close"><code>client.close()</code></a></td>
			<td scope="row" data-label="Description">Closes the connection and releases resources</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#use"><code>client.use(ns, db)</code></a></td>
			<td scope="row" data-label="Description">Selects a namespace and database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#version"><code>client.version()</code></a></td>
			<td scope="row" data-label="Description">Returns the server version</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#ping"><code>client.ping()</code></a></td>
			<td scope="row" data-label="Description">Pings the server</td>
		</tr>
	</tbody>
</table>

## Opening a connection

Construct a [`SurrealClient`](/docs/reference/kotlin/api/core/surreal-client.md) with a [`SurrealClientConfig`](/docs/reference/kotlin/api/core/client-config.md) whose `url` points at your SurrealDB instance. By default (`autoConnect = true`) the client connects lazily on the first request, so you rarely need to call [`.connect()`](/docs/reference/kotlin/api/core/surreal-client.md#connect) yourself.

```kotlin
import com.surrealdb.kotlin.SurrealClient
import com.surrealdb.kotlin.SurrealClientConfig

val client = SurrealClient(SurrealClientConfig(url = "ws://localhost:8000"))
```

## Connection string protocols

The URL scheme determines the transport. For more on server configuration, see the [start command](/docs/reference/cli/db/commands/start.md) documentation.

| Protocol | Description |
|---|---|
| `ws://` | Plain WebSocket connection |
| `wss://` | Secure WebSocket connection (TLS) |
| `http://` | Plain HTTP connection |
| `https://` | Secure HTTP connection (TLS) |

The WebSocket engine maintains a single long-lived connection, while the HTTP engine issues a request per call.

## Feature support by protocol

Not all features are available on every transport. You can check support at runtime with [`.supports()`](/docs/reference/kotlin/api/core/surreal-client.md#supports); unsupported calls throw [`SurrealFeatureNotSupportedException`](/docs/reference/kotlin/api/errors.md).

| Feature | WebSocket | HTTP |
|---|---|---|
| Authentication | Yes | Yes |
| Queries | Yes | Yes |
| CRUD operations | Yes | Yes |
| Live queries | Yes | No |
| Transactions | Yes | No |
| Multiple sessions | Yes | No |
| Refresh tokens | Yes | No |
| Export / Import | Yes | Yes |
| SurrealML | Yes | Yes |

See [Features and events](/docs/reference/kotlin/api/features.md) for the full [`SurrealFeature`](/docs/reference/kotlin/api/features.md#feature) enum.

## Selecting a namespace and database

After connecting, select a [namespace](/docs/reference/query-language/statements/define/namespace.md) and [database](/docs/reference/query-language/statements/define/database.md) with [`.use()`](/docs/reference/kotlin/api/core/surreal-client.md#use).

```kotlin
client.use("surrealdb", "docs")
```

## Reconnection

The WebSocket engine automatically reconnects with exponential backoff. Tune this through the [`ReconnectConfig`](/docs/reference/kotlin/api/core/client-config.md#reconnect-config) on your [`SurrealClientConfig`](/docs/reference/kotlin/api/core/client-config.md).

```kotlin
import com.surrealdb.kotlin.SurrealClientConfig
import com.surrealdb.kotlin.engine.ReconnectConfig

val client = SurrealClient(
    SurrealClientConfig(
        url = "wss://example.com",
        reconnect = ReconnectConfig(
            enabled = true,
            initialDelayMillis = 250,
            maxDelayMillis = 30_000,
            multiplier = 1.5,
            maxAttempts = null, // null means retry indefinitely
        ),
    ),
)
```

## Observing connection events

The client exposes a [`connectionEvents`](/docs/reference/kotlin/api/features.md#connection-events) [`SharedFlow`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/) you can collect to react to lifecycle changes.

```kotlin
import com.surrealdb.kotlin.engine.SurrealConnectionEvent
import kotlinx.coroutines.launch

scope.launch {
    client.connectionEvents.collect { event ->
        when (event) {
            is SurrealConnectionEvent.Connected -> println("connected")
            is SurrealConnectionEvent.Reconnecting -> println("reconnecting, attempt ${event.attempt}")
            is SurrealConnectionEvent.Disconnected -> println("disconnected")
            is SurrealConnectionEvent.Error -> println("error: ${event.cause.message}")
            else -> {}
        }
    }
}
```

## Closing a connection

Call [`.close()`](/docs/reference/kotlin/api/core/surreal-client.md#close) to release all resources associated with the connection.

```kotlin
client.close()
```

## Learn more

- [SurrealClient API reference](/docs/reference/kotlin/api/core/surreal-client.md) for complete method signatures
- [Client configuration](/docs/reference/kotlin/api/core/client-config.md) for all configuration options
- [Authentication](/docs/reference/kotlin/concepts/authentication.md) for signing in and managing sessions
- [Error handling](/docs/reference/kotlin/concepts/error-handling.md) for handling connection errors

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/data-manipulation

# Data manipulation

Create, read, update, and delete records with the fluent builders in the SurrealDB Kotlin SDK.

The Kotlin SDK provides fluent builders for the common CRUD operations. Each builder method (such as [`.select()`](/docs/reference/kotlin/api/core/surreal-client.md#select) or [`.create()`](/docs/reference/kotlin/api/core/surreal-client.md#create)) returns a [query builder](/docs/reference/kotlin/api/core/query-builder.md) that you refine and then terminate with `await()` (raw [`JsonElement`](/docs/reference/kotlin/concepts/value-types.md)) or the typed [`awaitAs<T>()`](/docs/reference/kotlin/api/core/query-builder.md#await-as) extension. Internally these compile to [SurrealQL](/docs/reference/query-language.md) and dispatch through [`.query()`](/docs/reference/kotlin/concepts/executing-queries.md), mirroring the [JavaScript SDK](/docs/languages/javascript.md).

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#select"><code>client.select(what)</code></a></td>
			<td scope="row" data-label="Description">Selects records from a table or record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#create"><code>client.create(what)</code></a></td>
			<td scope="row" data-label="Description">Creates a record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#update"><code>client.update(what)</code></a></td>
			<td scope="row" data-label="Description">Replaces the content of records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#upsert"><code>client.upsert(what)</code></a></td>
			<td scope="row" data-label="Description">Creates or updates records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#merge"><code>client.merge(what, data)</code></a></td>
			<td scope="row" data-label="Description">Merges data into records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#patch"><code>client.patch(what, patches)</code></a></td>
			<td scope="row" data-label="Description">Applies JSON patches to records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#delete"><code>client.delete(what)</code></a></td>
			<td scope="row" data-label="Description">Deletes records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#relate"><code>client.relate(in, relation, out)</code></a></td>
			<td scope="row" data-label="Description">Creates a graph edge between records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#insert"><code>client.insert(into, data)</code></a></td>
			<td scope="row" data-label="Description">Inserts one or more records</td>
		</tr>
	</tbody>
</table>

The `what` argument accepts a [`Table`](/docs/reference/kotlin/api/values/table.md) to target every record in a table, a [`RecordId`](/docs/reference/kotlin/api/values/record-id.md) to target a single record, or a [`RecordIdRange`](/docs/reference/kotlin/api/values/record-id-range.md) to target a range.

## Creating records

Build content with [`buildJsonObject`](/docs/reference/kotlin/concepts/value-types.md) and finish with the typed [`awaitAs<T>()`](/docs/reference/kotlin/api/core/query-builder.md#await-as).

```kotlin
import com.surrealdb.kotlin.query.RecordId
import com.surrealdb.kotlin.query.awaitAs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

@Serializable
data class Person(val name: String, val age: Int)

val ada: Person = client
    .create(RecordId("person", "ada"))
    .content(buildJsonObject {
        put("name", "Ada")
        put("age", 36)
    })
    .awaitAs()
```

## Selecting records

Refine a [`select`](/docs/reference/kotlin/api/core/query-builder.md#select-query) with [`.where()`](/docs/reference/kotlin/api/core/query-builder.md), [`.limit()`](/docs/reference/kotlin/api/core/query-builder.md), [`.start()`](/docs/reference/kotlin/api/core/query-builder.md), [`.fetch()`](/docs/reference/kotlin/api/core/query-builder.md), and others, using the [expression helpers](/docs/reference/kotlin/api/core/query-builder.md#expressions).

```kotlin
import com.surrealdb.kotlin.query.Table
import com.surrealdb.kotlin.query.field
import com.surrealdb.kotlin.query.gte
import com.surrealdb.kotlin.query.awaitAs

val adults: List<Person> = client
    .select(Table("person"))
    .where(field("age") gte 18)
    .limit(50)
    .awaitAs()
```

## Updating and merging

Use [`.update()`](/docs/reference/kotlin/api/core/surreal-client.md#update) to replace record content, [`.merge()`](/docs/reference/kotlin/api/core/surreal-client.md#merge) to merge data, or [`.upsert()`](/docs/reference/kotlin/api/core/surreal-client.md#upsert) to create or update. Control the returned payload with [`.returnMode()`](/docs/reference/kotlin/api/core/query-builder.md#return-mode-type).

```kotlin
import com.surrealdb.kotlin.query.RecordId
import com.surrealdb.kotlin.query.ReturnMode
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

client
    .merge(RecordId("person", "ada"), buildJsonObject { put("age", 37) })
    .returnMode(ReturnMode.After)
    .await()
```

## Deleting records

```kotlin
import com.surrealdb.kotlin.query.RecordId

client.delete(RecordId("person", "ada")).await()
```

## Relating records

Create a graph edge between two records with [`.relate()`](/docs/reference/kotlin/api/core/surreal-client.md#relate).

```kotlin
import com.surrealdb.kotlin.query.RecordId
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

client
    .relate(RecordId("person", "ada"), RecordId("wrote", "w1"), RecordId("article", "a1"))
    .content(buildJsonObject { put("year", 1843) })
    .await()
```

## Learn more

- [Query builder reference](/docs/reference/kotlin/api/core/query-builder.md) for every builder method and expression helper
- [Executing queries](/docs/reference/kotlin/concepts/executing-queries.md) for raw SurrealQL
- [Value types](/docs/reference/kotlin/concepts/value-types.md) for `Table`, `RecordId`, and the JSON model
- [Serialisation](/docs/reference/kotlin/concepts/serialization.md) for decoding into your own types

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/error-handling

# Error handling

Handle exceptions and feature-support errors raised by the SurrealDB Kotlin SDK.

Networked operations on the SDK throw a [`SurrealException`](/docs/reference/kotlin/api/errors.md) on failure, or you can use the [`Result` variants](/docs/reference/kotlin/concepts/executing-queries.md#result-variants) to handle failures functionally without exceptions.

The error kinds, wire codes and structured shape behind these are documented once in [Errors](/docs/reference/rest-api/errors.md), which applies to every SDK and protocol.

## Exception hierarchy

All SDK exceptions extend the sealed base [`SurrealException`](/docs/reference/kotlin/api/errors.md).

| Exception | Raised when |
|---|---|
| [`SurrealTransportException`](/docs/reference/kotlin/api/errors.md#transport) | The connection fails or drops |
| [`SurrealProtocolException`](/docs/reference/kotlin/api/errors.md#protocol) | A malformed or unexpected protocol message is received |
| [`SurrealRpcException`](/docs/reference/kotlin/api/errors.md#rpc) | The server returns an RPC error (carries `code` and `data`) |
| [`SurrealAuthenticationException`](/docs/reference/kotlin/api/errors.md#authentication) | Authentication fails (a subclass of `SurrealRpcException`) |
| [`SurrealFeatureNotSupportedException`](/docs/reference/kotlin/api/errors.md#feature-not-supported) | A feature is unavailable on the current transport |

## Catching exceptions

Because [`SurrealException`](/docs/reference/kotlin/api/errors.md) is a sealed class, you can exhaustively branch on it with `when`.

```kotlin
import com.surrealdb.kotlin.error.SurrealAuthenticationException
import com.surrealdb.kotlin.error.SurrealException
import com.surrealdb.kotlin.error.SurrealRpcException
import com.surrealdb.kotlin.error.SurrealTransportException

try {
    client.signin(buildJsonObject {
        put("user", "root")
        put("pass", "wrong")
    })
} catch (e: SurrealAuthenticationException) {
    println("bad credentials: ${e.message}")
} catch (e: SurrealRpcException) {
    println("server error ${e.code}: ${e.message}")
} catch (e: SurrealTransportException) {
    println("connection problem: ${e.message}")
} catch (e: SurrealException) {
    println("unexpected: ${e.message}")
}
```

## Feature support errors

Calling a feature that the current transport does not support - for example a [live query](/docs/reference/kotlin/concepts/live-queries.md) over HTTP - throws [`SurrealFeatureNotSupportedException`](/docs/reference/kotlin/api/errors.md#feature-not-supported). Guard against this with [`.supports()`](/docs/reference/kotlin/api/core/surreal-client.md#supports).

```kotlin
import com.surrealdb.kotlin.SurrealFeature

if (client.supports(SurrealFeature.LiveQueries)) {
    val subscription = client.live("person")
}
```

## Using Result variants

Each networked method has a `...Result` companion that wraps the outcome in a [`Result`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-result/) instead of throwing.

```kotlin
client.queryResult("SELECT * FROM person")
    .onSuccess { println("got $it") }
    .onFailure { println("failed: ${it.message}") }
```

## Learn more

- [Errors reference](/docs/reference/kotlin/api/errors.md) for every exception type
- [Features and events](/docs/reference/kotlin/api/features.md) for checking transport support
- [Executing queries](/docs/reference/kotlin/concepts/executing-queries.md) for the `Result` variants

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/executing-queries

# Executing queries

Run raw SurrealQL with bound parameters and decode results with the Kotlin SDK.

The [`.query()`](/docs/reference/kotlin/api/core/surreal-client.md#query) family runs raw [SurrealQL](/docs/reference/query-language.md) and is the foundation of the SDK - the [CRUD builders](/docs/reference/kotlin/concepts/data-manipulation.md) compile to SurrealQL and dispatch through it. Queries can be supplied as a string with a [`JsonObject`](/docs/reference/kotlin/concepts/value-types.md) of bound parameters, or built with the [`surql`](#building-queries) DSL.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#query"><code>client.query(sql, vars)</code></a></td>
			<td scope="row" data-label="Description">Runs SurrealQL and returns the raw result</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#query-as"><code>client.queryAs&lt;T&gt;(sql, vars)</code></a></td>
			<td scope="row" data-label="Description">Runs SurrealQL and decodes the result to <code>T</code></td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#query"><code>client.queryResult(sql, vars)</code></a></td>
			<td scope="row" data-label="Description">Runs SurrealQL and returns a <code>Result</code></td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#let"><code>client.let(key, value)</code></a></td>
			<td scope="row" data-label="Description">Defines a session parameter</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#unset"><code>client.unset(key)</code></a></td>
			<td scope="row" data-label="Description">Removes a session parameter</td>
		</tr>
	</tbody>
</table>

## Running a query

Pass SurrealQL and, optionally, a [`JsonObject`](/docs/reference/kotlin/concepts/value-types.md) of bound parameters. The result is returned as a [`JsonElement`](/docs/reference/kotlin/concepts/value-types.md).

```kotlin
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

val result = client.query(
    "SELECT * FROM person WHERE age > \$min_age",
    buildJsonObject { put("min_age", 25) },
)
```

> [!NOTE]
> Always bind variables with the `vars` argument rather than interpolating values into the query string. This avoids SurrealQL injection and lets the server cache query plans.

## Decoding results

To decode a query result straight into your own types, use the inline [`.queryAs<T>()`](/docs/reference/kotlin/api/core/surreal-client.md#query-as) helper with a [`@Serializable`](/docs/reference/kotlin/concepts/serialization.md) type.

```kotlin
import kotlinx.serialization.Serializable

@Serializable
data class Person(val name: String, val age: Int)

val people: List<Person> = client.queryAs(
    "SELECT * FROM person WHERE age > \$min_age",
    buildJsonObject { put("min_age", 25) },
)
```

You can also decode an arbitrary [`JsonElement`](/docs/reference/kotlin/concepts/value-types.md) yourself with [`.decode<T>()`](/docs/reference/kotlin/api/core/surreal-client.md#decode).

## Result variants

Every networked call has a `...Result` companion that returns a [`Result`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-result/) instead of throwing, which is useful when you prefer to handle failures functionally.

```kotlin
val outcome = client.queryResult("SELECT * FROM person")
outcome
    .onSuccess { println("got $it") }
    .onFailure { println("query failed: ${it.message}") }
```

See [Error handling](/docs/reference/kotlin/concepts/error-handling.md) for the exception hierarchy thrown by the non-`Result` variants.

## Session parameters

Define parameters that persist for the session with [`.let()`](/docs/reference/kotlin/api/core/surreal-client.md#let), and remove them with [`.unset()`](/docs/reference/kotlin/api/core/surreal-client.md#unset). These are referenced as `$name` in subsequent queries.

```kotlin
import kotlinx.serialization.json.JsonPrimitive

client.let("min_age", JsonPrimitive(18))
client.query("SELECT * FROM person WHERE age > \$min_age")
client.unset("min_age")
```

## Building queries

The [`surql`](/docs/reference/kotlin/api/core/query-builder.md#surql) DSL builds a parameterised `BoundQuery` with automatic binding of values and record identifiers.

```kotlin
import com.surrealdb.kotlin.query.surql

val bound = surql("SELECT * FROM person WHERE age > \$min", "min" to 25)
val result = client.query(bound)
```

## Learn more

- [SurrealClient API reference](/docs/reference/kotlin/api/core/surreal-client.md) for complete method signatures
- [Query builder reference](/docs/reference/kotlin/api/core/query-builder.md) for the fluent builders and `surql` DSL
- [Data manipulation](/docs/reference/kotlin/concepts/data-manipulation.md) for CRUD with the builders
- [Serialisation](/docs/reference/kotlin/concepts/serialization.md) for working with `@Serializable` types

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/live-queries

# Live queries

Subscribe to real-time changes with live queries in the SurrealDB Kotlin SDK.

[Live queries](/docs/reference/query-language/statements/live-select.md) push changes to your application as records are created, updated, or deleted. In the Kotlin SDK, a live subscription exposes a coroutine [`Flow`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/) of notifications that you collect.

> [!NOTE]
> Live queries require a stateful connection and are only available over the **WebSocket** transport. Calling [`.live()`](/docs/reference/kotlin/api/core/surreal-client.md#live) over HTTP throws [`SurrealFeatureNotSupportedException`](/docs/reference/kotlin/api/errors.md). Check support with [`client.supports(SurrealFeature.LiveQueries)`](/docs/reference/kotlin/api/core/surreal-client.md#supports).

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#live"><code>client.live(table)</code></a></td>
			<td scope="row" data-label="Description">Starts a live query and returns a subscription</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/live-subscription.md#events"><code>subscription.events</code></a></td>
			<td scope="row" data-label="Description">A <code>Flow</code> of live notifications</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/live-subscription.md#cancel"><code>subscription.cancel()</code></a></td>
			<td scope="row" data-label="Description">Stops the subscription</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#kill"><code>client.kill(id)</code></a></td>
			<td scope="row" data-label="Description">Kills a live query by ID</td>
		</tr>
	</tbody>
</table>

## Starting a live query

Call [`.live()`](/docs/reference/kotlin/api/core/surreal-client.md#live) with a table name to receive a [`LiveQuerySubscription`](/docs/reference/kotlin/api/core/live-subscription.md), then collect its [`events`](/docs/reference/kotlin/api/core/live-subscription.md#events) flow. Each [`SurrealLiveNotification`](/docs/reference/kotlin/api/core/live-subscription.md#notification) carries an `action` (`"CREATE"`, `"UPDATE"`, or `"DELETE"`) and a `result` payload.

```kotlin
import com.surrealdb.kotlin.SurrealFeature
import kotlinx.coroutines.launch

if (client.supports(SurrealFeature.LiveQueries)) {
    val subscription = client.live("person")

    val job = scope.launch {
        subscription.events.collect { event ->
            println("${event.action}: ${event.result}")
        }
    }
}
```

Pass `diff = true` to receive [JSON Patch](https://jsonpatch.com/) diffs instead of the full record.

```kotlin
val subscription = client.live("person", diff = true)
```

## Filtered live queries

To watch a subset of records with a `WHERE` clause, run a `LIVE SELECT` statement through [`.query()`](/docs/reference/kotlin/concepts/executing-queries.md); the result is the live query UUID, which you can later pass to [`.kill()`](/docs/reference/kotlin/api/core/surreal-client.md#kill).

```kotlin
val liveId = client.query("LIVE SELECT * FROM person WHERE age >= 18")
```

## Stopping a subscription

Cancel a subscription started with [`.live()`](/docs/reference/kotlin/api/core/surreal-client.md#live) by calling [`.cancel()`](/docs/reference/kotlin/api/core/live-subscription.md#cancel), which also kills the underlying live query on the server. Cancel the collecting coroutine separately.

```kotlin
subscription.cancel()
job.cancel()
```

For live queries started via raw SurrealQL, kill them with their UUID.

```kotlin
client.kill(liveId.toString())
```

## Learn more

- [Live subscription reference](/docs/reference/kotlin/api/core/live-subscription.md) for the subscription and notification types
- [SurrealClient API reference](/docs/reference/kotlin/api/core/surreal-client.md) for `.live()` and `.kill()`
- [Features and events](/docs/reference/kotlin/api/features.md) for checking transport support
- [LIVE SELECT](/docs/reference/query-language/statements/live-select.md) for the SurrealQL statement

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/multiple-sessions

# Multiple sessions

Isolate authentication and state across concurrent sessions with the SurrealDB Kotlin SDK.

A single WebSocket connection can host multiple independent **sessions**, each with its own authentication, namespace, database, and parameters. This is useful for multi-tenant applications where requests act on behalf of different users over one shared connection. The [`SurrealClient`](/docs/reference/kotlin/api/core/surreal-client.md) is itself the root session.

> [!NOTE]
> Multiple sessions require a stateful connection and are only available over the **WebSocket** transport. Check support with [`client.supports(SurrealFeature.Sessions)`](/docs/reference/kotlin/api/core/surreal-client.md#supports).

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#new-session"><code>client.newSession()</code></a></td>
			<td scope="row" data-label="Description">Creates a new isolated session</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/surreal-client.md#close-session"><code>client.closeSession(session)</code></a></td>
			<td scope="row" data-label="Description">Closes a session</td>
		</tr>
	</tbody>
</table>

## Creating sessions

Each session created with [`.newSession()`](/docs/reference/kotlin/api/core/surreal-client.md#new-session) is a [`SurrealSession`](/docs/reference/kotlin/api/core/session.md) that exposes the same querying and authentication API as the client, but with isolated state.

```kotlin
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

val tenantA = client.newSession()
val tenantB = client.newSession()

tenantA.signin(buildJsonObject { put("user", "a"); put("pass", "a") })
tenantA.use("acme", "main")

tenantB.signin(buildJsonObject { put("user", "b"); put("pass", "b") })
tenantB.use("globex", "main")

// Each query runs as its own authenticated tenant, isolated from the other.
val aPeople = tenantA.query("SELECT * FROM person")
val bPeople = tenantB.query("SELECT * FROM person")
```

## Closing sessions

Close a session with [`.closeSession()`](/docs/reference/kotlin/api/core/surreal-client.md#close-session) when you no longer need it. This does not close the underlying connection.

```kotlin
client.closeSession(tenantA)
```

## Learn more

- [Session reference](/docs/reference/kotlin/api/core/session.md) for the session API
- [Authentication](/docs/reference/kotlin/concepts/authentication.md) for per-session credentials
- [Transactions](/docs/reference/kotlin/concepts/transactions.md) - transactions run within a session

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/serialization

# Serialisation

Encode and decode records using kotlinx.serialisation with the SurrealDB Kotlin SDK.

The Kotlin SDK uses [`kotlinx.serialization`](https://github.com/Kotlin/kotlinx.serialization) as its data model. Payloads are sent and received as JSON, so you work with [`JsonElement`](/docs/reference/kotlin/concepts/value-types.md) values directly, or decode them into your own [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) types.

## Defining serializable types

Annotate your data classes with `@Serializable`. Field names map directly to record fields; use `@SerialName` to map a different key.

```kotlin
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class Person(
    val name: String,
    val age: Int,
    @SerialName("created_at") val createdAt: String? = null,
)
```

## Decoding query results

The inline [`.queryAs<T>()`](/docs/reference/kotlin/api/core/surreal-client.md#query-as) helper runs a query and decodes its result into `T`, while every fluent builder offers the typed [`.awaitAs<T>()`](/docs/reference/kotlin/api/core/query-builder.md#await-as) terminal.

```kotlin
import com.surrealdb.kotlin.query.Table
import com.surrealdb.kotlin.query.awaitAs

// Via raw SurrealQL
val people: List<Person> = client.queryAs("SELECT * FROM person")

// Via a builder
val sameAgain: List<Person> = client.select(Table("person")).awaitAs()
```

To decode an arbitrary [`JsonElement`](/docs/reference/kotlin/concepts/value-types.md) you already hold, use [`.decode<T>()`](/docs/reference/kotlin/api/core/surreal-client.md#decode).

```kotlin
val element = client.query("SELECT * FROM person:ada")
val ada: Person = client.decode(element)
```

## Building payloads

When writing data, build a [`JsonObject`](/docs/reference/kotlin/concepts/value-types.md) with [`buildJsonObject`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html), or encode a serializable instance with the client's [`json`](/docs/reference/kotlin/api/core/surreal-client.md) instance.

```kotlin
import kotlinx.serialization.json.encodeToJsonElement

val data = client.json.encodeToJsonElement(Person("Ada", 36))
client.create(com.surrealdb.kotlin.query.Table("person")).content(data).await()
```

## Customising the JSON format

The [`json`](/docs/reference/kotlin/api/core/client-config.md#fields) field on [`SurrealClientConfig`](/docs/reference/kotlin/api/core/client-config.md) lets you supply a custom [`Json`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/) instance, for example to register contextual serializers or change null handling. The default ignores unknown keys and is lenient.

```kotlin
import kotlinx.serialization.json.Json

val client = SurrealClient(
    SurrealClientConfig(
        url = "ws://localhost:8000",
        json = Json {
            ignoreUnknownKeys = true
            isLenient = true
            explicitNulls = false
        },
    ),
)
```

## Learn more

- [Value types](/docs/reference/kotlin/concepts/value-types.md) for `Table`, `RecordId`, and the JSON model
- [Executing queries](/docs/reference/kotlin/concepts/executing-queries.md) for `queryAs`
- [Query builder reference](/docs/reference/kotlin/api/core/query-builder.md) for `awaitAs`
- [kotlinx.serialisation](https://kotlinlang.org/docs/serialization.html) for the underlying library

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/transactions

# Transactions

Group operations into atomic transactions with the SurrealDB Kotlin SDK.

[Transactions](/docs/reference/query-language/language-primitives/transactions.md) group multiple operations so they either all succeed or all fail together. The Kotlin SDK exposes transactions as extension functions on a [session](/docs/reference/kotlin/concepts/multiple-sessions.md): a block form that commits or cancels automatically, and an explicit form for manual control.

> [!NOTE]
> Transactions require a stateful connection and are only available over the **WebSocket** transport. Check support with [`client.supports(SurrealFeature.Transactions)`](/docs/reference/kotlin/api/core/surreal-client.md#supports).

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/transaction.md#transaction-block"><code>session.transaction { }</code></a></td>
			<td scope="row" data-label="Description">Runs a block, committing or cancelling automatically</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/transaction.md#begin-transaction"><code>session.beginTransaction()</code></a></td>
			<td scope="row" data-label="Description">Begins a transaction explicitly</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/transaction.md#commit"><code>tx.commit()</code></a></td>
			<td scope="row" data-label="Description">Commits the transaction</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/kotlin/api/core/transaction.md#cancel"><code>tx.cancel()</code></a></td>
			<td scope="row" data-label="Description">Cancels the transaction</td>
		</tr>
	</tbody>
</table>

## Block form

The [`transaction { }`](/docs/reference/kotlin/api/core/transaction.md#transaction-block) builder runs your block against a [`SurrealTransaction`](/docs/reference/kotlin/api/core/transaction.md), commits it if the block returns normally, and cancels it if the block throws. The transaction is itself a queryable, so all the [CRUD builders](/docs/reference/kotlin/concepts/data-manipulation.md) are available scoped to it.

```kotlin
import com.surrealdb.kotlin.query.RecordId
import com.surrealdb.kotlin.transaction
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

client.transaction {
    create(RecordId("person", "tx"))
        .content(buildJsonObject { put("name", "Tx") })
        .await()

    update(RecordId("counter", "1"))
        .content(buildJsonObject { put("hits", 2) })
        .await()
}
```

## Explicit form

For finer control, begin a transaction with [`.beginTransaction()`](/docs/reference/kotlin/api/core/transaction.md#begin-transaction) and commit or cancel it yourself.

```kotlin
import com.surrealdb.kotlin.query.Table
import com.surrealdb.kotlin.beginTransaction
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

val tx = client.beginTransaction()
try {
    tx.create(Table("person"))
        .content(buildJsonObject { put("name", "Ada") })
        .await()
    tx.commit()
} catch (cause: Throwable) {
    tx.cancel()
    throw cause
}
```

## Learn more

- [Transaction reference](/docs/reference/kotlin/api/core/transaction.md) for the full API
- [Multiple sessions](/docs/reference/kotlin/concepts/multiple-sessions.md) - transactions run within a session
- [SurrealQL transactions](/docs/reference/query-language/language-primitives/transactions.md) for transaction semantics

---

Source: https://surrealdb.com/docs/reference/kotlin/concepts/value-types

# Value types

Work with record identifiers, tables, and the JSON value model in the SurrealDB Kotlin SDK.

The Kotlin SDK represents data with [`kotlinx.serialization`](/docs/reference/kotlin/concepts/serialization.md) JSON values, plus a small set of dedicated types for referring to tables and records. These are the building blocks passed to the [CRUD builders](/docs/reference/kotlin/concepts/data-manipulation.md) and queries.

## Record and table types

| Type | Description |
|---|---|
| [`Table`](/docs/reference/kotlin/api/values/table.md) | Refers to a table by name |
| [`RecordId`](/docs/reference/kotlin/api/values/record-id.md) | Refers to a single record (`table:id`) |
| [`RecordIdRange`](/docs/reference/kotlin/api/values/record-id-range.md) | Refers to a range of records within a table |

```kotlin
import com.surrealdb.kotlin.query.RecordId
import com.surrealdb.kotlin.query.Table

val table = Table("person")
val record = RecordId("person", "ada")
```

When these are bound into a query, the SDK renders them with the appropriate SurrealDB casting functions - `type::table(name)` for a [`Table`](/docs/reference/kotlin/api/values/table.md) and `type::record(table, id)` for a [`RecordId`](/docs/reference/kotlin/api/values/record-id.md) - so values are always passed safely.

## The JSON value model

Beyond the record types above, all data is modelled as [`JsonElement`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/). Read methods return a [`JsonElement`](/docs/reference/kotlin/api/values/value.md), and write methods accept a [`JsonObject`](/docs/reference/kotlin/api/values/value.md). Build payloads with [`buildJsonObject`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html).

```kotlin
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

val data = buildJsonObject {
    put("name", "Ada")
    put("age", 36)
}
```

> [!NOTE]
> The SDK does not ship dedicated wrapper classes for datetimes, durations, or geometries. Express these as SurrealQL literals in a query, or as the JSON values that SurrealDB expects, and decode results into your own [`@Serializable`](/docs/reference/kotlin/concepts/serialization.md) types.

## Learn more

- [`Table`](/docs/reference/kotlin/api/values/table.md), [`RecordId`](/docs/reference/kotlin/api/values/record-id.md), and [`RecordIdRange`](/docs/reference/kotlin/api/values/record-id-range.md) references
- [Value (JSON model)](/docs/reference/kotlin/api/values/value.md) reference
- [Serialisation](/docs/reference/kotlin/concepts/serialization.md) for decoding into your own types

---

Source: https://surrealdb.com/docs/reference/kotlin/installation

# Installation

The SurrealDB SDK for Kotlin is published to Maven Central and can be installed using Gradle or Maven.

The SurrealDB SDK for Kotlin is distributed through [Maven Central](https://central.sonatype.com/) under the `com.surrealdb` group. You can add it to your project using [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/).

> [!NOTE]
> The Kotlin SDK is in early development. The current version is `0.1.0-SNAPSHOT` and is **not yet published** to Maven Central. Until the first release is cut, the coordinates below are provisional, and you may need to build the SDK locally with `./gradlew publishToMavenLocal`.

## Requirements

The SDK is a [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library built with Kotlin `2.1.x`. It supports the following targets:

| Target | Requirement |
|---|---|
| JVM | Java 11 or later |
| Android | `minSdk` 26 or later, Java 11 |
| iOS | `iosX64`, `iosArm64`, `iosSimulatorArm64` |

It depends on Kotlin Coroutines, [`kotlinx.serialization`](https://github.com/Kotlin/kotlinx.serialization), [`kotlinx.datetime`](https://github.com/Kotlin/kotlinx-datetime), and [Ktor](https://ktor.io/) for transport. Embedded (in-process) databases are intentionally not included in this release; connect to a running SurrealDB instance over WebSocket or HTTP instead.

## Install the SDK

For a Kotlin Multiplatform project, add the metadata artifact `com.surrealdb:kotlin` to your `commonMain` source set. For a single-platform project, you may instead depend on the platform-specific variant (`kotlin-jvm`, `kotlin-android`, `kotlin-iosarm64`, `kotlin-iossimulatorarm64`, or `kotlin-iosx64`).

**Gradle (Kotlin)**

```kotlin
val surrealdbVersion = "0.1.0-SNAPSHOT"

dependencies {
    implementation("com.surrealdb:kotlin:$surrealdbVersion")
}
```

**Gradle (Groovy)**

```groovy
ext {
    surrealdbVersion = "0.1.0-SNAPSHOT"
}

dependencies {
    implementation "com.surrealdb:kotlin:${surrealdbVersion}"
}
```

**Maven**

```xml
<dependency>
    <groupId>com.surrealdb</groupId>
    <artifactId>kotlin-jvm</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>
```

> [!NOTE]
> Maven projects cannot resolve the Kotlin Multiplatform metadata artifact, so use a platform-specific variant such as `kotlin-jvm`.

## Import the SDK

After installing, import the client from the `com.surrealdb.kotlin` package.

```kotlin
import com.surrealdb.kotlin.SurrealClient
import com.surrealdb.kotlin.SurrealClientConfig
```

## Next steps

- [Getting started](/docs/languages/kotlin.md) for a complete working example
- [Connecting to SurrealDB](/docs/reference/kotlin/concepts/connecting-to-surrealdb.md) for connection options and protocols
- [Authentication](/docs/reference/kotlin/concepts/authentication.md) for signing in and managing credentials

---

Source: https://surrealdb.com/docs/reference/mojo

# Mojo SDK

The official SurrealDB SDK for Mojo. Simple and advanced querying of a remote database over HTTP, HTTPS, and WebSocket.

The SurrealDB SDK for Mojo lets you connect to a SurrealDB instance from your Mojo applications and run queries, manage data, call database functions, authenticate, and subscribe to changes with live queries. It speaks both CBOR-RPC and JSON-RPC over the same `/rpc` endpoint, and ships transports for `http://`, `https://`, `ws://`, and `wss://`.

The SDK has no third-party Mojo dependencies. The transport sits on a small libc-socket layer, and TLS is backed by a thin OpenSSL FFI with certificate verification against the system root store.

> [!IMPORTANT]
> The SDK requires Mojo `>= 0.26.1.0, < 0.26.2.0`, and is managed with [pixi](https://pixi.sh/). The current SDK version is `0.2.0`.

> [!NOTE]
> The SDK works with SurrealDB `3.x`, ensuring compatibility with the latest version, `v3.3.4`.

## Getting started

- [Installation](/docs/reference/mojo/installation.md) - Install the SDK and add it to your project.

- [Getting started guide](/docs/languages/mojo.md) - Connect to SurrealDB and run your first queries.

## Learn

- [Concepts](/docs/reference/mojo/concepts/connecting-to-surrealdb.md) - Guides for connecting, authenticating, querying, and working with data.

- [API Reference](/docs/reference/mojo/methods.md) - Complete reference for the SDK's methods, types, and errors.

## Concepts

- [Connecting to SurrealDB](/docs/reference/mojo/concepts/connecting-to-surrealdb.md) - open a connection over HTTP or WebSocket
- [Authentication](/docs/reference/mojo/concepts/authentication.md) - sign up, sign in, and authenticate with a token
- [Multiple sessions](/docs/reference/mojo/concepts/multiple-sessions.md) - run isolated sessions over a single connection
- [Executing queries](/docs/reference/mojo/concepts/executing-queries.md) - send SurrealQL and read the results back
- [Query builders](/docs/reference/mojo/concepts/query-builders.md) - compose a query without writing the string yourself
- [Value types](/docs/reference/mojo/concepts/value-types.md) - how SurrealDB's types map onto native ones
- [Transactions](/docs/reference/mojo/concepts/transactions.md) - group statements so they succeed or fail together
- [Live queries](/docs/reference/mojo/concepts/live-queries.md) - stream changes as they happen
- [Error handling](/docs/reference/mojo/concepts/error-handling.md) - what a failure looks like, and how to catch it

## Choosing a client

The SDK provides two clients with the same surface:

- `AsyncSurrealClient` for asynchronous applications.
- `SurrealClient`, a thin blocking wrapper around the async client.

```python title="Async client"
from surrealdb import AsyncSurrealClient, ConnectOptions
from std.collections import Optional


def main():
    var client = AsyncSurrealClient()
    _ = client.connect(
        "http://localhost:8000/rpc",
        ConnectOptions(
            namespace=Optional(String("test")),
            database=Optional(String("test")),
            access_token=Optional(String("Basic cm9vdDpyb290")),  # root:secret
        ),
    )

    var resp = client.query("RETURN 1 + 1;")
    if resp.is_ok():
        print("result:", resp.result.value() if resp.result else "null")
    else:
        print("error:", resp.error_message().value())
```

## Transports

HTTP and HTTPS are the recommended transports for everyday querying, and are the most thoroughly tested path. WebSocket (`ws://` and `wss://`) enables SurrealDB's stateful features: authenticated sessions, server-side transactions that span multiple requests, and live-query notifications delivered out of band.

> [!NOTE]
> WebSocket support is rolling out. For request and response querying, including atomic multi-statement transactions, use the HTTP or HTTPS transport.

The wire format is configurable. CBOR is the default and the most compact on the wire; JSON is useful when you want to inspect traffic or proxy through a JSON-only gateway. See [Connecting to SurrealDB](/docs/reference/mojo/concepts/connecting-to-surrealdb.md) for the full set of options.

## Contributing
To contribute to the SDK code, submit an Issue or Pull Request in the [surrealdb.mojo](https://github.com/surrealdb/surrealdb.mojo) repository. To contribute to this documentation, submit an Issue or Pull Request in the [docs.surrealdb.com](https://github.com/surrealdb/docs.surrealdb.com) repository.

## Sources
- [GitHub repository](https://github.com/surrealdb/surrealdb.mojo)

---

Source: https://surrealdb.com/docs/reference/mojo/concepts/authentication

# Authentication

Learn how to authenticate the Mojo SDK with SurrealDB using access tokens and the signin RPC.

The Mojo SDK authenticates in one of two ways: with an access token supplied on the connection, or with the `signin` RPC over a stateful WebSocket session.

## Access tokens

The `access_token` field on `ConnectOptions` is placed in the `Authorization` header. If the value already starts with `Bearer `, `Basic `, or `Digest `, it is passed through verbatim. Otherwise the SDK treats it as a raw JWT and prepends `Bearer `.

```python
# Basic auth (root user, dev fixtures)
ConnectOptions(access_token=Optional(String("Basic cm9vdDpyb290")))

# JWT from a previous signin
ConnectOptions(access_token=Optional(String("Bearer eyJhbGciOi...")))

# Raw JWT (the SDK prepends "Bearer ")
ConnectOptions(access_token=Optional(String("eyJhbGciOi...")))
```

This is the recommended approach for the HTTP and HTTPS transports.

## Signing in with credentials

Over a WebSocket session, you can authenticate with the `signin` RPC. Credentials are encoded as a CBOR map with `CborCodec`. On success, `signin()` stores the returned token on the client for subsequent requests.

```python
from surrealdb import AsyncSurrealClient, CborCodec, ConnectOptions
from std.collections import Optional, List


def main():
    var client = AsyncSurrealClient()
    _ = client.connect(
        "ws://localhost:8000/rpc",
        ConnectOptions(
            namespace=Optional(String("test")),
            database=Optional(String("test")),
        ),
    )

    # Encode the credentials map {"user": "root", "pass": "secret"}.
    var codec = CborCodec()
    var pairs = List[Tuple[String, List[UInt8]]]()
    pairs.append(Tuple(String("user"), codec.encode_text("root")))
    pairs.append(Tuple(String("pass"), codec.encode_text("root")))
    _ = client.signin(codec.encode_map(pairs))
    client.use("test", "test")
```

> [!NOTE]
> `signin`, `signup`, `authenticate`, and `invalidate` operate on a stateful session, which is provided by the WebSocket transport. WebSocket support is rolling out.

## Other auth methods

- `signup(credentials_cbor)` creates a new record-access account and signs in.
- `authenticate(token)` authenticates the current connection with a token, and stores it on the client.
- `invalidate()` clears the current token and invalidates the session.

```python
client.authenticate("eyJhbGciOi...")
client.invalidate()
```

See the method reference for [`signin`](/docs/reference/mojo/methods/signin.md), [`signup`](/docs/reference/mojo/methods/signup.md), [`authenticate`](/docs/reference/mojo/methods/authenticate.md), and [`invalidate`](/docs/reference/mojo/methods/invalidate.md).

---

Source: https://surrealdb.com/docs/reference/mojo/concepts/connecting-to-surrealdb

# Connecting to SurrealDB

Learn how to connect the Mojo SDK to SurrealDB over HTTP, HTTPS, and WebSocket, and how to choose a wire format.

The Mojo SDK connects to a SurrealDB instance with `connect()`, which takes an endpoint URL and an optional `ConnectOptions`. The URL scheme selects the transport.

```python
from surrealdb import AsyncSurrealClient, ConnectOptions
from std.collections import Optional


def main():
    var client = AsyncSurrealClient()
    _ = client.connect(
        "http://localhost:8000/rpc",
        ConnectOptions(
            namespace=Optional(String("test")),
            database=Optional(String("test")),
            access_token=Optional(String("Basic cm9vdDpyb290")),
        ),
    )
```

`connect()` returns a `Bool`. The endpoint path is `/rpc`.

## Transports

The SDK ships transports for four schemes:

| Scheme | Transport | Notes |
|--------|-----------|-------|
| `http://`  | HTTP/1.1 | Request and response querying. The most thoroughly tested path. |
| `https://` | HTTP/1.1 over TLS | Build with `-D HTTPS=1`. See [TLS](#tls). |
| `ws://`    | WebSocket | Stateful sessions, server-side transactions, and live queries. |
| `wss://`   | WebSocket over TLS | Build with `-D HTTPS=1`. |

> [!NOTE]
> WebSocket support is rolling out. For request and response querying, including atomic multi-statement transactions, use the HTTP or HTTPS transport.

## Connection options

`ConnectOptions` carries the namespace, database, credentials, and wire format for the connection.

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Field</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Field"><code>namespace</code></td>
            <td colspan="2" scope="row" data-label="Description">The namespace to use. Sent as the <code>Surreal-NS</code> header.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Field"><code>database</code></td>
            <td colspan="2" scope="row" data-label="Description">The database to use. Sent as the <code>Surreal-DB</code> header.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Field"><code>access_token</code></td>
            <td colspan="2" scope="row" data-label="Description">The credential placed in the <code>Authorization</code> header. See <a href="/docs/reference/mojo/concepts/authentication.md">Authentication</a>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Field"><code>tls_insecure</code></td>
            <td colspan="2" scope="row" data-label="Description">Disables TLS certificate verification. Dev fixtures only. Defaults to <code>False</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Field"><code>format</code></td>
            <td colspan="2" scope="row" data-label="Description"><code>RpcFormat.CBOR</code> (default) or <code>RpcFormat.JSON</code>.</td>
        </tr>
    </tbody>
</table>

The `Surreal-NS` and `Surreal-DB` headers are sent automatically when `namespace` and `database` are set.

## Selecting a namespace and database

You can also switch the namespace and database on an open connection with `use()`:

```python
client.use("test", "test")
```

## Wire format: CBOR or JSON

Both protocols use the same `/rpc` endpoint and the same RPC methods. Pick the wire format with `ConnectOptions.format`:

```python
from surrealdb import AsyncSurrealClient, ConnectOptions, RpcFormat
from std.collections import Optional


def main():
    var client = AsyncSurrealClient()
    _ = client.connect(
        "http://localhost:8000/rpc",
        ConnectOptions(
            namespace=Optional(String("test")),
            database=Optional(String("test")),
            access_token=Optional(String("Basic cm9vdDpyb290")),
            format=RpcFormat.JSON,  # or RpcFormat.CBOR (default)
        ),
    )
    var resp = client.query("RETURN 1 + 1;")
```

Switching `format` swaps the `Content-Type` and `Accept` headers (`application/json` versus `application/cbor`) and the codec used to encode and decode the RPC envelope. Everything else stays the same.

CBOR is the default because it is what the SurrealDB server uses internally and the most compact on the wire. JSON is useful when you want to inspect traffic in DevTools, match the SurrealDB JavaScript SDK behaviour, or proxy through a JSON-only gateway.

## TLS

Certificates are validated against the system root store by default. For self-signed dev fixtures, set `tls_insecure=True`:

```python
ConnectOptions(
    namespace=Optional(String("test")),
    access_token=Optional(String("Basic cm9vdDpyb290")),
    tls_insecure=True,  # never use in production
)
```

For the build flags required to connect over `https://` or `wss://`, see [Build with HTTPS](/docs/reference/mojo/installation.md#build-with-https).

## Connection state

Two helpers report the state of the connection and the features the active transport supports:

```python
if client.is_connected():
    var caps = client.capabilities()
```

`capabilities()` returns an `EngineCapabilities` describing which RPC features the active transport supports (live queries, sessions, server-side transactions, and the API endpoint). The client checks these flags before issuing a request, and raises an `UnsupportedFeatureError` rather than sending a request the server would reject.

Close the connection when you are done:

```python
client.close()
```

---

Source: https://surrealdb.com/docs/reference/mojo/concepts/error-handling

# Error handling

How the Mojo SDK reports errors, both on responses and as raised Mojo errors.

The Mojo SDK reports failures in two places: on the `RpcResponse` for errors the server returns, and as raised Mojo `Error` values for transport and protocol failures.

The error kinds, wire codes and structured shape behind these are documented once in [Errors](/docs/reference/rest-api/errors.md), which applies to every SDK and protocol.

## Response errors

A query that the server rejects comes back as an `RpcResponse` with `is_error()` true. Read the code and message with `error_code()` and `error_message()`.

```python
var resp = client.query("SELECT * FROM person WHERE;")  # invalid

if resp.is_error():
    print("code:", resp.error_code().value())
    print("message:", resp.error_message().value())
```

## Raised errors

Transport and protocol failures raise Mojo `Error` values whose messages carry a typed prefix from `SurrealErrorKind`:

```text
ConnectionError(-1): tcp connect failed to localhost:8000
ProtocolError(-1): http response has no header terminator
UnsupportedFeatureError(-1): websocket transport is not available; use the http engine
```

The kinds live in `surrealdb.errors.SurrealErrorKind` and cover connection, protocol, RPC, query, auth, decode, engine, serialisation, live-query, timeout, and unsupported-feature failures.

The matching helper functions (`fail_connection`, `fail_protocol`, `fail_unsupported_feature`, and the rest) are exported, so you can raise SDK-shaped errors from your own code.

```python
from surrealdb import fail_connection

fn dial() raises:
    fail_connection("tcp connect failed to localhost:8000")
```

## Unsupported features

Before issuing a request that needs a stateful feature, the client checks the active transport's `capabilities()`. If the transport does not support the feature, the SDK raises an `UnsupportedFeatureError` rather than sending a request the server would reject. This is how an HTTP-only connection responds to a live query or a session request.

---

Source: https://surrealdb.com/docs/reference/mojo/concepts/executing-queries

# Executing queries

Learn how to run SurrealQL with the Mojo SDK, use the CRUD convenience methods, and read responses.

The primary way to run SurrealQL with the Mojo SDK is `query()`, which sends one or more statements and returns an `RpcResponse`.

```python
var resp = client.query("SELECT * FROM person WHERE age > 18;")
```

## Reading the response

Every call returns an `RpcResponse`. Check `is_ok()` before reading the result. The decoded text representation is available on `result`, and the raw bytes on `result_raw`.

```python
if resp.is_ok():
    # CBOR-decoded text representation for convenience
    if resp.result:
        print(resp.result.value())
    # Raw CBOR bytes if you need them
    print("bytes:", len(resp.result_raw))
else:
    print("code:", resp.error_code().value())
    print("message:", resp.error_message().value())
```

`RpcResponse` exposes the following:

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Member</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Member"><code>is_ok()</code></td>
            <td colspan="2" scope="row" data-label="Description">Returns <code>True</code> when there is no error.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Member"><code>is_error()</code></td>
            <td colspan="2" scope="row" data-label="Description">Returns <code>True</code> when the response carries an error.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Member"><code>has_result()</code></td>
            <td colspan="2" scope="row" data-label="Description">Returns <code>True</code> when a result is present.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Member"><code>result</code></td>
            <td colspan="2" scope="row" data-label="Description">The decoded text representation, as <code>Optional[String]</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Member"><code>result_raw</code></td>
            <td colspan="2" scope="row" data-label="Description">The raw CBOR or JSON bytes, as <code>List[UInt8]</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Member"><code>error_message()</code></td>
            <td colspan="2" scope="row" data-label="Description">The error message, as <code>Optional[String]</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Member"><code>error_code()</code></td>
            <td colspan="2" scope="row" data-label="Description">The error code, as <code>Optional[Int]</code>.</td>
        </tr>
    </tbody>
</table>

## Convenience methods

The SDK wraps the most common statements so you do not have to write the SurrealQL by hand. Each takes the table or record to act on and a JSON document.

```python
client.create("person", '{ "name": "Chiru", "age": 30 }')
client.select("person:chiru")
client.update("person:chiru", '{ "age": 31 }')
client.delete("person:chiru")
client.insert("person", '[{ "name": "Alice" }, { "name": "Bob" }]')
```

These build a SurrealQL statement internally. For example, `create("person", data)` runs `CREATE person CONTENT <data>;`. See the method reference for the full list, including [`upsert`](/docs/reference/mojo/methods/upsert.md), [`merge`](/docs/reference/mojo/methods/merge.md), [`patch`](/docs/reference/mojo/methods/patch.md), and [`insert_relation`](/docs/reference/mojo/methods/insert-relation.md).

## Bindings

`query()` accepts a `bindings_json` argument.

```python
var resp = client.query("SELECT * FROM person;", "{}")
```

> [!NOTE]
> A dedicated API for passing arbitrary CBOR bindings is on the roadmap. Today, CBOR connections support the default `"{}"`, while JSON-RPC connections accept raw JSON strings via `bindings_json`.

## Sessions and transactions

Each `query` is wrapped in its own implicit transaction by the server. To run several statements atomically, use [`transaction_multi`](/docs/reference/mojo/methods/transaction-multi.md), or the [transactions](/docs/reference/mojo/concepts/transactions.md) concept page for the full picture.

---

Source: https://surrealdb.com/docs/reference/mojo/concepts/live-queries

# Live queries

Subscribe to changes on a table with the Mojo SDK and poll for live-query notifications.

A live query subscribes to a table and receives a notification whenever a matching record is created, updated, or deleted. Notifications arrive on the WebSocket transport out of band, and the SDK queues them as they come in.

> [!NOTE]
> Live queries run over a stateful WebSocket session. WebSocket support is rolling out. On the HTTP transport, the engine reports `live_queries=False` and the SDK raises an `UnsupportedFeatureError`.

## Starting a live query

`live_query()` subscribes to a table and returns the query id as a string. `live_raw()` returns the full `RpcResponse` if you need it.

```python
var query_id = client.live_query("person")
```

## Receiving notifications

The transport queues notifications as they arrive. Pull them out with `poll_notifications()`, which drains every queued notification across all live queries.

```python
var notifications = client.poll_notifications()
for ref in notifications:
    print(ref[].query_id, ref[].action)
```

Each entry is a `LiveNotificationRaw` carrying the query id, the action, and the raw record bytes.

## Stopping a live query

Stop a subscription with `kill()`, passing the query id returned by `live_query()`.

```python
client.kill(query_id)
```

See the method reference for [`live`](/docs/reference/mojo/methods/live.md) and [`kill`](/docs/reference/mojo/methods/kill.md).

---

Source: https://surrealdb.com/docs/reference/mojo/concepts/multiple-sessions

# Multiple sessions

Open and manage independent server-side sessions with the Mojo SDK.

A session is an independent, server-side context for authentication and session variables. Over a stateful WebSocket connection, the Mojo SDK can open several sessions on a single connection and address each one separately.

> [!NOTE]
> Sessions are a stateful feature provided by the WebSocket transport. WebSocket support is rolling out. On the HTTP transport, the engine reports `sessions=False` and the SDK raises an `UnsupportedFeatureError`.

## Opening a session

`new_session()` attaches a new session and returns a handle scoped to it. The handle exposes `use()`, `query()`, `set_string()`, `unset()`, `begin_transaction()`, and `close_session()`.

```python
var session = client.new_session()
session.use("test", "test")
var resp = session.query("SELECT * FROM person;")
session.close_session()
```

## Attaching and detaching

For lower-level control, `attach()` returns a session id you can pass to other calls, and `detach()` releases it.

```python
var session_id = client.attach()
var resp = client.query("SELECT * FROM person;", session=Optional(session_id))
client.detach(session_id)
```

`sessions()` lists the active sessions on the connection.

Most calls on the client, including `query`, `create`, and the auth methods, accept an optional `session` argument so you can target a specific session without a handle.

---

Source: https://surrealdb.com/docs/reference/mojo/concepts/query-builders

# Query builders

Build SurrealQL statements fluently with the Mojo SDK query builders.

The Mojo SDK ships a set of fluent builders that construct SurrealQL statements for you. Each builder is `Copyable` and `Movable`, so you can chain calls or pass it around, and each has a `build()` method that returns the statement as a string.

```python
var qb = client.select_builder("person")
    .fields("id, name, age")
    .where_clause("age >= 18")
    .order_by("age DESC")
    .limit(20)

var resp = client.query_select(qb)
```

`query_select()` runs a `SelectBuilder`. The generic `query_builder()` runs any builder via its `build()` output.

## Available builders

The client exposes a factory method for each builder.

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Builder</th>
            <th colspan="2" scope="col">Factory</th>
            <th colspan="2" scope="col">Methods</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Builder"><code>SelectBuilder</code></td>
            <td colspan="2" scope="row" data-label="Factory"><code>select_builder(target)</code></td>
            <td colspan="2" scope="row" data-label="Methods"><code>fields</code>, <code>where_clause</code>, <code>order_by</code>, <code>limit</code>, <code>start</code>, <code>fetch</code></td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Builder"><code>CreateBuilder</code></td>
            <td colspan="2" scope="row" data-label="Factory"><code>create_builder(target)</code></td>
            <td colspan="2" scope="row" data-label="Methods"><code>content</code>, <code>set_field</code></td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Builder"><code>UpdateBuilder</code></td>
            <td colspan="2" scope="row" data-label="Factory"><code>update_builder(target)</code></td>
            <td colspan="2" scope="row" data-label="Methods"><code>content</code>, <code>merge</code>, <code>patch</code>, <code>replace</code>, <code>where_clause</code></td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Builder"><code>UpsertBuilder</code></td>
            <td colspan="2" scope="row" data-label="Factory"><code>upsert_builder(target)</code></td>
            <td colspan="2" scope="row" data-label="Methods"><code>content</code>, <code>merge</code></td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Builder"><code>DeleteBuilder</code></td>
            <td colspan="2" scope="row" data-label="Factory"><code>delete_builder(target)</code></td>
            <td colspan="2" scope="row" data-label="Methods"><code>where_clause</code></td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Builder"><code>InsertBuilder</code></td>
            <td colspan="2" scope="row" data-label="Factory"><code>insert_builder(table)</code></td>
            <td colspan="2" scope="row" data-label="Methods"><code>values</code>, <code>relation</code></td>
        </tr>
    </tbody>
</table>

## Examples

Build and inspect a statement without running it:

```python
var qb = client.select_builder("person")
    .fields("name, age")
    .where_clause("age >= 18")
    .limit(10)

print(qb.build())  # SELECT name, age FROM person WHERE age >= 18 LIMIT 10;
```

Create a record:

```python
var cb = client.create_builder("person").content('{ "name": "Chiru" }')
var resp = client.query(cb.build())
```

> [!NOTE]
> The older `QueryBuilder` is kept for backwards compatibility. New code should use the builders above.

---

Source: https://surrealdb.com/docs/reference/mojo/concepts/transactions

# Transactions

Run multiple statements atomically with the Mojo SDK over HTTP or a WebSocket session.

SurrealDB wraps every `query` RPC in its own implicit `BEGIN`/`COMMIT`, so a transaction has to live inside a single multi-statement query. The Mojo SDK gives you two ways to do this.

## Atomic multi-statement transactions

`transaction_multi` takes a list of statements, wraps them in `BEGIN TRANSACTION;` and `COMMIT TRANSACTION;`, and sends them as a single atomic query. This works on any transport and is the recommended approach over HTTP.

```python
var stmts = List[String]()
stmts.append("CREATE car:a SET wheels = 4;")
stmts.append("CREATE car:b SET wheels = 4;")
var resp = client.transaction_multi(stmts)
```

If any statement fails, the whole transaction is rolled back.

## Session transactions

Over a stateful WebSocket session, `begin_transaction()` returns a handle that buffers statements and flushes them on `commit()`. The handle exposes `query()`, `create()`, `select()`, `commit()`, and `cancel()`.

```python
from surrealdb import AsyncSurrealClient, CborCodec, ConnectOptions
from std.collections import Optional, List


def main():
    var client = AsyncSurrealClient()
    _ = client.connect(
        "ws://localhost:8000/rpc",
        ConnectOptions(
            namespace=Optional(String("test")),
            database=Optional(String("test")),
        ),
    )

    var codec = CborCodec()
    var pairs = List[Tuple[String, List[UInt8]]]()
    pairs.append(Tuple(String("user"), codec.encode_text("root")))
    pairs.append(Tuple(String("pass"), codec.encode_text("root")))
    _ = client.signin(codec.encode_map(pairs))
    client.use("test", "test")

    # BEGIN, do work, COMMIT.
    var txn = client.begin_transaction()
    _ = txn.query("CREATE person:alice SET age = 30;")
    _ = txn.query("CREATE person:bob   SET age = 31;")
    txn.commit()
```

Call `cancel()` instead of `commit()` to discard the buffered statements.

> [!NOTE]
> Session transactions run over a stateful WebSocket session. WebSocket support is rolling out; for atomic transactions over HTTP, use `transaction_multi`.

See the method reference for [`transaction_multi`](/docs/reference/mojo/methods/transaction-multi.md) and [`begin_transaction`](/docs/reference/mojo/methods/begin-transaction.md).

---

Source: https://surrealdb.com/docs/reference/mojo/concepts/value-types

# Value types

The SurrealDB tagged value types that the Mojo SDK encodes and decodes.

The Mojo SDK ships CBOR encoders and decoders for the SurrealDB tagged value types. These types live in the `surrealdb.value` module and map directly onto the values SurrealDB stores and returns.

## Record identifiers

A `RecordId` pairs a table with a record key. Its `to_string()` returns the canonical `table:id` form.

```python
from surrealdb import RecordId, Table

var id = RecordId(Table("person"), "chiru")
print(id.to_string())  # person:chiru
```

`Table` wraps a table name on its own.

## Scalar types

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Fields</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Type"><code>Uuid</code></td>
            <td colspan="2" scope="row" data-label="Fields"><code>value</code></td>
            <td colspan="2" scope="row" data-label="Description">A UUID string.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Type"><code>DateTime</code></td>
            <td colspan="2" scope="row" data-label="Fields"><code>seconds</code>, <code>nanos</code></td>
            <td colspan="2" scope="row" data-label="Description">A point in time, split into seconds and nanoseconds.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Type"><code>Duration</code></td>
            <td colspan="2" scope="row" data-label="Fields"><code>seconds</code>, <code>nanos</code></td>
            <td colspan="2" scope="row" data-label="Description">A length of time.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Type"><code>Decimal</code></td>
            <td colspan="2" scope="row" data-label="Fields"><code>value</code></td>
            <td colspan="2" scope="row" data-label="Description">An arbitrary-precision decimal, held as a string.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Type"><code>FileRef</code></td>
            <td colspan="2" scope="row" data-label="Fields"><code>bucket</code>, <code>key</code></td>
            <td colspan="2" scope="row" data-label="Description">A reference to a file in a bucket.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Type"><code>SurrealSet</code></td>
            <td colspan="2" scope="row" data-label="Fields"><code>items</code></td>
            <td colspan="2" scope="row" data-label="Description">A set of values.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Type"><code>FutureValue</code></td>
            <td colspan="2" scope="row" data-label="Fields"><code>body</code></td>
            <td colspan="2" scope="row" data-label="Description">A computed (future) value, held as its expression body.</td>
        </tr>
    </tbody>
</table>

## Ranges

A `RangeValue` describes a bounded range. Its bounds are `BoundIncluded` or `BoundExcluded`, set on the `begin_included`, `begin_excluded`, `end_included`, and `end_excluded` fields.

## Geometry

The SDK models the full GeoJSON family:

- `GeometryPoint` with `longitude` and `latitude`.
- `GeometryLine` and `GeometryPolygon`.
- `GeometryMultiPoint`, `GeometryMultiLine`, and `GeometryMultiPolygon`.
- `GeometryCollection`, holding points, lines, and polygons together.

```python
from surrealdb import GeometryPoint

var here = GeometryPoint(-0.118092, 51.509865)
```

---

Source: https://surrealdb.com/docs/reference/mojo/installation

# Installation

In this section, you will learn how to install the SurrealDB Mojo SDK and add it to your project.

This section covers installing the SurrealDB Mojo SDK and adding it to your project.

## Requirements

The only runtime dependency is Mojo itself, pinned to `0.26.1.x`. The project uses [pixi](https://pixi.sh/) to manage the Mojo toolchain.

| Platform | Architecture | Status |
|----------|--------------|--------|
| macOS    | arm64        | Tested |
| Linux    | x86_64       | Tested |
| Linux    | aarch64      | Tested |
| Windows  | n/a          | Use WSL2; Mojo has no native Windows toolchain |

## Install the SDK

Clone the repository and install the toolchain with pixi:

```bash
git clone https://github.com/surrealdb/surrealdb.mojo
cd surrealdb.mojo
pixi install
```

To use the SDK from your own project, either vendor `src/surrealdb` into your Mojo package path, or build it into a `.mojopkg`:

```bash
pixi run check     # produces build/surrealdb.mojopkg
```

## Import the SDK into your project

Import the client and connection options from the `surrealdb` package:

```python
from surrealdb import AsyncSurrealClient, ConnectOptions
from std.collections import Optional
```

For the blocking client, import `SurrealClient` instead:

```python
from surrealdb import SurrealClient
```

## Run your program

Run a Mojo file with the SDK source on the include path:

```bash
pixi run mojo run -I src yourfile.mojo
```

## Build with HTTPS

The TLS code sits behind a compile-time gate, so `mojo run` invocations without HTTPS keep working with no OpenSSL on the system. To connect over `https://` or `wss://`, build your program with `mojo build`, define the `HTTPS` flag, and link against `libssl` and `libcrypto` from the pixi environment:

```bash
PIXI_ENV=$(pwd)/.pixi/envs/default
pixi run mojo build -I src -o app \
  -D HTTPS=1 \
  -Xlinker -L$PIXI_ENV/lib \
  -Xlinker -lssl -Xlinker -lcrypto \
  yourfile.mojo

DYLD_LIBRARY_PATH=$PIXI_ENV/lib ./app  # macOS
LD_LIBRARY_PATH=$PIXI_ENV/lib ./app    # Linux
```

If you use an `https://` URL without `-D HTTPS=1`, the SDK raises a `ConnectionError` that names the build flags you need.

## Next steps

- [Getting started](/docs/languages/mojo.md) for a complete working example.
- [Connecting to SurrealDB](/docs/reference/mojo/concepts/connecting-to-surrealdb.md) for connection options and transports.
- [Authentication](/docs/reference/mojo/concepts/authentication.md) for signing in and managing credentials.

---

Source: https://surrealdb.com/docs/reference/mojo/methods

# SDK methods

The full method reference for the SurrealDB Mojo SDK.

Most methods in the SurrealDB Mojo SDK are called on an instance of `AsyncSurrealClient` or its blocking wrapper `SurrealClient`. Both expose the same surface.

The table below lists documented methods **in alphabetical order** (by page name).

## All methods

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/api.md"><code>client.api()</code></a></td>
			<td scope="row" data-label="Description">Calls a custom API handler defined on the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/authenticate.md"><code>client.authenticate()</code></a></td>
			<td scope="row" data-label="Description">Authenticates the current connection with a token</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/begin-transaction.md"><code>client.begin_transaction()</code></a></td>
			<td scope="row" data-label="Description">Starts a session-scoped transaction, returning a handle</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/call.md"><code>client.call()</code></a></td>
			<td scope="row" data-label="Description">Runs a SurrealQL function</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/close.md"><code>client.close()</code></a></td>
			<td scope="row" data-label="Description">Closes the connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/connect.md"><code>client.connect()</code></a></td>
			<td scope="row" data-label="Description">Connects to a database endpoint</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/create.md"><code>client.create()</code></a></td>
			<td scope="row" data-label="Description">Creates a record in the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/delete.md"><code>client.delete()</code></a></td>
			<td scope="row" data-label="Description">Deletes all records, or a specific record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/health.md"><code>client.health()</code></a></td>
			<td scope="row" data-label="Description">Runs a health check against the server</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/insert.md"><code>client.insert()</code></a></td>
			<td scope="row" data-label="Description">Inserts one or more records into a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/insert-relation.md"><code>client.insert_relation()</code></a></td>
			<td scope="row" data-label="Description">Inserts one or more relations into a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/invalidate.md"><code>client.invalidate()</code></a></td>
			<td scope="row" data-label="Description">Invalidates the authentication for the current connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/kill.md"><code>client.kill()</code></a></td>
			<td scope="row" data-label="Description">Stops a running live query</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/live.md"><code>client.live_query()</code></a></td>
			<td scope="row" data-label="Description">Starts a live query on a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/merge.md"><code>client.merge()</code></a></td>
			<td scope="row" data-label="Description">Merges data into a record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/patch.md"><code>client.patch()</code></a></td>
			<td scope="row" data-label="Description">Applies a JSON Patch to a record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/query.md"><code>client.query()</code></a></td>
			<td scope="row" data-label="Description">Runs a set of SurrealQL statements against the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/select.md"><code>client.select()</code></a></td>
			<td scope="row" data-label="Description">Selects all records in a table, or a specific record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/set.md"><code>client.set()</code></a></td>
			<td scope="row" data-label="Description">Assigns a value as a parameter for this connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/signin.md"><code>client.signin()</code></a></td>
			<td scope="row" data-label="Description">Signs in to the database with credentials</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/signup.md"><code>client.signup()</code></a></td>
			<td scope="row" data-label="Description">Signs up to a record-access method</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/transaction-multi.md"><code>client.transaction_multi()</code></a></td>
			<td scope="row" data-label="Description">Runs a list of statements as one atomic transaction</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/unset.md"><code>client.unset()</code></a></td>
			<td scope="row" data-label="Description">Removes a parameter for this connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/update.md"><code>client.update()</code></a></td>
			<td scope="row" data-label="Description">Updates all records in a table, or a specific record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/upsert.md"><code>client.upsert()</code></a></td>
			<td scope="row" data-label="Description">Upserts all records in a table, or a specific record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/use.md"><code>client.use()</code></a></td>
			<td scope="row" data-label="Description">Switches to a specific namespace and database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/mojo/methods/version.md"><code>client.version()</code></a></td>
			<td scope="row" data-label="Description">Returns the database version</td>
		</tr>
	</tbody>
</table>

---

Source: https://surrealdb.com/docs/reference/mojo/methods/api

# api

The api() method for the SurrealDB Mojo SDK calls a custom API handler defined on the database.

Calls a custom API handler defined on the database with [`DEFINE API`](/docs/reference/query-language/statements/define/api.md).

```python title="Method Syntax"
client.api(path, method, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>path</code></td>
            <td colspan="2" scope="row" data-label="Description">The API path to call.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>method</code></td>
            <td colspan="2" scope="row" data-label="Description">The HTTP method. Defaults to <code>"GET"</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var resp = client.api("/users", "GET")
```

> [!NOTE]
> The API endpoint is checked against the active transport's capabilities. If the transport does not support it, the SDK raises an `UnsupportedFeatureError`.

---

Source: https://surrealdb.com/docs/reference/mojo/methods/authenticate

# authenticate

The authenticate() method for the SurrealDB Mojo SDK authenticates the current connection with a token.

Authenticates the current connection with a token, and stores it on the client for subsequent requests.

```python title="Method Syntax"
client.authenticate(token, session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>token</code></td>
            <td colspan="2" scope="row" data-label="Description">The JWT to authenticate with.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
client.authenticate("eyJhbGciOi...")
```

## See also

- [Authentication](/docs/reference/mojo/concepts/authentication.md)
- [`invalidate()`](/docs/reference/mojo/methods/invalidate.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/begin-transaction

# begin_transaction

The begin_transaction() method for the SurrealDB Mojo SDK starts a session-scoped transaction.

Starts a session-scoped transaction and returns a handle. The handle buffers statements and flushes them on `commit()`.

```python title="Method Syntax"
client.begin_transaction(session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Handle methods

The returned handle exposes:

- `query(query, bindings_json)` to buffer a statement.
- `create(thing, content_json)` and `select(thing)` convenience wrappers.
- `commit()` to run the buffered statements atomically.
- `cancel()` to discard them.

## Example usage

```python
var txn = client.begin_transaction()
_ = txn.query("CREATE person:alice SET age = 30;")
_ = txn.query("CREATE person:bob   SET age = 31;")
txn.commit()
```

> [!NOTE]
> Session transactions run over a stateful WebSocket session. WebSocket support is rolling out. For atomic transactions over HTTP, use [`transaction_multi()`](/docs/reference/mojo/methods/transaction-multi.md).

## See also

- [Transactions](/docs/reference/mojo/concepts/transactions.md)
- [`transaction_multi()`](/docs/reference/mojo/methods/transaction-multi.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/call

# call

The call() method for the SurrealDB Mojo SDK runs a SurrealQL function.

Runs a SurrealQL function, built-in or custom, and returns its result.

```python title="Method Syntax"
client.call(fn_name, args, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>fn_name</code></td>
            <td colspan="2" scope="row" data-label="Description">The function name, for example <code>fn::greet</code> or <code>time::now</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>args</code></td>
            <td colspan="2" scope="row" data-label="Description">The function arguments, each CBOR-encoded, as a <code>List[List[UInt8]]</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
from surrealdb import CborCodec
from std.collections import List

var codec = CborCodec()
var args = List[List[UInt8]]()
args.append(codec.encode_text("Chiru"))

var resp = client.call("fn::greet", args)
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/close

# close

The close() method for the SurrealDB Mojo SDK closes the connection.

Closes the connection and releases the active transport.

```python title="Method Syntax"
client.close()
```

## Arguments

This method takes no arguments.

## Example usage

```python
client.close()
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/connect

# connect

The connect() method for the SurrealDB Mojo SDK connects to a database endpoint.

Connects to a database endpoint. The URL scheme selects the transport (`http`, `https`, `ws`, or `wss`).

```python title="Method Syntax"
client.connect(endpoint, connect_options)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>endpoint</code></td>
            <td colspan="2" scope="row" data-label="Description">The database endpoint to connect to, ending in <code>/rpc</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>connect_options</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional <code>ConnectOptions</code> carrying the namespace, database, credentials, TLS setting, and wire format.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
from surrealdb import AsyncSurrealClient, ConnectOptions
from std.collections import Optional


def main():
    var client = AsyncSurrealClient()
    _ = client.connect(
        "http://localhost:8000/rpc",
        ConnectOptions(
            namespace=Optional(String("test")),
            database=Optional(String("test")),
            access_token=Optional(String("Basic cm9vdDpyb290")),
        ),
    )
```

`connect()` returns a `Bool`.

## See also

- [Connecting to SurrealDB](/docs/reference/mojo/concepts/connecting-to-surrealdb.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/create

# create

The create() method for the SurrealDB Mojo SDK creates a record in the database.

Creates a record in the database. This is a convenience wrapper that runs `CREATE <thing> CONTENT <content_json>;`.

```python title="Method Syntax"
client.create(thing, content_json, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>thing</code></td>
            <td colspan="2" scope="row" data-label="Description">The table or specific record to create.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>content_json</code></td>
            <td colspan="2" scope="row" data-label="Description">The record content as a JSON string.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var resp = client.create("person", '{ "name": "Chiru", "age": 30 }')
```

## Translated query

```surql
CREATE $thing CONTENT $content_json;
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/delete

# delete

The delete() method for the SurrealDB Mojo SDK deletes all records in a table, or a specific record.

Deletes all records in a table, or a specific record. This is a convenience wrapper that runs `DELETE <thing>;`.

```python title="Method Syntax"
client.delete(thing, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>thing</code></td>
            <td colspan="2" scope="row" data-label="Description">The table or specific record to delete.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
# Delete a specific record
var resp = client.delete("person:chiru")

# Delete every record in a table
var cleared = client.delete("person")
```

## Translated query

```surql
DELETE $thing;
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/health

# health

The health() method for the SurrealDB Mojo SDK runs a health check against the server.

Runs a health check to verify that the server is reachable and accepting commands.

```python title="Method Syntax"
client.health()
```

## Arguments

This method takes no arguments.

## Example usage

```python
client.health()
```

If the server is unreachable, the call raises a `ConnectionError`.

---

Source: https://surrealdb.com/docs/reference/mojo/methods/insert

# insert

The insert() method for the SurrealDB Mojo SDK inserts one or more records into a table.

Inserts one or more records into a table. This is a convenience wrapper that runs `INSERT INTO <table> <data_json>;`.

```python title="Method Syntax"
client.insert(table, data_json, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>table</code></td>
            <td colspan="2" scope="row" data-label="Description">The table to insert into.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>data_json</code></td>
            <td colspan="2" scope="row" data-label="Description">A single record or an array of records, as a JSON string.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var resp = client.insert("person", '[{ "name": "Alice" }, { "name": "Bob" }]')
```

## Translated query

```surql
INSERT INTO $table $data_json;
```

## See also

- [`insert_relation()`](/docs/reference/mojo/methods/insert-relation.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/insert-relation

# insert_relation

The insert_relation() method for the SurrealDB Mojo SDK inserts one or more relations into a table.

Inserts one or more relation records into a table. This is a convenience wrapper that runs `INSERT RELATION INTO <table> <data_json>;`.

```python title="Method Syntax"
client.insert_relation(table, data_json, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>table</code></td>
            <td colspan="2" scope="row" data-label="Description">The relation table to insert into.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>data_json</code></td>
            <td colspan="2" scope="row" data-label="Description">A single relation or an array of relations, as a JSON string. Each carries an <code>in</code> and an <code>out</code> record.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var resp = client.insert_relation(
    "likes",
    '[{ "in": "person:alice", "out": "person:bob" }]',
)
```

## Translated query

```surql
INSERT RELATION INTO $table $data_json;
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/invalidate

# invalidate

The invalidate() method for the SurrealDB Mojo SDK invalidates the authentication for the current connection.

Invalidates the current session and clears the stored token.

```python title="Method Syntax"
client.invalidate(session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
client.invalidate()
```

## See also

- [Authentication](/docs/reference/mojo/concepts/authentication.md)
- [`authenticate()`](/docs/reference/mojo/methods/authenticate.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/kill

# kill

The kill() method for the SurrealDB Mojo SDK stops a running live query.

Stops a running live query, using the query id returned by [`live_query()`](/docs/reference/mojo/methods/live.md).

```python title="Method Syntax"
client.kill(query_id, session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>query_id</code></td>
            <td colspan="2" scope="row" data-label="Description">The id of the live query to stop.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var query_id = client.live_query("person")
# ... later ...
client.kill(query_id)
```

> [!NOTE]
> Live queries run over a stateful WebSocket session. WebSocket support is rolling out. On the HTTP transport, the SDK raises an `UnsupportedFeatureError`.

## See also

- [Live queries](/docs/reference/mojo/concepts/live-queries.md)
- [`live_query()`](/docs/reference/mojo/methods/live.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/live

# live_query

The live_query() method for the SurrealDB Mojo SDK starts a live query on a table.

Starts a live query on a table and returns the query id as a string. Notifications arrive out of band and are queued by the transport; pull them out with `poll_notifications()`.

```python title="Method Syntax"
client.live_query(table, session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>table</code></td>
            <td colspan="2" scope="row" data-label="Description">The table to subscribe to.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var query_id = client.live_query("person")

var notifications = client.poll_notifications()
for ref in notifications:
    print(ref[].query_id, ref[].action)
```

To get the full `RpcResponse` instead of just the query id, use `live_raw(table, session)`.

> [!NOTE]
> Live queries run over a stateful WebSocket session. WebSocket support is rolling out. On the HTTP transport, the engine reports `live_queries=False` and the SDK raises an `UnsupportedFeatureError`.

## See also

- [Live queries](/docs/reference/mojo/concepts/live-queries.md)
- [`kill()`](/docs/reference/mojo/methods/kill.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/merge

# merge

The merge() method for the SurrealDB Mojo SDK merges data into a record.

Merges data into all records in a table, or a specific record, leaving unspecified fields untouched. This is a convenience wrapper that runs `UPDATE <thing> MERGE <data_json>;`.

```python title="Method Syntax"
client.merge(thing, data_json, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>thing</code></td>
            <td colspan="2" scope="row" data-label="Description">The table or specific record to merge into.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>data_json</code></td>
            <td colspan="2" scope="row" data-label="Description">The data to merge, as a JSON string.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var resp = client.merge("person:chiru", '{ "age": 31 }')
```

## Translated query

```surql
UPDATE $thing MERGE $data_json;
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/patch

# patch

The patch() method for the SurrealDB Mojo SDK applies a JSON Patch to a record.

Applies a JSON Patch to all records in a table, or a specific record. This is a convenience wrapper that runs `UPDATE <thing> PATCH <patch_json>;`.

```python title="Method Syntax"
client.patch(thing, patch_json, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>thing</code></td>
            <td colspan="2" scope="row" data-label="Description">The table or specific record to patch.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>patch_json</code></td>
            <td colspan="2" scope="row" data-label="Description">A JSON Patch array as a JSON string.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var resp = client.patch("person:chiru", '[{ "op": "replace", "path": "/age", "value": 31 }]')
```

## Translated query

```surql
UPDATE $thing PATCH $patch_json;
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/query

# query

The query() method for the SurrealDB Mojo SDK runs one or more SurrealQL statements against the database.

Runs one or more SurrealQL statements against the database and returns an `RpcResponse`.

```python title="Method Syntax"
client.query(query, bindings_json, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>query</code></td>
            <td colspan="2" scope="row" data-label="Description">The SurrealQL statements to run.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>bindings_json</code></td>
            <td colspan="2" scope="row" data-label="Description">Optional bindings as a JSON string. Defaults to <code>"{}"</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var resp = client.query("SELECT * FROM person WHERE age > 18;")

if resp.is_ok():
    if resp.result:
        print(resp.result.value())
else:
    print("error:", resp.error_message().value())
```

> [!NOTE]
> A dedicated API for passing arbitrary CBOR bindings is on the roadmap. Today, CBOR connections support the default `"{}"`, while JSON-RPC connections accept raw JSON strings via `bindings_json`.

## See also

- [Executing queries](/docs/reference/mojo/concepts/executing-queries.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/select

# select

The select() method for the SurrealDB Mojo SDK selects all records in a table, or a specific record.

Selects all records in a table, or a specific record. This is a convenience wrapper that runs `SELECT * FROM <thing>;`.

```python title="Method Syntax"
client.select(thing, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>thing</code></td>
            <td colspan="2" scope="row" data-label="Description">The table or specific record to select.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
# Select every record in a table
var people = client.select("person")

# Select a specific record
var chiru = client.select("person:chiru")
```

## Translated query

```surql
SELECT * FROM $thing;
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/set

# set

The set() method for the SurrealDB Mojo SDK assigns a value as a parameter for this connection.

Assigns a value to a parameter for the connection, so you can reference it as `$name` in subsequent queries.

```python title="Method Syntax"
client.set(name, value_cbor, session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>name</code></td>
            <td colspan="2" scope="row" data-label="Description">The parameter name, without the leading <code>$</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>value_cbor</code></td>
            <td colspan="2" scope="row" data-label="Description">The value, CBOR-encoded as a <code>List[UInt8]</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Convenience wrappers

For common scalar types, the SDK provides wrappers that encode the value for you:

- `set_string(name, value, session)`
- `set_int(name, value, session)`
- `set_bool(name, value, session)`

## Example usage

```python
client.set_string("name", "Chiru")
client.set_int("age", 30)

var resp = client.query("SELECT * FROM person WHERE name = $name AND age >= $age;")
```

To encode a value by hand, use `CborCodec`:

```python
from surrealdb import CborCodec

var codec = CborCodec()
client.set("name", codec.encode_text("Chiru"))
```

## See also

- [`unset()`](/docs/reference/mojo/methods/unset.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/signin

# signin

The signin() method for the SurrealDB Mojo SDK signs in to the database with credentials.

Signs in to the database with CBOR-encoded credentials. On success, the returned token is stored on the client for subsequent requests.

```python title="Method Syntax"
client.signin(credentials_cbor, session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>credentials_cbor</code></td>
            <td colspan="2" scope="row" data-label="Description">The credentials, CBOR-encoded as a map, as a <code>List[UInt8]</code>.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
from surrealdb import CborCodec
from std.collections import List

var codec = CborCodec()
var pairs = List[Tuple[String, List[UInt8]]]()
pairs.append(Tuple(String("user"), codec.encode_text("root")))
pairs.append(Tuple(String("pass"), codec.encode_text("root")))

var resp = client.signin(codec.encode_map(pairs))
```

> [!NOTE]
> `signin` operates on a stateful session, which is provided by the WebSocket transport. WebSocket support is rolling out. Over HTTP, supply credentials with the `access_token` field on `ConnectOptions`.

## See also

- [Authentication](/docs/reference/mojo/concepts/authentication.md)
- [`signup()`](/docs/reference/mojo/methods/signup.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/signup

# signup

The signup() method for the SurrealDB Mojo SDK signs up to a record-access method.

Creates a new record-access account from CBOR-encoded credentials and signs in.

```python title="Method Syntax"
client.signup(credentials_cbor, session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>credentials_cbor</code></td>
            <td colspan="2" scope="row" data-label="Description">The signup payload, CBOR-encoded as a map, as a <code>List[UInt8]</code>. It names the access method along with the namespace, database, and any variables it expects.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
from surrealdb import CborCodec
from std.collections import List

var codec = CborCodec()
var pairs = List[Tuple[String, List[UInt8]]]()
pairs.append(Tuple(String("namespace"), codec.encode_text("test")))
pairs.append(Tuple(String("database"), codec.encode_text("test")))
pairs.append(Tuple(String("access"), codec.encode_text("user")))
pairs.append(Tuple(String("email"), codec.encode_text("chiru@example.com")))
pairs.append(Tuple(String("pass"), codec.encode_text("changeme")))

var resp = client.signup(codec.encode_map(pairs))
```

> [!NOTE]
> `signup` operates on a stateful session, which is provided by the WebSocket transport. WebSocket support is rolling out.

## See also

- [Authentication](/docs/reference/mojo/concepts/authentication.md)
- [`signin()`](/docs/reference/mojo/methods/signin.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/transaction-multi

# transaction_multi

The transaction_multi() method for the SurrealDB Mojo SDK runs a list of statements as one atomic transaction.

Wraps a list of statements in `BEGIN TRANSACTION;` and `COMMIT TRANSACTION;` and sends them as a single atomic query. This works on any transport and is the recommended way to run a transaction over HTTP.

```python title="Method Syntax"
client.transaction_multi(statements, session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>statements</code></td>
            <td colspan="2" scope="row" data-label="Description">The statements to run, as a <code>List[String]</code>. A trailing semicolon is added to each if missing.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
from std.collections import List

var stmts = List[String]()
stmts.append("CREATE car:a SET wheels = 4;")
stmts.append("CREATE car:b SET wheels = 4;")
var resp = client.transaction_multi(stmts)
```

If any statement fails, the whole transaction is rolled back.

## See also

- [Transactions](/docs/reference/mojo/concepts/transactions.md)
- [`begin_transaction()`](/docs/reference/mojo/methods/begin-transaction.md)

---

Source: https://surrealdb.com/docs/reference/mojo/methods/unset

# unset

The unset() method for the SurrealDB Mojo SDK removes a parameter for this connection.

Removes a parameter previously assigned with [`set()`](/docs/reference/mojo/methods/set.md).

```python title="Method Syntax"
client.unset(name, session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>name</code></td>
            <td colspan="2" scope="row" data-label="Description">The parameter name to remove.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
client.unset("name")
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/update

# update

The update() method for the SurrealDB Mojo SDK updates all records in a table, or a specific record.

Updates all records in a table, or a specific record, replacing their content. This is a convenience wrapper that runs `UPDATE <thing> CONTENT <content_json>;`.

```python title="Method Syntax"
client.update(thing, content_json, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>thing</code></td>
            <td colspan="2" scope="row" data-label="Description">The table or specific record to update.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>content_json</code></td>
            <td colspan="2" scope="row" data-label="Description">The replacement content as a JSON string.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var resp = client.update("person:chiru", '{ "age": 31 }')
```

## Translated query

```surql
UPDATE $thing CONTENT $content_json;
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/upsert

# upsert

The upsert() method for the SurrealDB Mojo SDK upserts all records in a table, or a specific record.

Creates a record if it does not exist, or updates it if it does. This is a convenience wrapper that runs `UPSERT <thing> CONTENT <content_json>;`.

```python title="Method Syntax"
client.upsert(thing, content_json, session, txn)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>thing</code></td>
            <td colspan="2" scope="row" data-label="Description">The table or specific record to upsert.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>content_json</code></td>
            <td colspan="2" scope="row" data-label="Description">The record content as a JSON string.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>txn</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional transaction id.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
var resp = client.upsert("person:chiru", '{ "name": "Chiru", "age": 31 }')
```

## Translated query

```surql
UPSERT $thing CONTENT $content_json;
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/use

# use

The use() method for the SurrealDB Mojo SDK switches to a specific namespace and database.

Switches the connection to a specific namespace and database.

```python title="Method Syntax"
client.use(namespace, database, session)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>namespace</code></td>
            <td colspan="2" scope="row" data-label="Description">The namespace to switch to.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>database</code></td>
            <td colspan="2" scope="row" data-label="Description">The database to switch to.</td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument"><code>session</code></td>
            <td colspan="2" scope="row" data-label="Description">An optional session id to scope the change to a specific session.</td>
        </tr>
    </tbody>
</table>

## Example usage

```python
client.use("test", "test")
```

---

Source: https://surrealdb.com/docs/reference/mojo/methods/version

# version

The version() method for the SurrealDB Mojo SDK returns the database version.

Returns the version of the connected SurrealDB server as an `RpcResponse`.

```python title="Method Syntax"
client.version()
```

## Arguments

This method takes no arguments.

## Example usage

```python
var resp = client.version()
if resp.is_ok() and resp.result:
    print(resp.result.value())
```

---

Source: https://surrealdb.com/docs/reference/php

# PHP SDK

The official SurrealDB SDK for PHP. Query a remote instance from any PHP application, with a stable v1 release and a v2 rewrite in alpha.

The SurrealDB SDK for PHP lets you connect to SurrealDB from server-side applications, APIs, and command-line tools. You can run queries, manage data and authentication, call database functions, and subscribe to real-time updates with live queries. When connecting over WebSocket, the SDK reconnects automatically if the connection drops.

The SDK ships in two lines. Version 1.x is the current stable release and uses direct RPC-style methods such as `$db->create($thing, $data)`. Version 2.x is a rewrite with a fluent query builder, typed credentials, and a PSR-based transport layer. It is in alpha and introduces breaking changes.

> [!NOTE]
> The latest stable release is `2.0.0-alpha.3`, documented under [v1](/docs/reference/php/v1.md).
> The `2.0.0-alpha.1` release is documented under [v2](/docs/reference/php/v2.md). It is an alpha with breaking changes, so pin the exact version when installing it.

## Choose a version

- [v1 (stable)](/docs/reference/php/v1.md) (available) - The current stable release. Direct RPC-style methods over HTTP and WebSocket.

- [v2 (alpha)](/docs/reference/php/v2.md) (alpha) - The rewrite with fluent query builders and typed credentials. Alpha, breaking changes.

- [Migration guide](/docs/reference/php/v2/migration.md) - Move an existing project from v1 to v2, with a method-by-method mapping.

## Ecosystem

- [Surqlize (ORM)](/docs/reference/php/libraries/surqlize.md) (in development) - An object-relational mapper with attribute-driven models, a typed query builder, and graph relations.

## Frameworks

- [Laravel](/docs/reference/php/frameworks/laravel.md) (alpha) - A Laravel integration that wires the SDK and Surqlize into config, the service container, and Artisan.

## Contributing

To contribute to the SDK code, submit an issue or pull request in the [surrealdb.php](https://github.com/surrealdb/surrealdb.php) repository. To contribute to this documentation, submit an issue or pull request in the [docs.surrealdb.com](https://github.com/surrealdb/docs.surrealdb.com) repository.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.php)
- [Composer package](https://packagist.org/packages/surrealdb/surrealdb.php)

---

Source: https://surrealdb.com/docs/reference/php/frameworks/laravel

# Laravel

The Laravel integration wires the SurrealDB PHP SDK and the Surqlize ORM into Laravel's config, service container, facades, and Artisan commands.

The [Laravel integration](https://github.com/surrealdb/surrealdb.laravel) connects SurrealDB to a Laravel application. It wires [version 2 of the SDK](/docs/reference/php/v2.md) and the [Surqlize ORM](/docs/reference/php/libraries/surqlize.md) into Laravel's config, service container, facades, and Artisan commands.

Query execution and the database protocol are delegated to the SDK. Models, query compilation, graph relations, and schema definitions are delegated to Surqlize. The integration adds the Laravel glue: publishable config, container bindings, facades, schema commands, and testing helpers.

> [!IMPORTANT]
> The integration is published as `0.0.1-alpha.1` and is in early development. It requires PHP `8.4` and Laravel `11`, `12`, or `13`, and depends on the alpha SDK and ORM.

SurrealDB runs alongside Laravel's SQL database and your existing connections, and the integration does not use Eloquent or `config/database.php`.

## Getting started

- [Installation](/docs/reference/php/frameworks/laravel/installation.md) - Install the package and publish its configuration.

- [Configuration](/docs/reference/php/frameworks/laravel/configuration.md) - Set connection details, auth modes, and multiple connections.

## Using the integration

- [Container and facades](/docs/reference/php/frameworks/laravel/container-and-facades.md) - The service providers, container bindings, and the SurrealDB, Surreal, and Surqlize facades.

- [Queries and transactions](/docs/reference/php/frameworks/laravel/queries-and-transactions.md) - Run model queries, raw SurrealQL, and transactions in Laravel.

- [Schema commands](/docs/reference/php/frameworks/laravel/schema-commands.md) - Dump and apply your Surqlize schema with Artisan.

- [Testing](/docs/reference/php/frameworks/laravel/testing.md) - Fake the executor and assert the queries your code sends.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.laravel)
- [Surqlize ORM](/docs/reference/php/libraries/surqlize.md) for models and queries
- [PHP SDK v2](/docs/reference/php/v2.md) for the underlying client

---

Source: https://surrealdb.com/docs/reference/php/frameworks/laravel/configuration

# Configuration

Configure the SurrealDB Laravel integration with environment variables, authentication modes, multiple named connections, and the ORM model list.

The integration keeps SDK and ORM configuration separate. `config/surrealdb.php` configures the SDK client, and `config/surqlize.php` configures the ORM.

## Connection environment variables

`config/surrealdb.php` reads these environment variables for the default connection.

```dotenv
SURREALDB_CONNECTION=default
SURREALDB_URL=ws://127.0.0.1:8000/rpc
SURREALDB_NAMESPACE=test
SURREALDB_DATABASE=test
SURREALDB_USERNAME=root
SURREALDB_PASSWORD=secret
SURREALDB_AUTO_CONNECT=true
SURREALDB_CONNECT_ON_RESOLVE=true
SURREALDB_DISCONNECT_ON_TERMINATE=true
SURREALDB_HEALTH_CHECK_ON_RESOLVE=false
```

The lifecycle flags control when the integration opens and closes connections: whether to connect automatically, connect when the client is first resolved from the container, disconnect when the request terminates, and run a health check on resolve.

## Authentication modes

When `SURREALDB_USERNAME` is set, the integration authenticates with the SDK's `RootAuth` by default. For scoped authentication, set `SURREALDB_AUTH_MODE` and fill the matching keys in the published config.

| `SURREALDB_AUTH_MODE` | SDK credential |
|-----------------------|----------------|
| `namespace` | `NamespaceAuth` |
| `database` | `DatabaseAuth` |
| `record` | `RecordAccessAuth` |
| `bearer` | `BearerAuth` |
| `token` | An existing token |
| `none` | No authentication |

See [Authentication](/docs/reference/php/v2/concepts/authentication.md) for what each credential needs.

## Multiple connections

The SDK config supports several named connections under a `connections` key, with a `default` selecting which to use.

```php
'default' => env('SURREALDB_CONNECTION', 'default'),

'connections' => [
    'default' => [
        'url' => env('SURREALDB_URL', 'ws://127.0.0.1:8000/rpc'),
        'namespace' => env('SURREALDB_NAMESPACE', 'test'),
        'database' => env('SURREALDB_DATABASE', 'test'),
        // auth, lifecycle, and driver options...
    ],

    'analytics' => [
        'url' => env('SURREALDB_ANALYTICS_URL'),
        'namespace' => env('SURREALDB_ANALYTICS_NAMESPACE'),
        'database' => env('SURREALDB_ANALYTICS_DATABASE'),
        'auto_connect' => false,
    ],
],
```

A non-default connection is selected with the `connection:` argument on the [facade methods](/docs/reference/php/frameworks/laravel/queries-and-transactions.md) and the `--connection` option on the [schema commands](/docs/reference/php/frameworks/laravel/schema-commands.md).

## ORM configuration

`config/surqlize.php` holds the model list and the executor binding. The executor defaults to the Laravel-managed SurrealDB connection.

```php
'executor' => env('SURQLIZE_EXECUTOR', 'surrealdb.connection'),

'models' => [
    App\Models\User::class,
],
```

The `models` list is used by the [schema commands](/docs/reference/php/frameworks/laravel/schema-commands.md).

## Lifecycle and Octane

Under PHP-FPM the SDK client is resolved once per request and disconnected on terminate. Under [Laravel Octane](https://laravel.com/docs/octane), queue workers, or long-running commands, the container, and therefore the client, lives longer. The `disconnect_on_terminate` flag and Octane's worker model determine how long a connection stays open. For live queries, run them in dedicated workers as described in [Runtimes and workers](/docs/reference/php/v2/concepts/runtimes.md).

## Learn more

- [Container and facades](/docs/reference/php/frameworks/laravel/container-and-facades.md) for the bindings these settings drive
- [Authentication](/docs/reference/php/v2/concepts/authentication.md) for the credential types
- [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) for the SDK connection options

---

Source: https://surrealdb.com/docs/reference/php/frameworks/laravel/container-and-facades

# Container and facades

The service providers, container bindings, and the SurrealDB, Surreal, and Surqlize facades the Laravel integration registers.

The integration registers two service providers, binds the SDK and ORM into the container, and exposes three facades.

## Service providers

Laravel auto-discovers both providers.

- `SurrealDB\Laravel\SurrealDBServiceProvider` owns the SDK configuration, client construction, and the `surrealdb` container alias.
- `SurrealDB\Laravel\SurqlizeServiceProvider` owns the ORM configuration, the schema commands, Surqlize's executor binding, and `ConnectionManager` setup.

## Container bindings

The SDK provider binds:

- `SurrealDB\SDK\Surreal` and the `surrealdb` alias for the raw SDK client.
- `SurrealDB\Laravel\SurrealDBManager` plus the `surrealdb.manager` and `surrealdb.connection` aliases for the Laravel lifecycle helpers.

The ORM provider binds:

- `SurrealDB\SDK\Contracts\QueryExecutor` for Surqlize query execution.
- `Surqlize\Model\SchemaManager` for schema definitions and application.
- `SurrealDB\Laravel\SurqlizeManager` and the `surqlize` alias for the Laravel-friendly ORM helpers.

The ORM provider also registers Surqlize's global `ConnectionManager` with a lazy executor that resolves the configured `surqlize.executor` binding only when a query runs. By default that points at `surrealdb.connection`, so model queries use the Laravel-managed connection and connect lazily.

```php
use SurrealDB\SDK\Surreal;

$client = app(Surreal::class);
```

## Facades

The integration ships three facades in the `SurrealDB\Laravel\Facades` namespace.

### `SurrealDB`

The manager facade. It manages named connections, runs SurrealQL, and exposes lifecycle and testing helpers. Most methods take an optional connection name.

| Method | Description |
|--------|-------------|
| `run($surql, $bindings, $connection?)` | Run raw SurrealQL |
| `query($query, $connection?)` | Run a [`BoundQuery`](/docs/reference/php/v2/api/utilities.md#boundquery) |
| `client($name?)` / `connection($name?)` | Resolve the SDK client or its controller |
| `connect()` / `disconnect()` / `reconnect()` / `isConnected()` | Manage the connection lifecycle |
| `health()` / `version()` | Check the server |
| `using($connection)` | Get an executor scoped to a connection |
| `fake()` / `assertSurrealQuerySent()` / `resetFakes()` | [Testing](/docs/reference/php/frameworks/laravel/testing.md) helpers |

```php
use SurrealDB\Laravel\Facades\SurrealDB;

SurrealDB::health();

$result = SurrealDB::run('RETURN $message', ['message' => 'hello']);
```

### `Surreal`

Resolves the underlying SDK [`Surreal`](/docs/reference/php/v2/api/core.md) client for direct, lower-level access.

```php
use SurrealDB\Laravel\Facades\Surreal;

$result = Surreal::run('RETURN true;');
```

### `Surqlize`

Exposes the ORM manager: the executor, schema helpers, and transactions.

| Method | Description |
|--------|-------------|
| `executor()` | The configured Surqlize executor |
| `schemaDefinitions($models?)` | The schema statements for the models |
| `applySchema($models?, $executor?)` | Apply the schema |
| `transaction($callback, $executor?, $connection?)` | Run a [transaction](/docs/reference/php/frameworks/laravel/queries-and-transactions.md#transactions) |

```php
use SurrealDB\Laravel\Facades\Surqlize;

Surqlize::transaction(function ($transaction): void {
    User::createQuery(['name' => 'beau'], executor: $transaction)->execute();
});
```

## Learn more

- [Configuration](/docs/reference/php/frameworks/laravel/configuration.md) for the settings these bindings read
- [Queries and transactions](/docs/reference/php/frameworks/laravel/queries-and-transactions.md) for using the facades
- [Testing](/docs/reference/php/frameworks/laravel/testing.md) for the fake executor

---

Source: https://surrealdb.com/docs/reference/php/frameworks/laravel/installation

# Installation

Install the SurrealDB Laravel integration with Composer and publish the SDK and ORM configuration files.

Install the integration with [Composer](https://getcomposer.org/download/). It requires PHP `8.4` or later and Laravel `11`, `12`, or `13`, and pulls in the alpha [SDK](/docs/reference/php/v2.md) and [Surqlize ORM](/docs/reference/php/libraries/surqlize.md).

## Install the package

```bash
composer require surrealdb/laravel
```

The integration depends on alpha releases, so your project must allow them. If Composer cannot resolve the dependencies under the default `stable` minimum stability, lower it.

```bash
composer config minimum-stability alpha
composer config prefer-stable true
```

## Publish the configuration

The SDK and ORM keep their configuration separate. Publish both files.

```bash
php artisan vendor:publish --tag=surrealdb-config
php artisan vendor:publish --tag=surqlize-config
```

This creates `config/surrealdb.php` for the SDK client and `config/surqlize.php` for the ORM.

## Service provider discovery

Laravel auto-discovers the two service providers, so there is nothing to register manually. See [Container and facades](/docs/reference/php/frameworks/laravel/container-and-facades.md) for what they bind.

## Next steps

- [Configuration](/docs/reference/php/frameworks/laravel/configuration.md) - Set your connection details and authentication.

- [Queries and transactions](/docs/reference/php/frameworks/laravel/queries-and-transactions.md) - Run your first model queries.

---

Source: https://surrealdb.com/docs/reference/php/frameworks/laravel/queries-and-transactions

# Queries and transactions

Run Surqlize model queries, raw SurrealQL, and transactions in a Laravel application, including against named connections.

In Laravel you query SurrealDB three ways: through Surqlize models, through the raw SDK client, and through the `SurrealDB` manager facade. The integration registers the executor, so model queries work without passing one.

## Model queries

Define [Surqlize models](/docs/reference/php/libraries/surqlize/models.md) and query them directly. They run through the Laravel-managed connection.

```php
$users = User::select(fn ($user) => [$user->id, $user->name])
    ->where(fn ($user) => $user->name->eq('beau'))
    ->collectModels();

$user = User::create(['name' => 'Tobie', 'age' => 32], id: 'tobie');
```

See [Querying](/docs/reference/php/libraries/surqlize/querying.md) and [Mutations](/docs/reference/php/libraries/surqlize/mutations.md) for the full model API.

## Raw SDK access

Resolve the SDK client for lower-level access.

```php
use SurrealDB\SDK\Surreal;

$result = app(Surreal::class)->run('RETURN $message', ['message' => 'hello']);
```

## The manager facade

The `SurrealDB` facade runs SurrealQL with lifecycle helpers around the same client, and targets a named connection with the `connection:` argument.

```php
use SurrealDB\Laravel\Facades\SurrealDB;

SurrealDB::health();

$result = SurrealDB::run('RETURN $message', ['message' => 'hello']);

$analytics = SurrealDB::run(
    'RETURN $message',
    ['message' => 'hello'],
    connection: 'analytics',
);
```

## Transactions

The `Surqlize` facade runs a transaction over the executor. Run each query inside the callback through the transaction it passes you, with the `executor:` argument or `withExecutor()`. The transaction commits when the callback returns and rolls back if it throws.

```php
use SurrealDB\Laravel\Facades\Surqlize;

Surqlize::transaction(function ($transaction): void {
    User::createQuery(['name' => 'beau'], executor: $transaction)->execute();
});

Surqlize::transaction(
    fn ($transaction) => User::createQuery(['name' => 'beau'], executor: $transaction)->execute(),
    connection: 'analytics',
);
```

> [!NOTE]
> `Surqlize::transaction()` uses Surqlize's executor-based SurrealQL transaction batching. SDK-native transaction ids are not exposed through the Laravel facade yet, because they depend on the WebSocket transport and server feature support.

## Learn more

- [Container and facades](/docs/reference/php/frameworks/laravel/container-and-facades.md) for the facade method reference
- [Surqlize transactions](/docs/reference/php/libraries/surqlize/transactions.md) for the batching mechanism
- [Testing](/docs/reference/php/frameworks/laravel/testing.md) for asserting the queries your code sends

---

Source: https://surrealdb.com/docs/reference/php/frameworks/laravel/schema-commands

# Schema commands

Dump and apply your Surqlize schema in a Laravel application with the surqlize:schema-dump and surqlize:schema-apply Artisan commands.

The integration adds two Artisan commands for managing your [Surqlize schema](/docs/reference/php/libraries/surqlize/schema.md). Both read the model list from `config('surqlize.models')`.

## Dumping the schema

`surqlize:schema-dump` prints the schema definitions for the configured models without executing them.

```bash
php artisan surqlize:schema-dump
```

Pass `--connection` to indicate which connection the dump is intended for.

```bash
php artisan surqlize:schema-dump --connection=analytics
```

## Applying the schema

`surqlize:schema-apply` runs the schema definitions against the database.

```bash
php artisan surqlize:schema-apply
```

| Option | Description |
|--------|-------------|
| `--dry-run` | Print the schema DDL instead of executing it |
| `--dump` | Alias of `--dry-run` |
| `--connection=` | The SurrealDB connection to apply to |

```bash
php artisan surqlize:schema-apply --dry-run

php artisan surqlize:schema-apply --connection=analytics
```

> [!NOTE]
> Register every model that should take part in schema commands in `config('surqlize.models')`. The commands fail if a configured class does not extend `Surqlize\Model\Model`.

## Learn more

- [Schema](/docs/reference/php/libraries/surqlize/schema.md) for defining tables and fields
- [Configuration](/docs/reference/php/frameworks/laravel/configuration.md#orm-configuration) for the model list
- [Surqlize CLI](/docs/reference/php/libraries/surqlize/code-generation-and-cli.md) for the framework-agnostic commands

---

Source: https://surrealdb.com/docs/reference/php/frameworks/laravel/testing

# Testing

Test SurrealDB code in Laravel with the fake query executor, query assertions, and the trait that resets Surqlize's global state between tests.

The integration provides a fake executor so you can assert the queries your code sends without a live database, plus a trait that resets Surqlize's global caches between tests.

## Resetting state between tests

Surqlize keeps global state in its `ConnectionManager` and metadata caches. The `RefreshSurqlizeState` trait resets it so tests do not leak state into one another.

```php
use SurrealDB\Laravel\Testing\RefreshSurqlizeState;

final class UserTest extends TestCase
{
    use RefreshSurqlizeState;

    protected function tearDown(): void
    {
        $this->resetSurqlizeState();

        parent::tearDown();
    }
}
```

The trait also offers `useSurqlizeExecutor()` to set a specific executor, `fakeSurrealDB()` to fake the managed executor, and `resetSurrealDBFakes()` to clear fakes.

## Faking the executor

`SurrealDB::fake()` swaps the managed executor for a `FakeQueryExecutor` that records queries instead of running them. Assert what was sent with `assertSurrealQuerySent()`, or on the returned fake with `assertQuerySent()`.

```php
use SurrealDB\Laravel\Facades\SurrealDB;

$fake = SurrealDB::fake();

SurrealDB::run('RETURN true;');

SurrealDB::assertSurrealQuerySent('RETURN true;');
$fake->assertQuerySent('RETURN true;');
```

The fake also provides `assertNothingSent()` for asserting that no queries were sent.

## Named fakes

Fakes are scoped by connection name, so you can fake one connection and leave the others untouched.

```php
SurrealDB::fake('analytics');

SurrealDB::run('RETURN true;', connection: 'analytics');

SurrealDB::assertSurrealQuerySent('RETURN true;', connection: 'analytics');
```

## Unit testing without the container

For unit tests that do not boot the application, pass a `FakeQueryExecutor` directly to a Surqlize query through `withExecutor()`, or register it globally with `useSurqlizeExecutor()` from the trait.

```php
use SurrealDB\Laravel\Testing\FakeQueryExecutor;

$fake = new FakeQueryExecutor();

User::query()->withExecutor($fake)->collect();

$fake->assertQuerySent('SELECT * FROM user');
```

## Learn more

- [Container and facades](/docs/reference/php/frameworks/laravel/container-and-facades.md) for the facade testing helpers
- [Queries and transactions](/docs/reference/php/frameworks/laravel/queries-and-transactions.md) for the queries under test
- [Surqlize connections](/docs/reference/php/libraries/surqlize/connections.md) for executor injection

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize

# Surqlize

Surqlize is an object-relational mapper for SurrealDB in PHP, built on version 2 of the SDK, with attribute-driven models, a typed query builder, graph relations, and schema tooling.

[Surqlize](https://github.com/surrealdb/surqlize.php) is an object-relational mapper for SurrealDB in PHP. You describe tables as PHP classes with attributes, then compose SurrealQL through a typed query builder instead of writing strings. It is built on top of [version 2 of the SDK](/docs/reference/php/v2.md) and uses the SDK to execute queries.

Models and attributes describe your SurrealDB tables, and queries are built through typed PHP APIs that your IDE and static analyser understand. Surqlize compiles each query to a deterministic SurrealQL string for tests, and runs it through the SDK with parameter-bound values at runtime.

> [!IMPORTANT]
> Surqlize is published as `0.0.1-alpha.2` and is in early development. It requires PHP `8.4` and depends on the alpha [v2 SDK](/docs/reference/php/v2.md), so its API may change.

## Getting started

- [Installation](/docs/reference/php/libraries/surqlize/installation.md) - Install Surqlize with Composer and the alpha SDK it depends on.

- [Models](/docs/reference/php/libraries/surqlize/models.md) - Describe tables as PHP classes with attribute-driven fields.

- [Connections](/docs/reference/php/libraries/surqlize/connections.md) - Register an SDK executor and inject one per query when needed.

## Building queries

- [Querying](/docs/reference/php/libraries/surqlize/querying.md) - Typed select, where, ordering, projections, and the execution methods.

- [Mutations](/docs/reference/php/libraries/surqlize/mutations.md) - Create, update, upsert, and delete with model helpers and builders.

- [Edges and graph](/docs/reference/php/libraries/surqlize/edges-and-graph.md) - Edge models, graph traversal, and RELATE.

- [Search, vector, and geometry](/docs/reference/php/libraries/surqlize/search-vector-geometry.md) - Full-text search, vector KNN, and geometry helpers.

## Schema and tooling

- [Schema](/docs/reference/php/libraries/surqlize/schema.md) - Define tables with a schema contract or the fluent DSL.

- [Code generation and CLI](/docs/reference/php/libraries/surqlize/code-generation-and-cli.md) - Generate typed field adapters and run the CLI commands.

- [Transactions](/docs/reference/php/libraries/surqlize/transactions.md) - Batch ORM queries into a single transaction with rollback.

## Sources

- [GitHub repository](https://github.com/surrealdb/surqlize.php)
- [PHP SDK v2](/docs/reference/php/v2.md) that Surqlize builds on
- [Laravel integration](/docs/reference/php/frameworks/laravel.md) for using Surqlize in Laravel

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/code-generation-and-cli

# Code generation and CLI

Generate typed field adapters for Surqlize models and run the Surqlize CLI for field generation, schema application, and memory reports.

Typed callbacks work through `FieldSet` classes. Surqlize infers a model's fields at runtime, and can also generate explicit field adapter classes for better IDE completion and stricter static analysis.

## Field adapters

A generated field adapter is a `FieldSet` subclass that declares each property as a typed field. It makes the closures in `select()`, `where()`, and the other builder methods fully analysable.

```php
use App\Models\User;
use Surqlize\Query\Fields\FieldSet;
use Surqlize\Query\Fields\NumericField;
use Surqlize\Query\Fields\RecordIdField;
use Surqlize\Query\Fields\RecordLinkField;
use Surqlize\Query\Fields\StringField;

final class UserFields extends FieldSet
{
    public readonly RecordIdField $id;
    public readonly StringField $name;
    public readonly NumericField $age;
    public readonly RecordLinkField $address;

    public function __construct()
    {
        parent::__construct(User::class);

        $this->id = new RecordIdField('id', table: 'user');
        $this->name = new StringField('name');
        $this->age = new NumericField('age');
        $this->address = new RecordLinkField('address');
    }
}
```

You write these by hand only if you want to. The generator produces them for you.

## Configuration

The CLI reads a config file, by convention `surqlize.config.php`, that returns an array. For field generation it lists the models and where to write the adapters.

```php
use App\Models\Address;
use App\Models\HasAddress;
use App\Models\User;

return [
    'models' => [
        User::class,
        Address::class,
        HasAddress::class,
    ],
    'fields_namespace' => 'App\\Models\\Fields',
    'fields_path' => __DIR__ . '/src/Models/Fields',
];
```

For `schema:apply`, the config provides the models plus an SDK executor.

```php
return [
    'models' => [User::class, Address::class],
    'executor' => $surreal,
];
```

## CLI commands

Surqlize ships a Composer binary named `surqlize`.

```bash
vendor/bin/surqlize generate:fields [config-path]
vendor/bin/surqlize schema:apply [config-path]
vendor/bin/surqlize memory:footprint [--iterations=1000] [--output=path]
```

In a source checkout without Composer's bin proxy, run the binary directly with `php bin/surqlize ...`.

### generate:fields

Generates the `*Fields` classes and `*FieldTyping` traits for the configured models, into the configured namespace and path.

```bash
vendor/bin/surqlize generate:fields surqlize.config.php
```

### schema:apply {#schema-apply}

Applies the [schema](/docs/reference/php/libraries/surqlize/schema.md) definitions for the configured models through the configured executor.

```bash
vendor/bin/surqlize schema:apply surqlize.config.php
```

### memory:footprint

Generates a JSON memory report across the built-in scenarios (metadata reflection, field-set resolution, query compilation, hydration, graph traversal, and more), with retained, peak, and real memory deltas and durations.

```bash
vendor/bin/surqlize memory:footprint --iterations=5000 --output=memory-report.json
```

## Learn more

- [Querying](/docs/reference/php/libraries/surqlize/querying.md) for the typed callbacks adapters support
- [Schema](/docs/reference/php/libraries/surqlize/schema.md) for the definitions `schema:apply` runs
- [Laravel integration](/docs/reference/php/frameworks/laravel/schema-commands.md) for the Artisan schema commands

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/connections

# Connections

Register an SDK executor with Surqlize's ConnectionManager, and inject a per-query executor when you need more than one connection.

Surqlize does not open its own connection. It runs queries through an SDK instance that implements the SDK's `QueryExecutor` contract, which the [`Surreal`](/docs/reference/php/v2/api/core.md) client does. You register that instance once during bootstrap, and Surqlize uses it for every model query.

## Registering the executor

Create and connect an SDK [`Surreal`](/docs/reference/php/v2.md) instance, then hand it to the `ConnectionManager`.

```php
use Surqlize\Connection\ConnectionManager;
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\ConnectOptions;
use SurrealDB\SDK\Auth\RootAuth;

$db = new Surreal();
$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    namespace: 'surrealdb',
    database: 'docs',
    authentication: new RootAuth('root', 'root'),
));

ConnectionManager::set($db);
```

Once set, model queries resolve the executor automatically.

```php
$users = User::query()->collectModels();
```

## Per-query executors

`ConnectionManager` is a global singleton. When you need to run against a different connection, or want to avoid the singleton entirely, inject an executor for a single query with `withExecutor()`.

```php
$users = User::query()
    ->withExecutor($otherDb)
    ->collectModels();
```

The model helper methods accept an executor through an `executor:` argument for the same purpose.

```php
$user = User::create(['name' => 'Tobie', 'age' => 32], id: 'tobie', executor: $otherDb);

$user = User::find('tobie', executor: $otherDb);
```

> [!NOTE]
> In a [Laravel application](/docs/reference/php/frameworks/laravel.md), the integration registers the executor for you, so you do not call `ConnectionManager::set()` yourself.

## Learn more

- [PHP SDK v2](/docs/reference/php/v2.md) for connecting the underlying client
- [Querying](/docs/reference/php/libraries/surqlize/querying.md) for running queries through the executor
- [Transactions](/docs/reference/php/libraries/surqlize/transactions.md) for batching queries atomically

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/edges-and-graph

# Edges and graph

Model SurrealDB graph relations in Surqlize with edge models, traverse them in a SELECT, and create them with RELATE.

SurrealDB relation tables are represented by edge models. An edge model maps to a relation table and declares the models on each end, so traversals and `RELATE` statements are checked against your types.

## Edge models

An edge model extends `Surqlize\Edge\Edge` and uses the `#[Edge]` attribute with the relation table name and the `in` and `out` endpoint models.

```php
use Surqlize\Attributes\Edge;
use Surqlize\Attributes\Schema;
use Surqlize\Edge\Edge as EdgeModel;

#[Edge('has_address', in: User::class, out: Address::class)]
#[Schema(HasAddressSchema::class)]
final class HasAddress extends EdgeModel
{
}
```

Edge models inherit `RecordId $in` and `RecordId $out` endpoint properties from `Surqlize\Edge\Edge`.

## Traversing in a SELECT

Use graph fields inside a model `SELECT` to traverse relations. `Edge::out()` and `Edge::in()` start a traversal in the given direction; chain another `out()` or `in()` to reach the far table, optionally with a predicate, then name it with `as()` and resolve it with `fetch()`.

```php
use App\Models\Address;
use App\Models\HasAddress;
use App\Models\User;
use Surqlize\Edge\Edge;

$query = User::select([
        'name',
        Edge::out(HasAddress::class)
            ->out(Address::class, fn ($address) => $address->postcode->includes('24'))
            ->as('address')
            ->fetch(),
    ])
    ->where(fn ($user) => $user->name->eq('beau'))
    ->fetch('address');

$query->compile();
// SELECT name, ->has_address->address[WHERE postcode INCLUDES '24'] AS address
// FROM user WHERE name = "beau" FETCH address
```

`Edge::out()` is a magic static call. If your static analysis setup struggles with it, use the explicit factory.

```php
use Surqlize\Edge\GraphSelectField;
use Surqlize\Query\Ast\GraphDirection;

GraphSelectField::fromEdge(HasAddress::class, GraphDirection::Out)
    ->out(Address::class)
    ->as('address');
```

## Querying an edge's endpoints

An edge instance can query its endpoint tables with `in()` and `out()`, which return model queries.

```php
$edge = new HasAddress();

$users = $edge->in()
    ->select(fn ($user) => [$user->name])
    ->where(fn ($user) => $user->age->gt(27))
    ->collectModels();

$addresses = $edge->out()
    ->select(fn ($address) => [$address->postcode])
    ->collectModels();
```

## Creating relations

`Model::relate($from)` starts a `RELATE`. Chain `edge()` with the edge class, `with()` for the target, and `content()` for data on the edge.

```php
use Surqlize\Relate\Time;

User::relate($user)
    ->edge(HasAddress::class)
    ->with($address)
    ->content(['primary' => true])
    ->timeout(30, Time::Seconds)
    ->execute();
```

Both endpoint models must already have `RecordId` values. The builder validates that the source matches the edge's `in` endpoint and the target matches the `out` endpoint. Use `set($key, $value)` for a single field, and `compile()` or `toSdkQuery()` to inspect the statement instead of running it.

> [!NOTE]
> `Model::relate($from)` is model-first only. The source model is the `in` endpoint and the `with()` target is the `out` endpoint.

## Learn more

- [Models](/docs/reference/php/libraries/surqlize/models.md) for the `#[Edge]` attribute
- [Schema](/docs/reference/php/libraries/surqlize/schema.md) to define the relation table
- [RELATE](/docs/reference/query-language/statements/relate.md) for the SurrealQL statement

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/installation

# Installation

Install Surqlize with Composer, including the alpha v2 SurrealDB PHP SDK it depends on.

Surqlize is installed with [Composer](https://getcomposer.org/download/). It requires PHP `8.4` or later and depends on the alpha [v2 SurrealDB PHP SDK](/docs/reference/php/v2.md).

## Install the package

```bash
composer require surrealdb/surqlize
```

Surqlize depends on the alpha SDK, so your project must allow alpha releases. If Composer cannot resolve the SDK under the default `stable` minimum stability, lower the stability for the SDK or set it project-wide.

```bash
composer config minimum-stability alpha
composer config prefer-stable true
```

## Add an HTTP client

Surqlize runs queries through the SDK, which uses [PSR-18](https://www.php-fig.org/psr/psr-18/) and [PSR-17](https://www.php-fig.org/psr/psr-17/) interfaces for the HTTP transport. Install a client and factory implementation, plus discovery, when you connect over `http://` or `https://`.

```bash
composer require guzzlehttp/guzzle php-http/discovery
```

The WebSocket transport uses PHP's native stream functions and needs no extra packages. See the [SDK installation guide](/docs/reference/php/v2/installation.md) for details.

## Requirements

| Requirement | Version |
|-------------|---------|
| PHP | `>= 8.4` |
| SurrealDB PHP SDK | `surrealdb/surrealdb.php` (v2 alpha) |
| SurrealDB server | `1.x` up to (but not including) `4.0.0` |

## Next steps

- [Models](/docs/reference/php/libraries/surqlize/models.md) - Describe your first table as a model.

- [Connections](/docs/reference/php/libraries/surqlize/connections.md) - Register the SDK executor Surqlize runs queries through.

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/models

# Models

Describe SurrealDB tables as PHP classes in Surqlize, using attributes for the table name, record id, casts, schema, and specialised fields.

A Surqlize model is a PHP class that maps to a SurrealDB table. It extends `Surqlize\Model\Model`, declares its table with the `#[Table]` attribute, and exposes the record's fields as typed properties.

```php
use Surqlize\Attributes\Id;
use Surqlize\Attributes\Table;
use Surqlize\Model\Model;
use SurrealDB\SDK\Types\RecordId;

#[Table('user')]
final class User extends Model
{
    #[Id]
    public RecordId $id;

    public string $name;
    public int $age;
}
```

The property marked `#[Id]` holds the record's [`RecordId`](/docs/reference/php/v2/api/data-types.md#recordid) from the SDK. Property names map to SurrealDB field names directly, so a `name` property reads and writes the `name` field.

## Attributes

Surqlize describes a model through attributes in the `Surqlize\Attributes` namespace.

| Attribute | Target | Purpose |
|-----------|--------|---------|
| `#[Table('user')]` | class | Maps the model to a table |
| `#[Id]` | property | Marks the record id property (a `RecordId`) |
| `#[Cast(Address::class)]` | property | Hydrates a nested value as another model |
| `#[Schema(UserSchema::class)]` | class | Links the model to a [schema](/docs/reference/php/libraries/surqlize/schema.md) definition |
| `#[Search(analyzer: 'english')]` | property | Marks a [full-text search](/docs/reference/php/libraries/surqlize/search-vector-geometry.md) field |
| `#[Vector(dimension: 3)]` | property | Marks a [vector](/docs/reference/php/libraries/surqlize/search-vector-geometry.md) field and its dimension |
| `#[Geometry]` | property | Marks a [geometry](/docs/reference/php/libraries/surqlize/search-vector-geometry.md) field |
| `#[Edge('has_address', in: User::class, out: Address::class)]` | class | Defines a graph [edge](/docs/reference/php/libraries/surqlize/edges-and-graph.md) model |

The `#[Vector]` attribute also accepts a `distance` (default `cosine`), `#[Geometry]` accepts a `type` (default `geometry`), and `#[Search]` accepts an optional `analyzer`.

## Nested models

Use `#[Cast]` to hydrate an embedded object or a record link as another model. The nested class is itself a model.

```php
use Surqlize\Attributes\Cast;
use Surqlize\Attributes\Table;
use Surqlize\Model\Model;

#[Table('address')]
final class Address extends Model
{
    public string $street;
    public int $number;
    public string $postcode;
}

#[Table('user')]
final class User extends Model
{
    #[Id]
    public RecordId $id;

    public string $name;

    #[Cast(Address::class)]
    public ?Address $address = null;
}
```

## What a model gives you

Extending `Model` provides static query entry points and instance data operations.

| Method | Purpose |
|--------|---------|
| `select()` | Start a `SELECT` with fields or a typed callback |
| `query()` | Start `SELECT *` for the table |
| `selectValue()` | Start a `SELECT VALUE` query |
| `fields()` | Resolve the model's typed field set |
| `relate()` | Start a model-first [relation](/docs/reference/php/libraries/surqlize/edges-and-graph.md) builder |
| `create()`, `createQuery()`, `upsert()`, `save()`, `delete()` | [Persist](/docs/reference/php/libraries/surqlize/mutations.md) a record |
| `all()`, `find()`, `findOrFail()`, `count()`, `exists()`, `refresh()` | Read or reload records |
| `toArray()` | Serialise the initialised properties to an array |

These are covered in [Querying](/docs/reference/php/libraries/surqlize/querying.md) and [Mutations](/docs/reference/php/libraries/surqlize/mutations.md).

## Learn more

- [Connections](/docs/reference/php/libraries/surqlize/connections.md) to register the executor models run through
- [Querying](/docs/reference/php/libraries/surqlize/querying.md) for the typed query builder
- [Schema](/docs/reference/php/libraries/surqlize/schema.md) to define tables and fields in the database

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/mutations

# Mutations

Create, update, upsert, and delete records in Surqlize with model helpers and the mutation builder, including payload modes and return modes.

Surqlize exposes mutations two ways. Model helpers such as `create()` and `save()` cover the common cases and return hydrated models. The mutation builder gives full control over the payload, the return clause, and conditional updates.

## Model helpers

### Create

`create()` inserts a record and returns the model. Pass an `id` to set a specific record id.

```php
$user = User::create(['name' => 'beau', 'age' => 27]);

$user = User::create(['name' => 'beau', 'age' => 27], id: 'beau');
```

### Save

`save()` creates the record when the model has no `RecordId`, and updates it when it does.

```php
$user = User::findOrFail('beau');
$user->age = 28;
$user = $user->save();
```

### Upsert

`upsert()` creates the record if it does not exist, or updates it if it does. It requires an id.

```php
$user = User::upsert(['name' => 'beau', 'age' => 27], id: 'beau');
```

### Delete

`delete()` removes the record the model points at.

```php
$user = User::findOrFail('beau');
$user->delete();
```

### Reading records

The read helpers return models or scalars.

| Method | Purpose |
|--------|---------|
| `all()` | Fetch every record as models |
| `find($id)` | Find one model by id, or `null` |
| `findOrFail($id)` | Find one model by id, or throw `ModelNotFoundException` |
| `count($where?)` | Count records, optionally filtered |
| `exists($where?)` | Whether at least one matching record exists |
| `refresh()` | Reload the model instance from the database |

```php
$count = User::count(fn ($user) => $user->age->gte(18));

if (User::exists(fn ($user) => $user->name->eq('beau'))) {
    // ...
}
```

## The mutation builder

For more control, build the mutation explicitly. `createQuery()` returns a builder instead of running immediately, and `updateWhere()` and `deleteWhere()` target records by predicate.

```php
$query = User::createQuery(['name' => 'beau', 'age' => 27], id: 'beau');
$query->compile();
// CREATE user:beau CONTENT {"name":"beau","age":27} RETURN AFTER
```

Update or delete records that match a condition.

```php
User::updateWhere(fn ($user) => $user->age->gte(18))
    ->merge(['verified' => true])
    ->returnAfter()
    ->execute();

User::deleteWhere(fn ($user) => $user->age->lt(13))
    ->returnBefore()
    ->execute();
```

### Payload modes

| Method | SurrealQL |
|--------|-----------|
| `content($data)` | `CONTENT` |
| `merge($data)` | `MERGE` |
| `replace($data)` | `REPLACE` |
| `patch($patches)` | `PATCH` |

### Return modes

| Method | SurrealQL |
|--------|-----------|
| `returnNone()` | `RETURN NONE` |
| `returnBefore()` | `RETURN BEFORE` |
| `returnAfter()` | `RETURN AFTER` |
| `returnDiff()` | `RETURN DIFF` |
| `returning($fields)` | Return selected fields |
| `returningValue($field)` | Return one selected value |

### Running the mutation

`timeout($amount, $unit = 's')` adds a statement timeout. To run the mutation, call one of:

| Method | Result |
|--------|--------|
| `execute()` | The raw SDK result |
| `executeModels()` | A list of hydrated models |
| `firstModel()` | The first hydrated model, or `null` |
| `compile()` | A literal SurrealQL string |
| `toBoundQuery()` | An SDK [`BoundQuery`](/docs/reference/php/v2/api/utilities.md#boundquery) |

```php
$user = User::createQuery(['name' => 'beau'])
    ->returnAfter()
    ->firstModel();
```

## Learn more

- [Querying](/docs/reference/php/libraries/surqlize/querying.md) for reading records
- [Transactions](/docs/reference/php/libraries/surqlize/transactions.md) for grouping mutations atomically
- [Connections](/docs/reference/php/libraries/surqlize/connections.md) for per-query executor injection

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/querying

# Querying

Build SELECT queries in Surqlize with typed fields, where clauses, ordering, projections, and the advanced SurrealQL SELECT clauses, then execute or compile them.

Surqlize builds a `SELECT` as a typed query object. You start it from a model, refine it with chained methods, and either compile it to a SurrealQL string or execute it through the [SDK executor](/docs/reference/php/libraries/surqlize/connections.md).

```php
$query = User::select(fn ($user) => [$user->name, $user->age])
    ->where(fn ($user) => $user->age->gte(18))
    ->orderBy(fn ($user) => $user->name->asc());

$sql = $query->compile();
// SELECT name, age FROM user WHERE age >= 18 ORDER BY name ASC

$users = $query->collectModels();
```

The closures receive a typed field set, so field names, conditions, and projections are checked against your model. You can also pass plain strings where you prefer.

## Selecting fields

Pass a closure that returns the fields to select, or an array of field names. `query()` is shorthand for `SELECT *`.

```php
User::select(fn ($user) => [$user->name, $user->age])->compile();
// SELECT name, age FROM user

User::select(['name', 'age'])->compile();
// SELECT name, age FROM user

User::query()->compile();
// SELECT * FROM user
```

## Where clauses

A `where()` closure returns one predicate, or a list of predicates that are combined with `AND`.

```php
User::select(fn ($user) => [$user->name])
    ->where(fn ($user) => $user->age->gte(18))
    ->compile();
// SELECT name FROM user WHERE age >= 18

User::select(fn ($user) => [$user->name])
    ->where(fn ($user) => [
        $user->name->eq('beau'),
        $user->age->gte(18),
    ])
    ->compile();
// SELECT name FROM user WHERE name = "beau" AND age >= 18
```

Field helpers map to SurrealQL operators.

| Helper | Operator |
|--------|----------|
| `eq($value)` | `=` |
| `notEq($value)` | `!=` |
| `gt($value)` | `>` |
| `gte($value)` | `>=` |
| `lt($value)` | `<` |
| `lte($value)` | `<=` |
| `includes($value)` | `INCLUDES` |
| `contains($value)` | `CONTAINS` |
| `like($value)` | `LIKE` |
| `condition($operator, $value)` | A custom operator |

## Ordering

`orderBy()` accepts a field helper, or a field plus a direction.

```php
User::query()->orderBy(fn ($user) => $user->name->asc())->compile();
// SELECT * FROM user ORDER BY name ASC

User::query()->orderBy(fn ($user) => $user->name, 'DESC')->compile();
// SELECT * FROM user ORDER BY name DESC
```

## Fetching links

`fetch()` resolves record links so related records are returned inline.

```php
User::select(fn ($user) => [$user->name])
    ->fetch(fn ($user) => $user->address)
    ->compile();
// SELECT name FROM user FETCH address
```

## Pagination

`page()` sets a page and page size. `limit()` and `start()` give the same control directly.

```php
User::query()->page(page: 3, perPage: 25)->compile();
// SELECT * FROM user LIMIT 25 START 50

User::query()->limit(25)->start(50);
```

## Projections and aggregates

Combine projection helpers with `groupBy()` to aggregate.

```php
use Surqlize\Query\Fields\Projection;

User::select(fn ($user) => [
        $user->age,
        Projection::count()->as('total'),
    ])
    ->groupBy(fn ($user) => $user->age)
    ->orderBy('total', 'DESC')
    ->compile();
// SELECT age, count() AS total FROM user GROUP BY age ORDER BY total DESC
```

The available helpers are `Projection::count()`, `Projection::sum($field)`, `Projection::mean($field)`, and `Projection::raw($expression)`. Chain `->as('alias')` to name the result.

## Advanced SELECT clauses

Surqlize supports the wider set of SurrealQL `SELECT` clauses, compiled in the correct order.

```php
User::select(['*'])
    ->omit('password')
    ->withIndex('idx_user_email')
    ->where(fn ($user) => $user->age->gte(18))
    ->split('tags')
    ->orderBy(fn ($user) => $user->name->desc())
    ->limit(10)
    ->start(20)
    ->fetch(fn ($user) => $user->address)
    ->timeout(5)
    ->tempFiles()
    ->explain(full: true)
    ->compile();
// SELECT * OMIT password FROM user WITH INDEX idx_user_email WHERE age >= 18
// SPLIT tags ORDER BY name DESC LIMIT 10 START 20 FETCH address
// TIMEOUT 5s TEMPFILES EXPLAIN FULL
```

Other clause helpers include `withoutIndex()`, `groupAll()`, and `withoutFrom()`. `timeout()` takes an amount and an optional unit (`s` by default).

## Selecting values

`selectValue()` builds a `SELECT VALUE` query that returns scalar values.

```php
$name = User::selectValue(fn ($user) => $user->name)
    ->where(fn ($user) => $user->age->gte(18))
    ->first();
```

`SELECT VALUE` results are scalars and cannot be hydrated with `collectModels()`.

## Executing a query

| Method | Result |
|--------|--------|
| `compile()` | A literal SurrealQL string, for debugging and tests |
| `toBoundQuery()` | An SDK [`BoundQuery`](/docs/reference/php/v2/api/utilities.md#boundquery) with parameter-bound values |
| `collect()` | A list of raw results |
| `collectModels()` | A list of hydrated models |
| `lazyModels()` | A generator of hydrated models |
| `first()` | The first scalar or model, depending on the query |
| `explainPlan()` | The raw results from an `EXPLAIN` query |

```php
$users = User::query()
    ->where(fn ($user) => $user->age->gte(18))
    ->collectModels();

foreach ($users as $user) {
    echo $user->name;
}
```

> [!NOTE]
> `compile()` produces a literal string for inspection and deterministic tests. Runtime execution uses `toBoundQuery()` internally, so values are sent as bound parameters rather than interpolated into the query text.

## Learn more

- [Mutations](/docs/reference/php/libraries/surqlize/mutations.md) for create, update, and delete
- [Edges and graph](/docs/reference/php/libraries/surqlize/edges-and-graph.md) for graph traversal in a select
- [Search, vector, and geometry](/docs/reference/php/libraries/surqlize/search-vector-geometry.md) for specialised field helpers

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/schema

# Schema

Define SurrealDB tables, fields, indexes, and analysers in Surqlize with a schema contract or the fluent schema DSL, then apply them with the schema manager.

Surqlize can manage your database schema. A model links to a schema through the `#[Schema]` attribute, and the schema describes the `DEFINE` statements to apply. You can write raw SurrealQL, use the fluent DSL, or mix both.

## Schema contracts

A schema implements `Surqlize\Model\SchemaContract`. `definitions()` returns the schema statements, and `rules()` returns PHP validation callbacks that run before persistence operations such as `create()` and `save()`.

```php
use Surqlize\Model\SchemaContract;

final class UserSchema implements SchemaContract
{
    public function definitions(): array
    {
        return [
            'DEFINE TABLE user SCHEMAFULL;',
            'DEFINE FIELD name ON user TYPE string;',
            'DEFINE FIELD age ON user TYPE int;',
        ];
    }

    public function rules(): array
    {
        return [
            'name' => static fn (mixed $value): bool|string =>
                is_string($value) && $value !== '' ? true : 'Name is required.',
        ];
    }
}
```

Link the schema to a model with `#[Schema]`.

```php
use Surqlize\Attributes\Schema;
use Surqlize\Attributes\Table;
use Surqlize\Model\Model;

#[Table('user')]
#[Schema(UserSchema::class)]
final class User extends Model
{
    // ...
}
```

A validation rule returns `true` when the value is valid, or an error string when it is not.

## The schema DSL

The fluent DSL generates `DEFINE` statements for tables, fields, analysers, indexes, and assertions, so you describe the schema in PHP rather than strings.

```php
use Surqlize\Schema\Schema;

$schema = Schema::table('article')->schemafull();

$schema->analyzer('english')
    ->tokenizers(['class'])
    ->filters(['lowercase']);

$schema->field('title')
    ->string()
    ->assert(fn ($value) => $value->required()->minLength(3));

$schema->field('email')
    ->string()
    ->assert(fn ($value) => $value->email())
    ->unique('idx_article_email');

$schema->field('embedding')
    ->vector(3);

$schema->index('idx_article_embedding')
    ->fields(['embedding'])
    ->hnsw(3);

$definitions = $schema->definitions();
```

### Field types

`field()` returns a field definition with type helpers including `string()`, `int()`, `float()`, `bool()`, `datetime()`, `array()`, `record()`, `geometry()`, and `vector()`. Further modifiers include `default()`, `value()`, `computed()`, `readonly()`, `comment()`, `assert()`, and `unique()`.

### Assertions

`assert()` takes a closure that receives an assertion builder. It supports `required()`, `email()`, `minLength()`, `maxLength()`, `between()`, `greaterThan()`, `lessThan()`, `matchesRegex()`, `isRecord()`, and `customExpression()`.

### Indexes and analysers

`index()` builds an index with `fields()`, and `unique()`, `fullText()`, or `hnsw()` for the index kind. `analyzer()` builds a search analyser with `tokenizers()` and `filters()`.

## Mixing raw statements and the DSL

A schema contract can return a mix of raw strings and DSL objects.

```php
use Surqlize\Model\SchemaContract;
use Surqlize\Schema\Schema;

final class ArticleSchema implements SchemaContract
{
    public function definitions(): array
    {
        return [
            'DEFINE TABLE legacy;',
            Schema::table('article')
                ->schemafull()
                ->field('title')
                ->string()
                ->assert(fn ($value) => $value->minLength(3)),
        ];
    }

    public function rules(): array
    {
        return [];
    }
}
```

## Applying a schema

Apply the schema for a set of models with the `SchemaManager`. Pass an [executor](/docs/reference/php/libraries/surqlize/connections.md), or rely on the registered one.

```php
use Surqlize\Model\SchemaManager;

(new SchemaManager())->apply([
    User::class,
    Address::class,
], $surreal);
```

Use `definitions()` instead of `apply()` to inspect the statements without running them. You can also apply schemas from the [CLI](/docs/reference/php/libraries/surqlize/code-generation-and-cli.md#schema-apply).

## Learn more

- [Models](/docs/reference/php/libraries/surqlize/models.md) for the `#[Schema]` attribute
- [Code generation and CLI](/docs/reference/php/libraries/surqlize/code-generation-and-cli.md) to apply schemas from the command line
- [DEFINE](/docs/reference/query-language/statements/define/overview.md) for the SurrealQL schema statements

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/search-vector-geometry

# Search, vector, and geometry

Compile SurrealDB full-text search, vector KNN, and geometry expressions in Surqlize with the SearchField, VectorField, and GeometryField helpers.

Surqlize provides field helpers that compile SurrealDB's full-text search, vector, and geometry expressions. Mark the relevant properties with the matching attribute, then use the field helper in a `where()` or projection.

```php
use Surqlize\Attributes\Geometry;
use Surqlize\Attributes\Search;
use Surqlize\Attributes\Table;
use Surqlize\Attributes\Vector;
use Surqlize\Model\Model;

#[Table('searchable_article')]
final class SearchableArticle extends Model
{
    public string $title;

    #[Search]
    public string $body;

    /** @var list<float> */
    #[Vector(dimension: 3)]
    public array $embedding = [];

    /** @var list<float> */
    #[Geometry]
    public array $location = [];
}
```

## Full-text search

`SearchField` compiles search predicates and helpers. `matches()` builds the `@@` operator, `score()` returns the relevance score, and `highlight()` marks matched terms.

```php
use Surqlize\Query\Fields\SearchField;

$body = new SearchField('body');

SearchableArticle::select(['title', $body->score()->as('score')])
    ->where(fn () => $body->matches('surreal orm'))
    ->orderBy('score', 'DESC')
    ->compile();
// SELECT title, search::score(1) AS score FROM searchable_article
// WHERE body @@ 'surreal orm' ORDER BY score DESC
```

`score()` and `highlight()` take an optional match reference (default `1`) that ties the helper to the matching predicate.

## Vector search

`VectorField` compiles a K-nearest-neighbour query. `nearest()` builds the KNN predicate, and `knnDistance()` projects the computed distance.

```php
use Surqlize\Query\Fields\VectorField;

$embedding = new VectorField('embedding');

SearchableArticle::select(['title', $embedding->knnDistance()->as('distance')])
    ->where(fn () => $embedding->nearest([0.1, 0.2, 0.3], k: 10, effort: 40))
    ->orderBy('distance')
    ->compile();
```

`nearest()` takes the query vector and the number of neighbours `k`, with an optional `effort` for the HNSW search.

## Geometry

`GeometryField` compiles spatial predicates and distance projections.

```php
use Surqlize\Query\Fields\GeometryField;

$location = new GeometryField('location');

SearchableArticle::select(['*', $location->distanceTo([4.9, 52.3])->as('distance')])
    ->where(fn () => $location->withinMeters([4.9, 52.3], 5000))
    ->orderBy('distance')
    ->compile();
```

The helper also provides `inside()`, `intersects()`, and `containsGeometry()` predicates.

## Learn more

- [Models](/docs/reference/php/libraries/surqlize/models.md) for the `#[Search]`, `#[Vector]`, and `#[Geometry]` attributes
- [Schema](/docs/reference/php/libraries/surqlize/schema.md) to define search analysers and vector indexes
- [Querying](/docs/reference/php/libraries/surqlize/querying.md) for the query builder these helpers plug into

---

Source: https://surrealdb.com/docs/reference/php/libraries/surqlize/transactions

# Transactions

Batch Surqlize ORM queries into a single SurrealDB transaction with automatic rollback, and handle the validation exceptions Surqlize raises.

`ConnectionManager::transaction()` runs a set of ORM queries inside one transaction. It passes a transaction executor to your callback; run your queries through that executor with `withExecutor()` or the `executor:` argument. The transaction commits when the callback returns, and rolls back if it throws.

```php
use Surqlize\Connection\ConnectionManager;

ConnectionManager::transaction(function ($transaction): void {
    User::select(['name'])
        ->where(fn ($user) => $user->name->eq('beau'))
        ->withExecutor($transaction)
        ->collect();

    User::createQuery([
        'name' => 'tobie',
        'age' => 30,
    ], executor: $transaction)->execute();
});
```

If the callback throws, the transaction is rolled back and the exception is rethrown, so the batch either applies in full or not at all.

> [!NOTE]
> Every query inside the callback must use the transaction executor. A query that resolves the global executor instead runs outside the transaction.

## Validation and errors

Surqlize validates several contracts before it builds or executes a query, and raises typed exceptions when they fail.

- Model classes must extend `Surqlize\Model\Model`, and edge classes must extend `Surqlize\Edge\Edge`.
- Table and field identifiers are validated before compilation.
- `findOrFail()` throws `Surqlize\Model\Exception\ModelNotFoundException` when no record matches.
- Schema validation rules run before `create()` and `save()`, and a failing rule raises a `ValidationException`.
- Persistence methods throw when a required `RecordId` is missing.
- `RELATE` validates the edge endpoint classes and their record ids.

## Learn more

- [Connections](/docs/reference/php/libraries/surqlize/connections.md) for executor injection
- [Mutations](/docs/reference/php/libraries/surqlize/mutations.md) for the queries you batch
- [Transactions in the SDK](/docs/reference/php/v2/concepts/transactions.md) for the underlying mechanism

---

Source: https://surrealdb.com/docs/reference/php/v1

# PHP SDK v1

Version 1 is the current stable release of the SurrealDB PHP SDK, with direct RPC-style methods for querying a remote database.

Version 1 is the current stable release of the PHP SDK. It exposes a single `Surreal` class with direct, RPC-style methods such as `create()`, `select()`, `query()`, and `signin()`. Each method maps closely to a SurrealDB RPC call.

> [!NOTE]
> The latest stable release is `2.0.0-alpha.3`. A rewrite is available as [v2 (alpha)](/docs/reference/php/v2.md). If you are starting a new project and can accept alpha software, see the [migration guide](/docs/reference/php/v2/migration.md) for the differences.

The SDK requires PHP `8.2` or later and the `curl` extension. It connects to a remote SurrealDB instance over HTTP or WebSocket.

## Getting started

- [Installation](/docs/reference/php/v1/installation.md) - Install the SDK with Composer.

- [Quickstart](/docs/languages/php.md) - Connect to SurrealDB and run your first queries.

## Concepts

- [Connecting to SurrealDB](/docs/reference/php/v1/concepts/connecting.md) - Initialize the SDK, connect, and select a namespace and database.

- [Authentication](/docs/reference/php/v1/concepts/authentication.md) - Sign up and sign in users with scopes, credentials, and tokens.

- [Executing queries](/docs/reference/php/v1/concepts/executing-queries.md) - Create, select, update, and delete records.

- [Methods](/docs/reference/php/v1/methods.md) - The full reference of methods on the Surreal class.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.php)
- [Composer package](https://packagist.org/packages/surrealdb/surrealdb.php)

---

Source: https://surrealdb.com/docs/reference/php/v1/concepts/authentication

# Authentication

Learn how to authenticate users and secure the database with the SurrealDB PHP SDK.

Since SurrealDB is a database that is designed to be used in a distributed environment, it is important to secure the database and the data that is stored in it.
SurrealDB provides a number of methods for authenticating users and securing the database.

## Define scope

If you haven't defined a scope for your database, you can define a scope by querying the database using the [`query`](/docs/reference/php/v1/methods/query.md) method.

```php
$db->query('
	DEFINE SCOPE user SESSION 24h
	SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) );
');
```

In `2.0` and above, the `DEFINE SCOPE` Statement has been replaced with the `DEFINE ACCESS ... TYPE RECORD` Statement so the above query would be:

```php
$db->query('
DEFINE ACCESS user ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;
');
```

## Signup with credentials

To signup a new scoped user, you can use the [`signup`](/docs/reference/php/v1/methods/signup.md) method from the `Surreal` class. This method takes an associative array
with the `namespace`, `database`, and `scope` keys as arguments. The `email` and `pass` keys are also required for this example, but it can be different
depending on the required parameters you have defined for the scope.

```php
$db->signup([
	"namespace" => "surrealdb",
	"database" => "surrealdb",
	"scope" => "user",
	"email" => "user@email.com",
	"pass" => "password-123"
]);
```

```php
// 2.0 and above
$db->signup([
	"namespace" => "surrealdb",
	"database" => "surrealdb",
	"access" => "user",
	"email" => "user@email.com",
	"pass" => "password-123"
]);
```

## Signin with credentials

To signin with credentials, you can use the [`signin`](/docs/reference/php/v1/methods/signin.md) method.

**Root**

Root authentication gives you access to all namespaces and databases within the SurrealDB instance.
		```php
		$token = $db->signin([
			"username" => "root",
			"password" => "secret"
		]);
		```

**Namespace**

Namespace authentication gives you access to all databases within a particular namespace.
		```php
		$token = $db->signin([
			"username" => "root",
			"password" => "secret",
			"namespace" => "surrealdb"
		]);
		```

**Database**

Database authentication gives you access to all data within a single database.
		```php
		$token = $db->signin([
			"username" => "root",
			"password" => "secret",
			"namespace" => "surrealdb",
			"database" => "surrealdb"
		]);
		```

**Scope**

Authenticate using a specific [Scope](/docs/reference/query-language/statements/define/scope.md) within a database.
		```php
		$token = $db->signin([
			"email" => "user@email.com",
			"pass" => "secret",
			"namespace" => "surrealdb",
			"database" => "surrealdb",
			"scope" => "user"
		]);
		```

**Access**

Authenticate using a specific [access method](/docs/reference/query-language/statements/define/access.md) within a database.
		```php
		$token = $db->signin([
			"email" => "user@email.com",
			"pass" => "secret",
			"namespace" => "surrealdb",
			"database" => "surrealdb",
			"access" => "user"
		]);
		```

## Signin with auth token

If you already have signed in and have an auth token stored somewhere, you can authenticate using the [`authenticate`](/docs/reference/php/v1/methods/authenticate.md) method.
This method takes one argument, the auth token.

```php
$db->authenticate($token);
```

## User information

When you signed in successfully, you can get the user information by using the [`info`](/docs/reference/php/v1/methods/info.md) method.
This returns the user information as an associative array.

```php
$user = $db->info();
```

## Invalidate user session

To invalidate a user session, you can use the [`invalidate`](/docs/reference/php/v1/methods/invalidate.md) method. When executed, the user
session will be invalidated and the user will be signed out.

```php
$db->invalidate();
```

In the next article we will cover how to query the database.

---

Source: https://surrealdb.com/docs/reference/php/v1/concepts/connecting

# Connecting to SurrealDB

Initialise the SurrealDB PHP SDK, connect to an instance, and select a namespace and database.

This guide shows how to initialise version 1 of the SurrealDB PHP SDK and connect to an instance. If you have not installed the SDK yet, see the [installation guide](/docs/reference/php/v1/installation.md).

**Composer**

Before we can make use of any of the packages we have installed for our project, we need to include the `autoload.php` file in our project.
		This file is generated by Composer and it contains all the classes and dependencies that we have installed in our project.
		```php
		include_once __DIR__ . '/vendor/autoload.php';
		```

When we have imported the `Surreal` class, we can initialise a new instance of SurrealDB.
After this is done we can make use of certain methods to interact with the database.

```php
$db = new Surreal();
```

Then we connect to the database. The `connect` method takes the URL of
the database as an argument. The URL can be either `http` or `ws` protocol.

**HTTP**

		```php
		$db->connect("http://127.0.0.1:8000/rpc");
		```

**WebSocket**

		```php
		$db->connect("ws://127.0.0.1:8000/rpc");
		```

In order to send queries to the database,
we need to set the `namespace` and `database` for the current connection you want
to interact with.

```php
$db->use([
	"namespace" => "main",
	"database" => "main"
]);
```

We should now be successfully connected to the database. In the next article we will cover authenticating our connection.

---

Source: https://surrealdb.com/docs/reference/php/v1/concepts/data-types

# Data types

The SurrealDB SDK for PHP enables simple and advanced querying of a remote database.

The PHP SDK translates all SurrealQL datatypes into native PHP types, or a custom implementation. This document describes all datatypes, and links to their respective documentation.

## Data types overview

<table>
    <thead>
        <tr>
            <th colspan="1" scope="col">Datatype</th>
            <th colspan="1" scope="col">Kind</th>
            <th colspan="2" scope="col">Documentation</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="1" scope="row">String</td>
            <td colspan="1" scope="row">Native</td>
            <td colspan="2" scope="row">
                <a href="https://www.php.net/manual/en/language.types.string.php">
                    <code>string</code> on php.net
                </a>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Int</td>
            <td colspan="1" scope="row">Native</td>
            <td colspan="2" scope="row">
                <a href="https://www.php.net/manual/en/language.types.integer.php">
                    <code>integer</code> on php.net
                </a>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Float</td>
            <td colspan="1" scope="row">Native</td>
            <td colspan="2" scope="row">
                <a href="https://www.php.net/manual/en/language.types.float.php">
                    <code>float</code> on php.net
                </a>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Bool</td>
            <td colspan="1" scope="row">Native</td>
            <td colspan="2" scope="row">
                <a href="https://www.php.net/manual/en/language.types.boolean.php">
                    <code>bool</code> on php.net
                </a>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">null</td>
            <td colspan="1" scope="row">Native</td>
            <td colspan="2" scope="row">
                <a href="https://www.php.net/manual/en/language.types.null.php">
                    <code>null</code> on php.net
                </a>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Array / Associative</td>
            <td colspan="1" scope="row">Native</td>
            <td colspan="2" scope="row">
                <a href="https://www.php.net/manual/en/language.types.array.php">
                    <code>array</code> on php.net
                </a>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Datetime</td>
            <td colspan="1" scope="row">Native</td>
            <td colspan="2" scope="row">
                <a href="https://www.php.net/manual/en/class.datetime">
                    <code>DateTime</code> on php.net
                </a>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Binary</td>
            <td colspan="1" scope="row">Custom</td>
            <td colspan="2" scope="row">
                <a href="https://github.com/welpie21/cbor.php/blob/main/src/utils/CborByteString.php">
                    <code>CborByteString</code>
                </a>
            </td>
        </tr>
		<tr>
            <td colspan="1" scope="row">None</td>
            <td colspan="1" scope="row">Custom</td>
            <td colspan="2" scope="row">
                <a href="https://github.com/surrealdb/surrealdb.php/blob/main/src/Cbor/Types/None.php">
                    <code>[None](#none)</code>
                </a>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">RecordId</td>
            <td colspan="1" scope="row">Custom</td>
            <td colspan="2" scope="row">
                <code>[RecordId](#recordid)</code>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Uuid</td>
            <td colspan="1" scope="row">Custom</td>
            <td colspan="2" scope="row">
                <code>[Uuid](#uuid)</code>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Duration</td>
            <td colspan="1" scope="row">Custom</td>
            <td colspan="2" scope="row">
                <code>[Duration](#duration)</code>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Geometry</td>
            <td colspan="1" scope="row">Custom</td>
            <td colspan="2" scope="row">
                <code>[Geometry](#geometry)</code>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Decimal</td>
            <td colspan="1" scope="row">Custom</td>
            <td colspan="2" scope="row">
                <code>[Decimal](#decimal)</code>
            </td>
        </tr>
        <tr>
            <td colspan="1" scope="row">Table</td>
            <td colspan="1" scope="row">Custom</td>
            <td colspan="2" scope="row">
                <code>[Table](#table)</code>
            </td>
        </tr>
    </tbody>
</table>

<br /><br />

##  `RecordId`

When you receive a RecordId from SurrealDB, it will always be represented as a `RecordId` class.
This class holds a `tb` and `id` field, representing the table name, and a unique identifier for the record on that table.
A `RecordId` can be converted into a string, and will be represented as such when it's converted to JSON.

```php title="Signature"
new RecordId(string $tb, string|int|array $id)
```

### Working with a `RecordId`

```php title="Constructing"
// table is "person"
// unique identifier on the table is "john"
$rid = new RecordId("person", "john");
```

```php title="Extracting data"
// Simple
$rid = new RecordId("person", "john");
$rid->tb // "person"
$rid->id // "john"

// Complex
$rid = new RecordId("recording", ["city" => "London", "data" => 123 ]);

$rid->id         // [ "city" => "London", "data" => 123 ]
$rid->id["city"] // "London"
$rid->id["data"] // 123
```

### Convert to String

The PHP SDK efficiently handles escaping the `tb` and `id` parts in Record Id's into their string counterparts.
Below are some examples

```php title="Simple"
$rid = (new RecordId("table", 123))->toString();
// 'table:123'
$rid = (new RecordId("table", "abc"))->toString();
// 'table:abc'
```

```php title="Complex characters"
$rid = (new RecordId("table", "123"))->toString();
// 'table:`123`'
$rid = (new RecordId("table", "123withletters"))->toString();
// 'table:123withletters'
$rid = (new RecordId("table", "complex-string"))->toString();
// 'table:`complex-string`'
$rid = (new RecordId("table-name", 123))->toString();
// '`table-name`:123'
```

```php title="Objects and Arrays"
$rid = (new RecordId("table", ["city" => "London"]))->toString();
// 'table:{ city: "London" }'
$rid = (new RecordId("table", ["London"]))->toString();
// 'table:["London"]'
```

### Send back string

If you need to send back a Record Id in string format, you can do so with the `StringRecordId` class.

We do not implement the parsing of Record Ids in the PHP SDK, as that would mean that we need to be able to parse any SurrealQL value,
which comes with a cost. Instead you can send it over as a string with `StringRecordId`, allowing the server to handle the parsing.

```php
new StringRecordId("person:john");
```

<br />

## `Geometry`

When a Geometry is sent back from SurrealDB, be it a `Point`, `Line`, `Polygon`, `MultiPoint`, `MultiLine`, `MultiPolygon` or `Collection`, it will be represented as a derivative of the `Geometry` class.

### Methods

Below are all the methods implemented across all geometry derivatives.

#### `->toJSON()`

Used to convert a geometry to a GeoJSON representation

```php title="Signature"
Geometry->toJson();
```

```php title="Example"
$line = new GeometryLine([
    new GeometryPoint([1, 2]),
    new GeometryPoint([3, 4]),
]);

$line->toJson();                    // '{ type: "LineString", coordinates: [ [1, 2], [3, 4] ] }'
json_encode($line);                 // '{ type: "LineString", coordinates: [ [1, 2], [3, 4] ] }'
```

#### `->is()`

Used to check if geometry X is exactly equal to geometry Y

```php title="Signature"
Geometry->is(Geometry $geometry)
```

```php title="Example"
$point1 = new GeometryPoint([1, 2]);
$point2 = new GeometryPoint([3, 4]);
$line = new GeometryLine([$point1, $point2]);

$point1->is($point1);      // true
$point1->is($point2);      // false
$point1->is($line);        // false

// Checks the inner values, does not need to be the same instance
$duplicate = new GeometryPoint([1, 2]);
$point1->is($duplicate);   // true
```

#### `->clone()`

Used to deeply clone a geometry. Creates a new replica of the original instance, but changing the new instance won't affect the other.

```php title="Signature"
Geometry->clone()
```

### Properties

#### `->coordinates`

A getter property, representing the coordinates as shown in GeoJSON format for X Geometry

```php title="Signature"
Geometry.coordinates
```

### Derivatives

#### `GeometryPoint`

A [point](/docs/reference/query-language/language-primitives/data-types/geometries.md#point) in space, made up of a long and lat coordinate, automatically converted to a float.

```php title="Signature"
new GeometryPoint([int|float|Decimal $long, int|float|Decimal $lat]);
```

#### `GeometryLine`

A line, made up of two or more points

```php title="Signature"
new GeometryLine([GeometryPoint, GeometryPoint, ...GeometryPoint[]]);
```

#### `GeometryPolygon`

A polygon, made up of self-closing lines

**Note**: The lines inside the polygon will automatically be closed if not already, meaning that the last point will be the same as the first.

```php title="Signature"
new GeometryPolygon([GeometryLine, ...GeometryLine[]]);
```

#### `GeometryMultiPoint`

A collection of one or more points

```php title="Signature"
new GeometryMultiPoint([GeometryPoint, ...GeometryPoint[]]);
```

#### `GeometryMultiLine`

A collection of one or more lines

```php title="Signature"
new GeometryMultiLine([GeometryLine, ...GeometryLine[]]);
```

#### `GeometryMultiPolygon`

A collection of one or more polygons

```php title="Signature"
new GeometryMultiPolygon([GeometryPolygon, ...GeometryPolygon[]]);
```

#### `GeometryCollection`

A collection of one or more `Geometry` derivatives

```php title="Signature"
new GeometryCollection([Geometry, ...Geometry[]]);
```

<br />

## `Decimal`

Because PHP does not support Decimals natively,
our SDK represents them in a `Decimal` class as a string.
This means if you want to work with Decimals, you will need to use an external library for this.

```php title="Signature"
new Decimal(string|float|Decimal $decimal);
```

### Converting to string

```php
$decimal = new Decimal("123.456");
decimal->toString(); // "123.456"
```

### Converting to JSON

A `Decimal` will be represented as a string in JSON to perserve accuracy

```php
$decimal = new Decimal("123.456");
$decimal->toJson();                  // "123.456"
json_encode(decimal);                // "123.456"
```

<br />

## `Table`

When you get a table name sent back from SurrealDB, it will be represented as a `Table` class.

```php title="Signature"
new Table(string $table);
```

### Converting to string

```php
$table = new Table("table");
$table->toString();              // "table"
```

### Converting to JSON

A `Table` will be represented as a string in JSON

```php
$table = new Table("table");
$table->jsonSerializable();      // "table"
json_encode($table);             // "table"
```

---

Source: https://surrealdb.com/docs/reference/php/v1/concepts/executing-queries

# Executing queries

Interact with the database and perform CRUD operations using version 1 of the SurrealDB PHP SDK.

The methods below are used to interact with the database and perform CRUD operations.
You can also use the [`query`](/docs/reference/php/v1/methods/query.md) method to run [SurrealQL statements](/docs/reference/query-language/statements/overview.md) against the database.

## Creating records

If we wish to create a new record in the database, we can use the [`create`](/docs/reference/php/v1/methods/create.md) method. The first argument
is the table name and the second argument is an associative array with the field names and values.

```php
$person = $db->create("person:tobie", [
	"name" => "Tobie",
	"lastname" => "Morgan Hitchcock",
	"age" => 30,
	"hobbies" => ["reading", "coding"]
]);
```

## Selecting records

After creating a record, you can use the [`select`](/docs/reference/php/v1/methods/select.md) method to fetch the newly created person.
The first argument is the newly created person's ID or a string which is the table name.

```php
$person = $db->select($person->id);
```

Or you can fetch it manually by using the RecordID or RecordStringId.

```php
// using the StringRecordId
$id = StringRecordId::create("person:tobie");
$person = $db->select($id);

// using the RecordId
$id = RecordId::create("person", "tobie");
$person = $db->select($id);
```

## Updating records

To update a record, you can use the [`update`](/docs/reference/php/v1/methods/update.md) method. The first argument is the RecordID or a StringRecordId,
and the second argument is an associative array with the field names and values. Updating a record can be done in 3 ways:

**update**

The [`update`](/docs/reference/php/v1/methods/update.md) method will replace the entire record with the new values. So make sure you include all the fields in the associative array.
		```php
		$person = $db->update($person->id, [
			"name" => "Tobie",
			"lastname" => "Morgan Hitchcock",
			"age" => 31
		]);
		```
		You can find more information about updating a record using the update method in [the `UPDATE` statement reference](/docs/reference/query-language/statements/update.md).

**merge**

The [`merge`](/docs/reference/query-language/statements/update.md#merge-clause) method will merge the new values with the existing record. If the field already exists, it will be replaced with the new value.
		```php
		$person = $db->merge($person->id, [
			"age" => 31
		]);
		```
		You can find more information about updating a record using the merge method in [the RPC protocol reference](/docs/reference/rest-api/rpc-protocol.md#merge).

**patch**

The [`patch`](/docs/reference/php/v1/methods/patch.md) method will update a field in a single or multiple record(s) based on the path provided.
		```php
		$person = $db->patch($person->id, [
			"path" => "/hobbies/0",
			"op" => "replace",
			"value" => "writing"
		]);
		```
		You can find more information about updating a record using the patch method in [the `UPDATE` statement reference](/docs/reference/query-language/statements/update.md).

## Deleting records

To delete a record, you can use the [`delete`](/docs/reference/php/v1/methods/delete.md) method. The first argument is the RecordID or a StringRecordId.

```php
$db->delete($person->id);
```

or we can use the RecordId or StringRecordId to delete the record.

```php
$id = StringRecordId::create("person:tobie");
$db->delete($id);

$id = RecordId::create("person", "tobie");
$db->delete($id);
```

---

Source: https://surrealdb.com/docs/reference/php/v1/installation

# Installation

Install version 1 of the SurrealDB PHP SDK with Composer.

Version 1 of the PHP SDK is installed with the [Composer](https://getcomposer.org/download/) package manager. It requires PHP `8.2` or later and the `curl` extension.

## Install the SDK

Run the following command in your project to install the latest stable release.

```bash
composer require surrealdb/surrealdb.php:^1.0
```

## Import the SDK

Include the [Composer autoloader](https://getcomposer.org/doc/01-basic-usage.md#autoloading), then import the `Surreal` class.

```php
require __DIR__ . '/vendor/autoload.php';

use Surreal\Surreal;
```

You can now create a `Surreal` instance and connect to your database.

## Next steps

- [Connecting to SurrealDB](/docs/reference/php/v1/concepts/connecting.md) to open a connection
- [Getting started](/docs/languages/php.md) to run your first queries

---

Source: https://surrealdb.com/docs/reference/php/v1/methods

# SDK methods

List of methods available in the SurrealDB SDK for PHP. Learn how to connect to a database, query data, and manage authentication.

The SurrealDB SDK for PHP has a single SurrealDB class that provides methods for querying a remote SurrealDB database.
The class is designed to be simple to use and easy to understand for developers who are new to PHP or SurrealDB.
This page lists out the methods that are available in the SurrealDB class.

## Initialisation methods

<table>
	<thead>
		<tr>
			<th scope="col">Function</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/connect.md"> <code> $db->connect($url, $options) </code></a></td>
			<td scope="row" data-label="Description">Connects to a local or remote database endpoint</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/close.md"> <code> $db->close() </code></a></td>
			<td scope="row" data-label="Description">Closes the persistent connection to the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/use.md"> <code> $db->use($options)</code></a></td>
			<td scope="row" data-label="Description">Switch to a specific namespace and database</td>
		</tr>
		<tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/let.md"> <code>$db->let($key,$val)</code></a></td>
            <td scope="row" data-label="Description">Assigns a value as a parameter for this connection</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/unset.md"> <code>$db->unset($key)</code></a></td>
            <td scope="row" data-label="Description">Removes a parameter for this connection</td>
        </tr>
	</tbody>
</table>

## Query methods

<table>
    <thead>
        <tr>
            <th scope="col">Function</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/query.md"> <code>$db->query($sql,$vars)</code></a></td>
            <td scope="row" data-label="Description">Runs a set of [SurrealQL statements](/docs/reference/query-language.md) against the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/select.md"> <code>$db->select($thing)</code></a></td>
            <td scope="row" data-label="Description">Selects all records in a table, or a specific record</td>
        </tr>
    </tbody>
</table>

## Mutation methods

<table>
    <thead>
        <tr>
            <th scope="col">Function</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/create.md"> <code>$db->create($thing,$data)</code></a></td>
            <td scope="row" data-label="Description">Creates a record in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/insert.md"> <code>$db->insert($thing,$data)</code></a></td>
            <td scope="row" data-label="Description">Inserts one or multiple records in the database</td>
        </tr>
		<tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/insert-relation.md"> <code>$db->insertRelation($thing,$data)</code></a></td>
            <td scope="row" data-label="Description">Inserts one or multiple records in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/update.md"> <code>$db->update($thing,$data)</code></a></td>
            <td scope="row" data-label="Description">Updates all records in a table, or a specific record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/merge.md"> <code>$db->merge($thing,$data)</code></a></td>
            <td scope="row" data-label="Description">Modifies all records in a table, or a specific record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/patch.md"> <code>$db->patch($thing,$data)</code></a></td>
            <td scope="row" data-label="Description">Applies JSON Patch changes to all records in a table, or a specific record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/delete.md"> <code>$db->delete($thing,$data)</code></a></td>
            <td scope="row" data-label="Description">Deletes all records, or a specific record</td>
        </tr>
    </tbody>
</table>

## Authentication methods

<table>
    <thead>
        <tr>
            <th scope="col">Function</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/signup.md"> <code>$db->signup($vars)</code></a></td>
            <td scope="row" data-label="Description">Signs this connection up to a specific authentication scope</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/signin.md"> <code>$db->signin($vars)</code></a></td>
            <td scope="row" data-label="Description">Signs this connection in to a specific authentication scope</td>
        </tr>
		<tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/invalidate.md"> <code>$db->invalidate()</code></a></td>
            <td scope="row" data-label="Description">Invalidates the authentication for the current connection</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/authenticate.md"> <code>$db->authenticate(token)</code></a></td>
            <td scope="row" data-label="Description">Authenticates the current connection with a JWT token</td>
        </tr>
		<tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/info.md"> <code>$db->info()</code></a></td>
            <td scope="row" data-label="Description">Returns the record of an authenticated scope user</td>
        </tr>
    </tbody>
</table>

## Utility methods

<table>
	<thead>
		<tr>
			<th scope="col">Function</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/import.md"> <code>$db->import($content, $username, $password)</code></a></td>
			<td scope="row" data-label="Description">Imports data into the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/export.md"> <code>$db->export($username, $password)</code></a></td>
			<td scope="row" data-label="Description">Exports data from the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/health.md"> <code>$db->health()</code></a></td>
			<td scope="row" data-label="Description">Checks wether the database is running and the storage engine is healthy</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/status.md"> <code>$db->status()</code></a></td>
			<td scope="row" data-label="Description">Wether the database is running or is reachable</td>
		</tr>
	        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/query-raw.md"> <code>$db-&gt;queryRaw($query, $params)</code></a></td>
            <td scope="row" data-label="Description">Runs SurrealQL statements and returns the raw, unprocessed response</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/upsert.md"> <code>$db-&gt;upsert($thing, $data)</code></a></td>
            <td scope="row" data-label="Description">Creates a record if it does not exist, or updates it if it does</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/run.md"> <code>$db-&gt;run($function, $version, $params)</code></a></td>
            <td scope="row" data-label="Description">Runs a SurrealQL function on the server</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="/docs/reference/php/v1/methods/version.md"> <code>$db-&gt;version()</code></a></td>
            <td scope="row" data-label="Description">Retrieves the version of the SurrealDB instance</td>
        </tr>
</tbody>
</table>

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/authenticate

# authenticate

Authenticate the current connection with a JWT token using the authenticate method in the SurrealDB PHP SDK.

Authenticates the current connection with a JWT token.

```php title="Method Syntax"
$db->authenticate($token)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>token</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The JWT authentication token.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
$db->authenticate('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJTdXJyZWFsREIiLCJpYXQiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMiwiZXhwIjoxODM2NDM5MDIyLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJTQyI6InVzZXIiLCJJRCI6InVzZXI6dG9iaWUifQ.N22Gp9ze0rdR06McGj1G-h2vu6a6n9IVqUbMFJlOxxA');
```

You can invalidate the authentication for the current connection using the [`invalidate()` method](/docs/reference/php/v1/methods/invalidate.md).

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/close

# close

Close the persistent connection to the database using the close method in the SurrealDB PHP SDK.

Closes the persistent connection to the database.

```php title="Method Syntax"
$db->close()
```

## Example usage
```php
$db->close();
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/connect

# connect

Connect to a local or remote database endpoint using the connect method in the SurrealDB PHP SDK.

Connects to a local or remote database endpoint.

```php title="Method Syntax"
$db->connect($host, $options)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>host</code>
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The url of the database endpoint to connect to.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>options</code>
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>associative array</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                An object with options to initiate the connection to SurrealDB.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

There are several ways to connect to a database endpoint. You can connect to a local or remote endpoint, specify a namespace and database pair to use, authenticate with an existing token, authenticate using a pair of credentials, or use advanced custom logic to prepare the connection to the database.

```php
// Connect to a local endpoint
$db->connect('http://127.0.0.1:8000/rpc');

// Connect to a remote endpoint
$db->connect('https://cloud.surrealdb.com/rpc');

// Specify a namespace and database pair to use
$db->connect('https://cloud.surrealdb.com/rpc', [
	"namespace" => "surrealdb",
	"database" => "docs",
]);
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/create

# create

Create a record in the database using the create method with the SurrealDB PHP SDK.

Creates a record in the database.

```php title="Method Syntax"
$db->create($thing, $data)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>, <code>RecordId</code> or <code>StringRecordId</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/php/v1/concepts/data-types.md#recordid) to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
               <label label="optional" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>mixed</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to create.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Create a record with a random ID
[$person] = $db->create('person');

// Create a record with a specific ID
$person = $db->create(new RecordId('person', 'tobie'), [
	"name" => 'Tobie',
	"settings" => [
		"active" => true,
		"marketing" => true,
	],
]);

// The content you are creating the record with might differ from the return type
[$record] = $db->create(
    new RecordId('person', 'tobie'),
    ["name" => "Tobie"]
);
```

## Translated query
This function will run the following query in the database.

```surql
CREATE $thing CONTENT $data;
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/delete

# delete

Delete records from a table in the database using the delete method with the SurrealDB PHP SDK.

Deletes all records in a table, or a specific record, from the database.

```php title="Method Syntax"
$db->delete($thing)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>, <code>RecordId</code> or <code>StringRecordId</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/php/v1/concepts/data-types.md#recordid) to delete.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Delete all records from a table
$db->delete('person');

// Delete a specific record from a table
$db->delete(new RecordId('person', 'h5wxrf2ewk8xjxosxtyc'));
```

## Translated query
This function will run the following query in the database.

```surql
DELETE $thing;
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/export

# export

Export data from a local or remote database using the export method with the SurrealDB PHP SDK.

Exports data from a table.

> [!NOTE]
> This method is only available on a remote database targeted with the http protocol.

```php title="Method Syntax"
$db->export($username, $password);
```

## Arguments

<table>
	<thead>
		<tr>
			<th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
			<th colspan="2" scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>username</code>
				<label label="required" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`string`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				The username to authenticate with.
			</td>
		</tr>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>password</code>
				<label label="required" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`string`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				The password to authenticate with.
			</td>
		</tr>
	</tbody>
</table>

## Example

```php title="Example"
// connect to the remote database. For the export to work, the database must exists with also existing data.
$db->connect('http://localhost:8080', [
	'namespace' => 'example',
	'database' => 'example',
]);

// Export data
$response = $db->export('admin', 'password');

// Create a file and write the response to it
$fp = fopen('exported_data.json', 'w');

// Write the response to the file
fwrite($fp, $response);

// Close the file
fclose($fp);
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/health

# health

Check the storage layer health of a local or remote database using the health method with the SurrealDB PHP SDK.

This method checks whether the database is running and the storage engine is running.

> [!NOTE]
> This method is only available on a remote database targeted with the http protocol.

```php title="Method Syntax"
$db->health();
```

## Example usage
```php
$health = $db->health();
echo "The health status is: $health."; // 200 or 500
```

The health function returns `200` if the database is running and healthy, and `500` if a failure occurred.

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/import

# import

Import data into an existing database using the import method with the SurrealDB PHP SDK.

Imports data into a table.

> [!NOTE]
> This method is only available on a remote database targeted with the http protocol.

```php title="Method Syntax"
$db->import($content, $username, $password);
```

## Arguments

<table>
	<thead>
		<tr>
			<th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
			<th colspan="2" scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>content</code>
				<label label="required" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`string`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				The content to import.
			</td>
		</tr>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>username</code>
				<label label="required" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`string`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				The username to authenticate with.
			</td>
		</tr>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>password</code>
				<label label="required" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`string`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				The password to authenticate with.
			</td>
		</tr>
	</tbody>
</table>

## Example

```php title="Example"
// connect and select the namespace + database that the import function will use to import the data
$db->connect('http://localhost:8080', [
	'namespace' => 'example',
	'database' => 'example',
]);

// grab file contents and import the data
$import = file_get_contents('data.surql');
$db->import($import, 'admin', 'password');
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/info

# info

The info method returns information about the authenticated user in the SurrealDB PHP SDK.

This method returns the authenticated record user.

```php title="Method Syntax"
$db->info()
```

## Example usage
```php
$user = $db->info();
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/insert

# insert

Insert one or multiple records in the database using the insert method with the SurrealDB PHP SDK.

Inserts one or multiple records in the database.

```php title="Method Syntax"
$db->insert($thing, $data)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or [`RecordId`](/docs/reference/php/v1/concepts/data-types.md#recordid) to insert to.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
               <label label="optional" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>associative array</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                Either a single document/record or an array of documents/records to insert
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Insert a single record
[$person] = $db->insert('person', [
	"name" => 'Tobie',
	"settings" => [
		"active" => true,
		"marketing" => true,
	],
]);

$person = $db->insert(new RecordId('person', 'tobie'), [
	"name" => 'Tobie',
	"settings" => [
		"active" => true,
		"marketing" => true,
	],
]);

// Insert multiple records
$people = $db->insert('person', [
	[
		"name" => 'Tobie',
		"settings" => [
			"active" => true,
			"marketing" => true,
		],
	],
	[
		"name" => 'Jaime',
		"settings" => [
			"active" => true,
			"marketing" => true,
		],
	],
]);

// The content you are creating the record with might differ from the return type
$people = $db->insert('person', [
	[ "name" => 'Tobie' ],
	[ "name" => 'Jaime' ],
]);
```

## Translated query
This function will run the following query in the database.

```surql
INSERT INTO $thing $data;
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/insert-relation

# insertRelation

Insert one or multiple relations in the database using the insertRelation method with the SurrealDB PHP SDK.

Inserts one or multiple relations in the database.

```php title="Method Syntax"
$db->insertRelation($thing, $data)
```

## Arguments

<table>
	<thead>
		<tr>
			<th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
			<th colspan="2" scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>thing</code>
				<label label="required" />
			</td>
			<td colspan="2" scope="row" data-label="Description">
				`string` or `Table`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				Target table to insert the relation to.
			</td>
		</tr>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>data</code>
			   <label label="optional" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`array`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				An array of relations to insert.
			</td>
		</tr>
	</tbody>
</table>

## Example usage
```php
// Insert a single relation
$relation = $db->insertRelation('person', [
	"id" => new RecordId('person', 'tobie'),
	"in" => new RecordId('company', 'surreal'),
	"out" => new RecordId('role', 'founder'),
]);

// Insert multiple relations
$relations = $db->insertRelation('person', [
	[
		"id" => new RecordId('person', 'tobie'),
		"in" => new RecordId('company', 'surreal'),
		"out" => new RecordId('role', 'founder'),
	],
	[
		"id" => new RecordId('person', 'jaime'),
		"in" => new RecordId('company', 'surreal'),
		"out" => new RecordId('role', 'cofounder'),
	],
]);
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/invalidate

# invalidate

Invalidate the authentication for the current connection using the invalidate method in the SurrealDB PHP SDK.

Invalidates the authentication for the current connection.

```php title="Method Syntax"
$db->invalidate()
```

## Example usage
```php
$db->invalidate();
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/let

# let

Assign parameters to a query using the let method in the SurrealDB PHP SDK.

Assigns a value as a parameter for this connection.

```php title="Method Syntax"
$db->let($name, $value)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>name</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the name of the variable.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>value</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>mixed</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns the value to the variable name.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Assign the variable on the connection
$db->let('name', [
	"first" => "Tobie",
	"last" => "Morgan Hitchcock",
]);

// Use the variable in a subsequent query
$db->query('CREATE person SET name = $name');

// Use the variable in a subsequent query
$db->query('SELECT * FROM person WHERE name.first = $name.first');
```

You can remove the variable from the connection using the [`unset()` method](/docs/reference/php/v1/methods/unset.md).

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/merge

# merge

The ->merge() method for the SurrealDB SDK for PHP merges record data with the specified data.

Modifies all records in a table, or a specific record, in the database.

```php title="Method Syntax"
$db->merge($thing, $data)
```

> [!NOTE]
> This function merges the current document / record data with the specified data.

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>, <code>RecordId</code> or <code>StringRecordId</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific [`RecordId`](/docs/reference/php/v1/concepts/data-types.md#recordid) to merge.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
               <label label="optional" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>mixed</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to merge.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Update all records in a table
$people = $db->merge('person', [
	"updated_at" => new Date(),
]);

// Update a record with a specific ID
$person = $db->merge(new RecordId('person', 'tobie'), [
	"updated_at" => new Date(),
	"settings" => [
		"active" => true,
	],
]);

// The content you are merging the record with might differ from the return type
$record = $db->merge(new RecordId('person', 'tobie'), [
	"name" => 'Tobie',
]);
```

## Translated query
This function will run the following query in the database.

```surql
UPDATE $thing MERGE $data;
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/patch

# patch

The ->patch() method for the SurrealDB SDK for PHP applies JSON patch changes to records in the database.

Applies JSON Patch changes to all records, or a specific record, in the database.

```php title="Method Syntax"
$db->patch($thing, $data, $diff)
```

> [!NOTE]
> This function patches the current document / record data with the specified JSON Patch data.

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>, <code>RecordId</code> or <code>StringRecordId</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific [`RecordId`](/docs/reference/php/v1/concepts/data-types.md#recordid) to patch.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
               <label label="optional" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>associative array</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The JSON Patch data with which to patch the records.
            </td>
        </tr>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>diff</code>
			   <label label="optional" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				<code>boolean</code>
			</td>
			<td colspan="2" scope="row" data-label="Description">
				Whether to return the diff of the patched record.
			</td>
		</tr>
    </tbody>
</table>

## Example usage
```php
// Update all records in a table
$people = $db->patch('person', [
		[ "op" => 'replace', "path" => '/created_at',
	    "value" => new Date() ],
]);

// Update a record with a specific ID
$person = $db->patch(new RecordId('person', 'tobie'), [
		[ "op" => 'replace', "path" => '/settings/active',
	    "value" => false ],
		[ "op" => 'add', "path" => '/tags', "value" => ['developer',
	    'engineer'] ],
	[ "op" => 'remove', "path" => '/temp' ],
]);
```

## Translated query
This function will run the following query in the database.

```surql
UPDATE $thing PATCH $data;
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/query

# query

The ->query() method for the SurrealDB SDK for PHP runs a set of SurrealQL statements against the database.

Runs a set of [SurrealQL statements](/docs/reference/query-language.md) against the database.

```php title="Method Syntax"
$db->query($query, $vars)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>$query</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>$vars</code>
               <label label="optional" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>associative array</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns variables which can be used in the query.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Assign the variable on the connection
$result = db->query(
	'CREATE person SET name = "John"; SELECT * FROM type::table($tb);',
	[ "tb" => "person" ]
);

// Get the first result from the first query
$created = $result[0]->result[0];

// Get all of the results from the second query
$people = $result[1]->result;
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/query-raw

# queryRaw

The queryRaw method in the SurrealDB PHP SDK allows you to execute raw SQL queries and return the raw RPC response.

With queryRaw, you will get back the raw RPC response.
In contrast to the .query() method, this will not throw for errors that occur in individual queries,
but will rather give those back as a string, and this will include the time it took to execute the individual queries.

```php title="Method Syntax"
$db->queryRaw($query, $params);
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>$query</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The query to execute
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>$params</code>
               <label label="optional" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>associative array</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                An associative array of parameters to bind to the query.
            </td>
        </tr>
    </tbody>
</table>

## Example

```php title="Example"
// Query the database for all users with the firstname "Tobie" with parameters.
$response = $db->queryRaw(
	'SELECT * FROM users WHERE firstname = $firstname',
	['firstname' => 'Tobie']
);

// Query the database for all users with the firstname "Tobie" without parameters.
$response = $db->queryRaw('SELECT * FROM users WHERE firstname = "Tobie"');
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/run

# run

The run method in the SurrealDB PHP SDK allows you to execute a defined SurrealQL function on the remote database.

Runs a defined SurrealQL function on the remote database.

```php title="Method Syntax"
$db->run($function, $version, $params);
```

## Arguments

<table>
	<thead>
		<tr>
			<th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
			<th colspan="2" scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>$function</code>
				<label label="required" />
			</td>
			<td colspan="2" scope="row" data-label="Description">
				<code>string</code>
			</td>
			<td colspan="2" scope="row" data-label="Description">
				The name of the function to run.
			</td>
		</tr>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>$version</code>
				<label label="optional" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`string` or `null`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				The version of the function to run.
			</td>
		</tr>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>$params</code>
				<label label="optional" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`array` or `null`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				An array of parameters to pass to the function.
			</td>
		</tr>
	</tbody>
</table>

## Example

```php title="Example"
$functionA = $db->run("fn::hello_world", null, ["Tobie"]);
$functionB = $db->run("fn::hello_world", "v1", ["Tobie"]);

// or with named arguments
$functionA = $db->run("fn::hello_world", params: ["Tobie"]);
$functionB = $db->run("fn::hello_world", version: "v1", params: ["Tobie"]);
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/select

# select

The ->select() method for the SurrealDB SDK for PHP selects all or specific records from the database.

Selects all records in a table, or a specific record, from the database.

```php title="Method Syntax"
$db->select($thing)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>, <code>RecordId</code> or <code>StringRecordId</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a [`RecordId`](/docs/reference/php/v1/concepts/data-types.md#recordid) to select.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Select all records from a table
$people = $db->select('person');

// Select a specific record from a table
$person = $db->select(new RecordId('person', 'h5wxrf2ewk8xjxosxtyc'));
$person = $db->select(new StringRecordId('person:h5wxrf2ewk8xjxosxtyc'));
```

## Translated query
This function will run the following query in the database.

```surql
SELECT * FROM $thing;
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/signin

# signin

The ->signin() method for the SurrealDB SDK for PHP signs in to a specific access method.

Signs in to a root, namespace, database or record user.

```php title="Method Syntax"
$db->signin([
    "namespace" => "main",
    "database" => "db",
    "access" => "account",
    // ... other variables
]);
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Properties</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Properties">
                <code>username</code>
                <label label="REQUIRED FOR ROOT, NAMESPACE & DATABASE" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The username of the database user
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Properties">
                <code>password</code>
                <label label="REQUIRED FOR ROOT, NAMESPACE & DATABASE" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The password of the database user
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Properties">
                <code>namespace</code>
                <label label="REQUIRED FOR DATABASE & ACCESS" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The namespace to sign in to
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Properties">
                <code>database</code>
                <label label="REQUIRED FOR ACCESS" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The database to sign in to
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Properties">
                <code>access</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The access method to sign in to. Also pass any variables used in the access definition.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Authenticate with a root user
$token = $db->signin([
	"username" => "root",
	"password" => "surrealdb",
]);

// Authenticate with a Namespace user
$token = $db->signin([
	"namespace" => "surrealdb",
	"username" => "tobie",
	"password" => "surrealdb",
]);

// Authenticate with a Database user
$token = $db->signin([
	"namespace" => "surrealdb",
	"database" => "docs",
	"username" => "tobie",
	"password" => "surrealdb",
]);

// Authenticate with a record access user
$token = $db->signin([
	"namespace" => "surrealdb",
	"database" => "docs",
	"access" => "user",

	// Also pass any properties required by the access definition
	"email" => "info@surrealdb.com",
	"pass" => "123456",
]);
```

You can invalidate the authentication for the current connection using the [`invalidate()` method](/docs/reference/php/v1/methods/invalidate.md).

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/signup

# signup

The ->signup() method for the SurrealDB SDK for PHP signs up to a specific access method.

Signs up to a specific authentication access method.

```php title="Method Syntax"
$db->signup([
    "namespace" => "main",
    "database" => "db",
    "access" => "account",
    // ... other variables
]);
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>namespace</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The namespace to sign up to
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>database</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The database to sign up to
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Properties">
                <code>access</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The access method to sign up to. Also pass any variables used in the access definition.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
$token = $db->signup([
	"namespace" => "surrealdb",
	"database" => "docs",
	"access" => "user",

	// Also pass any properties required by the access definition
	"email" => "info@surrealdb.com",
	"pass" => "123456",
]);
```

You can invalidate the authentication for the current connection using the [`invalidate()` method](/docs/reference/php/v1/methods/invalidate.md).

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/status

# status

The status method in the SurrealDB PHP SDK retrieves the current status of a remote database.

This method retrieves the current status of a remote database.

```php title="Method Syntax"
$db->status();
```

## Example usage
```php
$status = $db->status();
echo "The status code is: $status."; // 200 or 500
```

The status function returns `200` if the database is running and `500` if the database is down or cannot be reached.

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/unset

# unset

The ->unset() method for the SurrealDB SDK for PHP removes a parameter from the connection.

Removes a parameter for this connection.

```php title="Method Syntax"
$db->unset($key)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>key</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the name of the variable.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Remove the variable from the connection
$db->unset('name');
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/update

# update

The ->update() method for the SurrealDB SDK for Rust updates all or specific records in the database if they exist.

Updates all records in a table, or a specific record, in the database.

```php title="Method Syntax"
$db->update($thing, $data)
```

> [!NOTE]
> This function replaces the current document / record data with the specified data.

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>thing</code>
                <label label="required" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>string</code>, <code>RecordId</code> or <code>StringRecordId</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific [`RecordId`](/docs/reference/php/v1/concepts/data-types.md#recordid) to update.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
               <label label="optional" />
            </td>
			<td colspan="2" scope="row" data-label="Type">
				<code>mixed</code>
			</td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to update.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
// Update all records in a table
$people = $db->update('person');

// Update a record with a specific ID
$person = $db->update(new RecordId('person', 'tobie'), [
	"name" => 'Tobie',
	"settings" => [
		"active" => true,
		"marketing" => true,
	],
]);

// The content you are updating the record with might differ from the return type
$record = $db->update(new RecordId('person', 'tobie'), [
	"name" => 'Tobie',
]);
```

## Translated query
This function will run the following query in the database.

```surql
UPDATE $thing CONTENT $data;
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/upsert

# upsert

The upsert method in the SurrealDB PHP SDK allows you to create or update a record in a table.

Creates or updates a record in a table.

```php title="Method Syntax"
$db->upsert($thing, $data);
```

## Arguments

<table>
	<thead>
		<tr>
			<th colspan="2" scope="col">Arguments</th>
			<th colspan="2" scope="col">Type</th>
			<th colspan="2" scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>$thing</code>
				<label label="required" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`RecordId`. `StringRecordId` or `string`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				The collection to upsert the record to.
			</td>
		</tr>
		<tr>
			<td colspan="2" scope="row" data-label="Arguments">
				<code>$data</code>
				<label label="required" />
			</td>
			<td colspan="2" scope="row" data-label="Type">
				`mixed`
			</td>
			<td colspan="2" scope="row" data-label="Description">
				The record to upsert.
			</td>
		</tr>
	</tbody>
</table>

## Example

```php title="Example"
$id = new RecordId('users', 'tobie');

// Upsert a record to the "users" collection.
$response = $db->upsert($id, [
	'firstname' => 'Tobie',
	'lastname' => 'Hitchcock',
]);
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/use

# use

The ->use() method for the SurrealDB SDK for PHP switches to a specific namespace and database.

Switch to a specific namespace and database. If only the ns or db property is specified, the current connection details will be used to fill the other property.

```php title="Method Syntax"
$db->use([ "namespace" => "...", "database" => "..." ]);
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Arguments</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>namespace</code>
                <label label="initially required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Switches to a specific namespace.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>database</code>
                <label label="initially required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Switches to a specific database.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```php
$db->use([
    "namespace" => "surrealdb",
    "database" => "docs"
]);
```

---

Source: https://surrealdb.com/docs/reference/php/v1/methods/version

# version

The version method in the SurrealDB PHP SDK retrieves the current version of a remote database.

This method retrieves the current version of a remote database.

```php title="Method Syntax"
$db->version();
```

## Example usage
```php
try {
	$version = $db->version();
	echo "The remote database is running version $version.";
} catch (Exception $e) {
	echo "An error occurred while retrieving the version: " . $e->getMessage();
}

```

---

Source: https://surrealdb.com/docs/reference/php/v2

# PHP SDK v2

Version 2 of the SurrealDB SDK for PHP is a rewrite with a fluent query builder, typed credentials, and a PSR-based transport layer.

Version 2 is a rewrite of the PHP SDK. It keeps the same goal as v1, connecting your PHP application to SurrealDB, but changes most of the public surface. Queries are built with a fluent builder (`$db->select($table)->where(...)->limit(10)`), credentials are typed value objects, and the transport layer is built on PSR HTTP interfaces so you can swap in your own client.

> [!IMPORTANT]
> Version 2 is published as `2.0.0-alpha.1`. It is an alpha with breaking changes against [v1](/docs/reference/php/v1.md), and the public API may still change before a stable release. Pin the exact version when installing.
>
> These docs are a work in progress. While the SDK is in alpha, expect them to change substantially as decisions about the public-facing API are settled.

The SDK requires PHP `8.4` or later and works with SurrealDB server versions `1.0.0` up to (but not including) `4.0.0`. The version is checked on connect unless you disable it.

If you are upgrading an existing project, start with the [migration guide](/docs/reference/php/v2/migration.md).

## Getting started

- [Installation](/docs/reference/php/v2/installation.md) - Install the alpha release with Composer and add a PSR-18 HTTP client.

- [Quickstart](/docs/reference/php/versions/v2-alpha.md) - Connect to SurrealDB and run your first queries in a few minutes.

## Core concepts

- [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) - Open a connection over WebSocket or HTTP, select a namespace and database, and handle reconnection.

- [Authentication](/docs/reference/php/v2/concepts/authentication.md) - Sign in and sign up with typed credentials, then manage tokens.

- [Executing queries](/docs/reference/php/v2/concepts/executing-queries.md) - Run raw SurrealQL or use the fluent query builders for select, create, update, and delete.

- [Live queries](/docs/reference/php/v2/concepts/live-queries.md) - Subscribe to real-time changes over a WebSocket connection.

## Advanced

- [Observability](/docs/reference/php/v2/concepts/observability.md) - Emit traces and metrics through OpenTelemetry or PSR-3 adapters.

- [Middleware](/docs/reference/php/v2/concepts/middleware.md) - Intercept every RPC with built-in or custom middleware.

- [Events](/docs/reference/php/v2/concepts/events.md) - Observe lifecycle and RPC traffic with PSR-14 events.

- [Sessions](/docs/reference/php/v2/concepts/sessions.md) - Run multiple independent sessions over one connection.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.php)
- [Composer package](https://packagist.org/packages/surrealdb/surrealdb.php)

---

Source: https://surrealdb.com/docs/reference/php/v2/api/core

# Core classes

Reference for the Surreal class, the ConnectionController, and the supporting connection types in version 2 of the PHP SDK.

The `Surreal` class is the entry point for version 2 of the SDK. It manages the connection, the session, authentication, and query execution.

**Namespace:** `SurrealDB\SDK\Surreal`

**Source:** [src/Surreal.php](https://github.com/surrealdb/surrealdb.php/blob/main/src/Surreal.php)

## Constructor

```php title="Syntax"
new Surreal(?DriverOptions $options = null)
```

`DriverOptions` customises driver-wide behaviour such as the codec, HTTP client, scheduler, and middleware. Pass `null` for the defaults.

```php
use SurrealDB\SDK\Surreal;

$db = new Surreal();
```

## Connection methods

### `connect()` {#connect}

Connect to a SurrealDB endpoint.

```php title="Syntax"
$db->connect(string|Endpoint $url, ?ConnectOptions $options = null): void
```

<table>
    <thead>
        <tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr>
            <td><code>url</code> <label label="required" /></td>
            <td><code>string | Endpoint</code></td>
            <td>The endpoint to connect to, such as <code>ws://127.0.0.1:8000/rpc</code>.</td>
        </tr>
        <tr>
            <td><code>options</code> <label label="optional" /></td>
            <td><code><a href="#connectoptions">ConnectOptions</a></code></td>
            <td>Namespace, database, authentication, and reconnection settings.</td>
        </tr>
    </tbody>
</table>

```php
$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    namespace: 'surrealdb',
    database: 'docs',
));
```

### `close()` {#close}

Close the connection and release its resources.

```php title="Syntax"
$db->close(): void
```

### `status()` {#status}

Return the current [`ConnectionStatus`](#connectionstatus).

```php title="Syntax"
$db->status(): ConnectionStatus
```

### `isConnected()` {#isconnected}

Return whether the connection is established. Equivalent to `status() === ConnectionStatus::Connected`.

```php title="Syntax"
$db->isConnected(): bool
```

### `health()` {#health}

Throw if the instance is unreachable, otherwise return nothing.

```php title="Syntax"
$db->health(): void
```

### `version()` {#version}

Return the server version string, for example `surrealdb-2.1.0`.

```php title="Syntax"
$db->version(): string
```

### `isFeatureSupported()` {#isfeaturesupported}

Return whether a feature is available on the current connection and server.

```php title="Syntax"
$db->isFeatureSupported(Feature $feature): bool
```

### `subscribe()` {#subscribe}

Subscribe to a lifecycle event: `connecting`, `connected`, `reconnecting`, `disconnected`, `error`, `auth`, or `using`. Returns a closure that removes the listener.

```php title="Syntax"
$db->subscribe(string $event, callable $listener): Closure
```

### `connection()` {#connection}

Return the underlying [`ConnectionController`](#connectioncontroller) for advanced operations such as transactions, sessions, and import/export.

```php title="Syntax"
$db->connection(): ConnectionController
```

## Session methods

### `use()` {#use}

Select a namespace and an optional database.

```php title="Syntax"
$db->use(?string $namespace, ?string $database = null): void
```

### `let()` {#let}

Define a session parameter, available in later queries as `$name`.

```php title="Syntax"
$db->let(string $name, mixed $value): void
```

### `unset()` {#unset}

Remove a session parameter.

```php title="Syntax"
$db->unset(string $name): void
```

## Authentication methods

### `signin()` {#signin}

Sign in with a [credential object](/docs/reference/php/v2/concepts/authentication.md#credential-types) or an array. Returns a [`Tokens`](#tokens) object.

```php title="Syntax"
$db->signin(Credentials|array $auth): Tokens
```

### `signup()` {#signup}

Sign up a new record user. Returns a [`Tokens`](#tokens) object.

```php title="Syntax"
$db->signup(Credentials|array $auth): Tokens
```

### `authenticate()` {#authenticate}

Authenticate the session with an existing token.

```php title="Syntax"
$db->authenticate(Token|string $token): void
```

### `invalidate()` {#invalidate}

Clear the session's authentication.

```php title="Syntax"
$db->invalidate(): void
```

## Query methods

### `query()` {#query}

Execute a [`BoundQuery`](/docs/reference/php/v2/api/utilities.md#boundquery), returning one result per statement.

```php title="Syntax"
$db->query(BoundQuery $query): array
```

### `run()` {#run}

Execute raw SurrealQL with optional bindings, returning one result per statement.

```php title="Syntax"
$db->run(string $surql, array $bindings = []): array
```

### Statement builders

These methods start a fluent [query builder](/docs/reference/php/v2/api/query-builders.md). Call `execute()` to run it.

- `select($what)`, `create($what)`, `update($what)`, `upsert($what)`, `delete($what)`
- `insert($tableOrData, $data = null)`, `relate($from, $edge, $to, $data = null)`
- `call($name, $version = null, $args = [])`, `auth()`

### `live()` {#live}

Subscribe to a live query by its ID. Returns an iterable of [`LiveMessage`](#livemessage) objects.

```php title="Syntax"
$db->live(string $queryUuid): iterable
```

Iterating the result blocks the process while it waits for messages. See [running without blocking the application](/docs/reference/php/v2/concepts/live-queries.md#running-without-blocking-the-application) for consuming live queries in a worker.

---

## Supporting types

## `ConnectOptions` {#connectoptions}

Per-connection settings passed to `connect()`.

**Namespace:** `SurrealDB\SDK\Connection\ConnectOptions`

```php title="Constructor"
new ConnectOptions(
    ?string $namespace = null,
    ?string $database = null,
    Credentials|Token|AuthProviderInterface|Closure|string|null $authentication = null,
    bool $versionCheck = true,
    bool $invalidateOnExpiry = false,
    bool|array|ReconnectStrategyInterface $reconnect = true,
)
```

## `ConnectionStatus` {#connectionstatus}

A string-backed enum with the connection lifecycle states.

**Namespace:** `SurrealDB\SDK\Connection\ConnectionStatus`

**Values:** `Disconnected`, `Connecting`, `Reconnecting`, `Connected`

## `Tokens` {#tokens}

The result of `signin()` and `signup()`: an access token and an optional refresh token.

**Namespace:** `SurrealDB\SDK\Auth\Tokens`

```php title="Properties"
$tokens->access;   // ?string
$tokens->refresh;  // ?string
```

## `LiveMessage` {#livemessage}

A single live query notification.

**Namespace:** `SurrealDB\SDK\Live\LiveMessage`

```php title="Properties"
$message->queryId;  // string
$message->action;   // LiveAction (Create, Update, Delete, Killed)
$message->record;   // mixed: the affected record id
$message->value;    // mixed: the new record value
```

---

## `ConnectionController` {#connectioncontroller}

The controller orchestrates the connection. Access it with `$db->connection()` for operations that are not on the `Surreal` facade.

**Namespace:** `SurrealDB\SDK\Connection\ConnectionController`

## Transactions

```php title="Syntax"
$db->connection()->begin(?string $session = null): string
$db->connection()->commit(string $txn, ?string $session = null): void
$db->connection()->cancel(string $txn, ?string $session = null): void
```

## Import and export

```php title="Syntax"
$db->connection()->importSql(string $data): void
$db->connection()->exportSql(array $options = []): string
```

```php
$sql = $db->connection()->exportSql();
$db->connection()->importSql($sql);
```

## Token renewal

```php title="Syntax"
$db->connection()->refresh(Tokens $tokens, ?string $session = null): Tokens
$db->connection()->revoke(Tokens $tokens, ?string $session = null): void
```

## Sessions

```php title="Syntax"
$db->connection()->sessions(): array
$db->connection()->createSession(?string $clone = null): string
$db->connection()->destroySession(?string $session): void
```

## See also

- [Query Builders](/docs/reference/php/v2/api/query-builders.md) for the fluent statement API
- [Data types](/docs/reference/php/v2/api/data-types.md) for the value classes
- [Utilities](/docs/reference/php/v2/api/utilities.md) for `BoundQuery`, enums, and driver options
- [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) for the connection guide

---

Source: https://surrealdb.com/docs/reference/php/v2/api/data-types

# Data types

Reference for the SurrealQL value classes in version 2 of the PHP SDK, in the SurrealDB\SDK\Types namespace.

Version 2 represents SurrealQL types that PHP lacks with value classes in the `SurrealDB\SDK\Types` namespace. Every class extends the abstract `Value` and provides `escape()` (the inline SurrealQL form), `jsonSerialize()`, `equals()`, and `__toString()`.

## `RecordId` {#recordid}

A record identifier: a table name and an ID.

```php title="Constructor"
new RecordId(string $table, string|int|array|object $id)
RecordId::from(string $table, string|int|array|object $id)
```

```php
use SurrealDB\SDK\Types\RecordId;

$id = new RecordId('person', 'tobie');
echo $id->table; // "person"
echo $id->id;    // "tobie"

$composite = new RecordId('temperature', ['city' => 'London', 'time' => 1700000000]);
```

The ID can be a string, integer, array, or object. The `table` and `id` properties expose the parts.

## `StringRecordId` {#stringrecordid}

A record ID kept as a raw string. Use it to pass an ID through verbatim and let the server parse it.

```php title="Constructor"
new StringRecordId(StringRecordId|RecordId|string $id)
```

```php
use SurrealDB\SDK\Types\StringRecordId;

$db->select(new StringRecordId('person:tobie'))->execute();
```

## `Table` {#table}

A table reference.

```php title="Constructor"
new Table(string $name)
```

```php
use SurrealDB\SDK\Types\Table;

$table = new Table('person');
echo $table->name; // "person"
```

## `DateTime` {#datetime}

A datetime with nanosecond precision, stored as a `(seconds, nanoseconds)` pair.

```php title="Constructor and factories"
new DateTime(int $seconds = 0, int $nanoseconds = 0)
DateTime::fromString(string $iso)
DateTime::fromDateTime(DateTimeInterface $dateTime)
DateTime::now()
DateTime::epoch()
```

```php
use SurrealDB\SDK\Types\DateTime;

$now = DateTime::now();
$parsed = DateTime::fromString('2024-01-15T12:00:00.123456789Z');

$native = $now->toDateTimeImmutable(); // microsecond precision
$iso = $now->toIso();
```

## `Duration` {#duration}

A duration with nanosecond precision, rendered with SurrealQL's compact syntax such as `1h30m`.

```php title="Constructor and factories"
new Duration(int $seconds = 0, int $nanoseconds = 0)
Duration::fromString(string $input)
Duration::seconds(int $value)   // also: nanoseconds, microseconds, milliseconds, minutes, hours, days, weeks, years
```

```php
use SurrealDB\SDK\Types\Duration;

$ttl = Duration::fromString('1h30m');
$combined = Duration::hours(1)->add(Duration::minutes(30));
echo $combined->totalNanoseconds();
```

## `Decimal` {#decimal}

An arbitrary-precision decimal backed by `Brick\Math\BigDecimal`. Construct from a string to keep precision.

```php title="Constructor"
new Decimal(Decimal|BigDecimal|string|int|float $value)
```

```php
use SurrealDB\SDK\Types\Decimal;

$price = new Decimal('19.99');
$total = $price->multipliedBy(3); // 59.97
echo $total; // "59.97"
```

Arithmetic methods include `plus()`, `minus()`, `multipliedBy()`, `dividedBy()`, `abs()`, and `negated()`.

## `Uuid` {#uuid}

A universally unique identifier, backed by `symfony/uid`.

```php title="Factories"
Uuid::v4()                      // random
Uuid::v7()                      // time-ordered
Uuid::fromString(string $uuid)
Uuid::fromBytes(string $bytes)
```

```php
use SurrealDB\SDK\Types\Uuid;

$id = Uuid::v7();
echo $id;            // canonical RFC 4122 string
$binary = $id->toBytes();
```

## `Range` and `RecordIdRange` {#range}

A `Range` is a bounded or open interval. A `RecordIdRange` is a range of record IDs in a table. Bounds are `BoundIncluded`, `BoundExcluded`, or `null` for an open end.

```php title="Constructors"
new Range(BoundIncluded|BoundExcluded|null $begin, BoundIncluded|BoundExcluded|null $end)
new RecordIdRange(string $table, $begin, $end)
new BoundIncluded(mixed $value)
new BoundExcluded(mixed $value)
```

```php
use SurrealDB\SDK\Types\RecordIdRange;
use SurrealDB\SDK\Types\BoundIncluded;
use SurrealDB\SDK\Types\BoundExcluded;

// person:1..=100
$range = new RecordIdRange('person', new BoundIncluded(1), new BoundIncluded(100));
$slice = $db->select($range)->execute();
```

## `Set` {#set}

An array whose items are deduplicated.

```php title="Constructor"
new Set(iterable $items = [])
```

```php
use SurrealDB\SDK\Types\Set;

$tags = new Set(['a', 'b', 'a']); // ['a', 'b']
```

## `Bytes` {#bytes}

A binary value. Construct from a raw string or a base64url string.

```php title="Constructor and factory"
new Bytes(string $bytes)
Bytes::fromBase64(string $base64)
```

```php
use SurrealDB\SDK\Types\Bytes;

$bytes = new Bytes($raw);
echo $bytes->toBase64();
```

## `None` {#none}

The SurrealQL `NONE` value, distinct from `null`. It is a singleton.

```php
use SurrealDB\SDK\Types\None;

$none = None::instance();
```

## `File` {#file}

A reference to a file stored in a bucket.

```php title="Constructor"
new File(string $bucket, string $key)
```

```php
use SurrealDB\SDK\Types\File;

$file = new File('avatars', '/tobie.png');
echo $file->bucket; // "avatars"
echo $file->key;    // "/tobie.png"
```

## `Future` {#future}

An uncomputed SurrealQL [future](/docs/reference/query-language/language-primitives/data-types/futures.md), such as `<future> { ... }`. The body is the SurrealQL expression to evaluate.

```php title="Constructor"
new Future(string $body)
```

```php
use SurrealDB\SDK\Types\Future;

$future = new Future('{ created_at + 1w }');
echo $future->body;
```

> [!NOTE]
> Futures were removed in SurrealDB `3.0`. This type is retained for compatibility with older servers and parity with the JavaScript SDK, and is deprecated.

## Geometry {#geometry}

Classes for each [GeoJSON geometry type](/docs/reference/query-language/language-primitives/data-types/geometries.md). They all extend the abstract `Geometry`, which provides `toGeoJson()`, `is()`, and the static `Geometry::fromGeoJson()`.

| Class | Constructor |
|-------|-------------|
| `GeometryPoint` | `new GeometryPoint(float $longitude, float $latitude)` |
| `GeometryLine` | `new GeometryLine(GeometryPoint $first, GeometryPoint ...$rest)` |
| `GeometryPolygon` | `new GeometryPolygon(GeometryLine $exterior, GeometryLine ...$interior)` |
| `GeometryMultiPoint` | `new GeometryMultiPoint(GeometryPoint $first, GeometryPoint ...$rest)` |
| `GeometryMultiLine` | `new GeometryMultiLine(GeometryLine $first, GeometryLine ...$rest)` |
| `GeometryMultiPolygon` | `new GeometryMultiPolygon(GeometryPolygon $first, GeometryPolygon ...$rest)` |
| `GeometryCollection` | `new GeometryCollection(Geometry $first, Geometry ...$rest)` |

```php
use SurrealDB\SDK\Types\GeometryPoint;
use SurrealDB\SDK\Types\GeometryLine;

$point = new GeometryPoint(-0.118092, 51.509865);
$line = new GeometryLine(
    new GeometryPoint(0, 0),
    new GeometryPoint(1, 1),
);

$geojson = $line->toGeoJson();
```

## See also

- [Data types concept](/docs/reference/php/v2/concepts/data-types.md) for the type mapping and guidance
- [SurrealQL data model](/docs/reference/query-language/language-primitives/data-types.md) for the database type system

---

Source: https://surrealdb.com/docs/reference/php/v2/api/query-builders

# Query builders

Reference for the fluent query builders in version 2 of the PHP SDK, including select, create, update, delete, insert, and relate.

The fluent builders compile a statement and run it through the connection. Each builder method on `Surreal` returns a builder object you configure with chained calls, then run with `execute()` or inspect with `compile()`.

All builders extend `QueryBuilder` in the `SurrealDB\SDK\Query` namespace.

## Shared methods

Every builder provides these methods.

| Method | Returns | Description |
|--------|---------|-------------|
| `execute()` | `mixed` | Run the statement and return the first statement's result |
| `compile()` | [`BoundQuery`](/docs/reference/php/v2/api/utilities.md#boundquery) | Compile to SurrealQL and bindings without running |
| `json(bool $json = true)` | `static` | Request JSON-compatible results |

## Raw queries

### `run()` {#run}

Execute raw SurrealQL with optional bindings. Returns one result per statement.

```php title="Syntax"
$db->run(string $surql, array $bindings = []): array
```

```php
[$people] = $db->run('SELECT * FROM person WHERE age > $min', ['min' => 18]);
```

### `query()` {#query}

Execute a pre-built [`BoundQuery`](/docs/reference/php/v2/api/utilities.md#boundquery). Returns one result per statement.

```php title="Syntax"
$db->query(BoundQuery $query): array
```

## `select()` {#select}

Start a `SELECT`. Accepts a `RecordId`, `Table`, or string target.

```php title="Methods"
->fields(string ...$fields)   // SELECT specific fields
->value(string $field)        // SELECT VALUE for one field
->where(string|BoundQuery $cond)
->start(int $start)
->limit(int $limit)
->fetch(string ...$fields)    // resolve record links
->timeout(string $duration)   // e.g. "5s"
->version(string $datetime)   // historical read
```

```php
$people = $db->select(new Table('person'))
    ->fields('name', 'age')
    ->where('age >= 18')
    ->limit(10)
    ->execute();
```

## `create()` {#create}

Start a `CREATE`. Accepts a `RecordId`, `Table`, or string target.

```php title="Methods"
->content(array|object $data)       // CONTENT
->patch(array $patches)             // PATCH
->output(Output $output)            // RETURN clause
->timeout(string $duration)
->version(string $datetime)
```

```php
$person = $db->create(new RecordId('person', 'tobie'))
    ->content(['name' => 'Tobie'])
    ->execute();
```

## `update()` and `upsert()` {#update}

Start an `UPDATE` or `UPSERT`. `update()` modifies existing records; `upsert()` creates the record if it does not exist.

```php title="Methods"
->content(array|object $data)   // replace the record
->merge(array|object $data)     // merge fields
->replace(array|object $data)   // REPLACE
->patch(array $patches)         // JSON Patch
->where(string|BoundQuery $cond)
->output(Output $output)
->timeout(string $duration)
```

```php
$db->update(new RecordId('person', 'tobie'))
    ->merge(['age' => 33])
    ->execute();

$db->upsert(new RecordId('person', 'tobie'))
    ->content(['name' => 'Tobie', 'age' => 33])
    ->execute();
```

## `delete()` {#delete}

Start a `DELETE`. It defaults to `RETURN BEFORE`, so deleted records are returned.

```php title="Methods"
->output(Output $output)
->timeout(string $duration)
->version(string $datetime)
```

```php
$db->delete(new RecordId('person', 'tobie'))->execute();
```

## `insert()` {#insert}

Start an `INSERT`. Pass a target table and records, or records alone when each carries its own ID.

```php title="Methods"
->relation()   // INSERT RELATION
->ignore()     // INSERT IGNORE
->output(Output $output)
->timeout(string $duration)
->version(string $datetime)
```

```php
$db->insert(new Table('person'), [
    ['name' => 'Alice'],
    ['name' => 'Bob'],
])->execute();
```

## `relate()` {#relate}

Start a `RELATE`, creating one or more graph edges.

```php title="Methods"
->content(array|object $data)   // store data on the edge
->unique()
->output(Output $output)
->timeout(string $duration)
->version(string $datetime)
```

```php
$db->relate(
    new RecordId('person', 'tobie'),
    new Table('likes'),
    new RecordId('post', 'surrealdb'),
)->content(['since' => 2024])->execute();
```

## `call()` {#call}

Invoke a SurrealQL or SurrealML function by name. `run()` already handles raw SurrealQL, so function invocation has its own method.

```php title="Syntax"
$db->call(string $name, ?string $version = null, array $args = []): RunQuery
```

```php
$greeting = $db->call('fn::greet', null, ['Tobie'])->execute();
```

## `auth()` {#auth}

Compile to `SELECT * FROM ONLY $auth`, returning the authenticated record user.

```php
$me = $db->auth()->execute();
```

## Modifiers

### Output

`output()` accepts the `SurrealDB\SDK\Enum\Output` enum: `NONE`, `NULL_`, `DIFF`, `BEFORE`, `AFTER`.

```php
use SurrealDB\SDK\Enum\Output;

$db->update(new RecordId('person', 'tobie'))
    ->merge(['age' => 33])
    ->output(Output::AFTER)
    ->execute();
```

### Where

`where()` accepts a SurrealQL string or a [`BoundQuery`](/docs/reference/php/v2/api/utilities.md#boundquery) fragment. Use a `BoundQuery` to keep dynamic values parameterised.

### Timeout

`timeout()` accepts a SurrealQL duration string such as `5s` or `1m30s`.

## See also

- [Executing queries](/docs/reference/php/v2/concepts/executing-queries.md) for the guide
- [Core classes](/docs/reference/php/v2/api/core.md) for the `Surreal` entry point
- [Utilities](/docs/reference/php/v2/api/utilities.md) for `BoundQuery` and the `Output` enum

---

Source: https://surrealdb.com/docs/reference/php/v2/api/utilities

# Utilities

Reference for BoundQuery, driver options, enums, and helpers in version 2 of the PHP SDK.

This page covers the supporting types used across the SDK: bound queries, driver-wide options, and the enums that statements accept.

## `BoundQuery` {#boundquery}

A parameter-bound SurrealQL fragment: the query text plus the values bound to generated placeholders. The query builders compile to a `BoundQuery`, and you can build one by hand.

**Namespace:** `SurrealDB\SDK\Query\BoundQuery`

```php title="Constructor"
new BoundQuery(string $query = '', array $bindings = [])
```

| Method | Description |
|--------|-------------|
| `bind(mixed $value): string` | Bind a value and return its generated placeholder, such as `$bind_0` |
| `append(BoundQuery\|string $sql, array $bindings = []): self` | Append SurrealQL or another `BoundQuery`, merging bindings |

```php
use SurrealDB\SDK\Query\BoundQuery;

$query = new BoundQuery('SELECT * FROM person WHERE age > $min', ['min' => 18]);
$results = $db->query($query);
```

You can pass a `BoundQuery` to a builder's `where()` to keep dynamic values parameterised.

```php
$db->select(new Table('person'))
    ->where(new BoundQuery('age > $min', ['min' => 18]))
    ->execute();
```

## `DriverOptions` {#driveroptions}

Driver-wide configuration passed to the `Surreal` constructor. Every field has a default, so `new Surreal()` works without arguments.

**Namespace:** `SurrealDB\SDK\Connection\DriverOptions`

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `format` | `CodecEnum` | `JSON` | The wire format |
| `codec` | `?Codec` | derived from `format` | A custom serialiser and deserialiser |
| `events` | `?EventDispatcherInterface` | new dispatcher | PSR-14 dispatcher for [domain events](/docs/reference/php/v2/concepts/events.md) |
| `logger` | `?LoggerInterface` | null logger | PSR-3 logger; enables the [logging middleware](/docs/reference/php/v2/concepts/middleware.md#logging) |
| `tracer` | `?Tracer` | no-op tracer | Tracing backend; enables [telemetry](/docs/reference/php/v2/concepts/observability.md) |
| `meter` | `?Meter` | no-op meter | Metrics backend; enables [telemetry](/docs/reference/php/v2/concepts/observability.md) |
| `httpClient` | `?ClientInterface` | discovered | PSR-18 HTTP client |
| `requestFactory` | `?RequestFactoryInterface` | discovered | PSR-17 request factory |
| `streamFactory` | `?StreamFactoryInterface` | discovered | PSR-17 stream factory |
| `scheduler` | `?Scheduler` | sync scheduler | Synchronous or async scheduler |
| `engines` | `?array` | `null` | Engine factory overrides keyed by URL scheme |
| `webSocketClientFactory` | `?Closure` | `null` | Override the WebSocket client |
| `webSocketTransportFactory` | `?Closure` | `null` | Swap the entire WebSocket transport |
| `httpTransportFactory` | `?Closure` | `null` | Swap the entire HTTP transport |
| `middleware` | `list<MiddlewareInterface>` | `[]` | Extra [middleware](/docs/reference/php/v2/concepts/middleware.md) appended to the pipeline |
| `pingInterval` | `int` | `30` | WebSocket ping interval in seconds |

The `engines` and `*Factory` options are the transport-level extension seams. The [runtime presets](/docs/reference/php/v2/concepts/runtimes.md) configure them for you, so you only set them when supplying a transport of your own.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;
use SurrealDB\SDK\Enum\CodecEnum;

$db = new Surreal(new DriverOptions(format: CodecEnum::CBOR));
```

## `CodecEnum` {#codecenum}

The wire format used to serialise values.

**Namespace:** `SurrealDB\SDK\Enum\CodecEnum`

- `CodecEnum::JSON` is the zero-dependency default.
- `CodecEnum::CBOR` is a compact binary format that preserves more type information.

### Custom codecs

A `Codec` pairs a serialiser with a deserialiser. The built-in pairs are `Codec::json()` and `Codec::cbor()`, selected from the `format` option. To customise serialisation, implement `SurrealDB\SDK\Codec\SerializerInterface` and `SurrealDB\SDK\Codec\DeserializerInterface`, wrap them in a `Codec`, and pass it as the `codec` option.

```php
use SurrealDB\SDK\Codec\Codec;
use SurrealDB\SDK\Connection\DriverOptions;
use SurrealDB\SDK\Enum\CodecEnum;

$db = new Surreal(new DriverOptions(
    format: CodecEnum::CBOR,
    codec: new Codec(new MySerializer(), new MyDeserializer()),
));
```

The codec must agree with the `format`: a `CBOR` format requires a CBOR-compatible codec, and a `JSON` format requires a JSON-compatible one. Mixing them throws a `ConfigurationException`.

## `Output` {#output}

The `RETURN` clause variants accepted by mutating statements.

**Namespace:** `SurrealDB\SDK\Enum\Output`

**Values:** `NONE`, `NULL_`, `DIFF`, `BEFORE`, `AFTER`

```php
use SurrealDB\SDK\Enum\Output;

$db->create(new RecordId('person', 'tobie'))
    ->content(['name' => 'Tobie'])
    ->output(Output::AFTER)
    ->execute();
```

## `Endpoint` {#endpoint}

A parsed, normalised endpoint. `connect()` accepts a string and parses it for you, but you can build one explicitly with `Endpoint::parse()`. Remote schemes (`ws`, `wss`, `http`, `https`) get a `/rpc` suffix when one is missing.

**Namespace:** `SurrealDB\SDK\Connection\Endpoint`

```php
use SurrealDB\SDK\Connection\Endpoint;

$endpoint = Endpoint::parse('ws://127.0.0.1:8000'); // path becomes /rpc
$db->connect($endpoint);
```

## `Features` {#features}

A catalogue of features with the server version each requires. Pass one to [`isFeatureSupported()`](/docs/reference/php/v2/api/core.md#isfeaturesupported).

**Namespace:** `SurrealDB\SDK\Protocol\Features`

```php
Features::liveQueries();
Features::transactions();
Features::sessions();
Features::refreshTokens();
Features::surrealMl();
```

## See also

- [Core classes](/docs/reference/php/v2/api/core.md) for the `Surreal` entry point
- [Query builders](/docs/reference/php/v2/api/query-builders.md) for the fluent statement API
- [Executing queries](/docs/reference/php/v2/concepts/executing-queries.md) for the guide

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/authentication

# Authentication

Sign in and sign up with typed credentials in version 2 of the PHP SDK, then manage tokens and authentication state.

SurrealDB supports several levels of authentication, from [system users](/docs/learn/security/authentication/users.md#system-users) to fine-grained [record access](/docs/learn/security/authentication/users.md#record-users). Version 2 of the PHP SDK represents each level as a typed credential class, so the required fields are explicit.

## API references

<table>
    <thead>
        <tr>
            <th scope="col">Method</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/core.md#signin"> <code> $db->signin($auth) </code></a></td>
            <td scope="row" data-label="Description">Signs in as a root, namespace, database, or record user</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/core.md#signup"> <code> $db->signup($auth) </code></a></td>
            <td scope="row" data-label="Description">Signs up a new record user through an access method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/core.md#authenticate"> <code> $db->authenticate($token) </code></a></td>
            <td scope="row" data-label="Description">Authenticates the session with an existing token</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/core.md#invalidate"> <code> $db->invalidate() </code></a></td>
            <td scope="row" data-label="Description">Invalidates the current session, signing the user out</td>
        </tr>
    </tbody>
</table>

## Credential types

Each authentication level has a credential class in the `SurrealDB\SDK\Auth` namespace.

| Class | Level | Constructor |
|-------|-------|-------------|
| `RootAuth` | Root (system) user | `new RootAuth($username, $password)` |
| `NamespaceAuth` | Namespace user | `new NamespaceAuth($namespace, $username, $password)` |
| `DatabaseAuth` | Database user | `new DatabaseAuth($namespace, $database, $username, $password)` |
| `RecordAccessAuth` | Record access | `new RecordAccessAuth($namespace, $database, $access, $variables)` |
| `BearerAuth` | Bearer access key | `new BearerAuth($namespace, $database, $access, $key)` |

## Signing in users

The `signin()` method authenticates an existing user. Pass the credential class for the level you need. It returns a [`Tokens`](/docs/reference/php/v2/api/core.md#tokens) object holding the access token and an optional refresh token.

**Root user**

```php
use SurrealDB\SDK\Auth\RootAuth;

$tokens = $db->signin(new RootAuth('root', 'surrealdb'));
```

**Namespace user**

```php
use SurrealDB\SDK\Auth\NamespaceAuth;

$tokens = $db->signin(new NamespaceAuth('surrealdb', 'tobie', 'surrealdb'));
```

**Database user**

```php
use SurrealDB\SDK\Auth\DatabaseAuth;

$tokens = $db->signin(new DatabaseAuth('surrealdb', 'docs', 'tobie', 'surrealdb'));
```

**Record access**

```php
use SurrealDB\SDK\Auth\RecordAccessAuth;

$tokens = $db->signin(new RecordAccessAuth(
    namespace: 'surrealdb',
    database: 'docs',
    access: 'account',
    variables: [
        'email' => 'info@surrealdb.com',
        'pass' => '123456',
    ],
));

echo $tokens->access;
```

The session is authenticated after a successful sign in.

> [!NOTE]
> The credential classes map to the keys SurrealDB expects (`user`, `pass`, `ns`, `db`, `ac`). You can also pass a raw array if you prefer, but the typed classes document the required fields and avoid mistakes.

## Signing up users

The `signup()` method creates a new record user through a defined [record access method](/docs/reference/query-language/statements/define/access/record.md). Use `RecordAccessAuth` with the variables your access definition expects.

```php
use SurrealDB\SDK\Auth\RecordAccessAuth;

$tokens = $db->signup(new RecordAccessAuth(
    namespace: 'surrealdb',
    database: 'docs',
    access: 'account',
    variables: [
        'email' => 'info@surrealdb.com',
        'pass' => '123456',
    ],
));
```

## Authenticating with a token

If you already have an access token, authenticate with it directly instead of signing in again. This restores a session without re-entering credentials.

```php
$db->authenticate($accessToken);
```

## Bearer access

A bearer access key authenticates against a defined [bearer access method](/docs/reference/query-language/statements/define/access/bearer.md). Use `BearerAuth` with the access name and the key.

```php
use SurrealDB\SDK\Auth\BearerAuth;

$tokens = $db->signin(new BearerAuth(
    namespace: 'surrealdb',
    database: 'docs',
    access: 'api',
    key: $bearerKey,
));
```

## Refreshing and revoking tokens

When a record access method issues refresh tokens, `signin()` and `signup()` return a `Tokens` object with both an `access` and a `refresh` token. Use the refresh token to obtain a new pair without re-entering credentials. Both operations are on the [`ConnectionController`](/docs/reference/php/v2/api/core.md#connectioncontroller).

```php
$tokens = $db->connection()->refresh($tokens);   // new access + refresh pair

$db->connection()->revoke($tokens);              // invalidate the refresh token
```

> [!IMPORTANT]
> Refresh tokens require SurrealDB `3.0.0` or later. Check with `isFeatureSupported(Features::refreshTokens())` before relying on them.

By default the SDK renews an expiring token in the background. To sign the session out instead of renewing it, set `invalidateOnExpiry` to `true` on [`connect()`](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md#connection-options).

```php
use SurrealDB\SDK\Connection\ConnectOptions;

$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    invalidateOnExpiry: true,
));
```

## Providing credentials on connect

Rather than calling `signin()` separately, pass credentials to `connect()` through the `authentication` option. This is preferred for system users because it lets the SDK re-authenticate automatically after a reconnect.

```php
use SurrealDB\SDK\Connection\ConnectOptions;
use SurrealDB\SDK\Auth\RootAuth;

$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    namespace: 'surrealdb',
    database: 'docs',
    authentication: new RootAuth('root', 'surrealdb'),
));
```

The `authentication` option also accepts a closure, which is useful when credentials are fetched at runtime.

```php
$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    authentication: fn () => new RootAuth(getUsername(), getPassword()),
));
```

## Listening to authentication changes

The SDK emits an `auth` event whenever the authentication state changes, including on sign in, sign up, token renewal, and invalidation. The payload is the current `Tokens` object, or `null` when signed out.

```php
$db->subscribe('auth', function (?Tokens $tokens): void {
    echo $tokens === null ? 'Signed out' : 'Authenticated';
});
```

## Selecting the current user

The `auth()` builder compiles to `SELECT * FROM ONLY $auth`, which returns the record of the authenticated record user.

```php
$me = $db->auth()->execute();
```

## Signing out

The `invalidate()` method clears the session's authentication. Queries after this run unauthenticated.

```php
$db->invalidate();
```

## Learn more

- [Surreal API reference](/docs/reference/php/v2/api/core.md) for the authentication method signatures
- [Authentication in SurrealDB](/docs/learn/security/authentication/users.md) for how authentication works at the database level
- [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md) for defining access methods

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/connecting-to-surrealdb

# Connecting to SurrealDB

Open a connection to a SurrealDB instance with version 2 of the PHP SDK, select a namespace and database, and configure reconnection.

Before you can run queries, you open a connection to a SurrealDB instance. You create a `Surreal` instance and call `connect()` with a connection string and a set of options. The options carry the namespace, database, authentication, and reconnection settings.

## API references

<table>
    <thead>
        <tr>
            <th scope="col">Method</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/core.md#connect"> <code> $db->connect($url, $options) </code></a></td>
            <td scope="row" data-label="Description">Connects to a local or remote database endpoint</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/core.md#close"> <code> $db->close() </code></a></td>
            <td scope="row" data-label="Description">Closes the connection to the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/core.md#use"> <code> $db->use($namespace, $database) </code></a></td>
            <td scope="row" data-label="Description">Switches to a specific namespace and database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/core.md#status"> <code> $db->status() </code></a></td>
            <td scope="row" data-label="Description">Returns the current connection status</td>
        </tr>
    </tbody>
</table>

## Opening a connection

Create a `Surreal` instance, then call `connect()` with a connection string and a [`ConnectOptions`](/docs/reference/php/v2/api/core.md#connectoptions) object.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\ConnectOptions;
use SurrealDB\SDK\Auth\RootAuth;

$db = new Surreal();

$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    namespace: 'surrealdb',
    database: 'docs',
    authentication: new RootAuth('root', 'root'),
));
```

### Connection string

The connection string is a URI pointing to a SurrealDB instance. Version 2 supports two transports:

- **WebSocket** (`ws://`, `wss://`) for long-lived connections that support live queries and server-side transactions.
- **HTTP** (`http://`, `https://`) for stateless, short-lived requests.

```php
// WebSocket, local
$db->connect('ws://127.0.0.1:8000/rpc');

// HTTP, local
$db->connect('http://127.0.0.1:8000/rpc');

// WebSocket, remote
$db->connect('wss://cloud.surrealdb.com');
```

The SDK appends `/rpc` to the path if you leave it out, so `ws://127.0.0.1:8000` and `ws://127.0.0.1:8000/rpc` are equivalent.

> [!NOTE]
> Version 2 does not include the embedded engines available in some other SDKs. Connect to a running SurrealDB instance over WebSocket or HTTP.

### Connection options

`ConnectOptions` configures the connection. Every argument is optional.

| Option | Type | Description |
|--------|------|-------------|
| `namespace` | `?string` | Namespace to select on connect |
| `database` | `?string` | Database to select on connect |
| `authentication` | `Credentials \| Token \| string \| Closure \| null` | Credentials or a token used to authenticate, and to re-authenticate after a reconnect |
| `versionCheck` | `bool` | Check the server version on connect (default `true`) |
| `invalidateOnExpiry` | `bool` | Invalidate the session when its token expires instead of renewing it (default `false`) |
| `reconnect` | `bool \| ReconnectStrategyInterface` | Reconnection behaviour for WebSocket connections (default `true`) |

### Authentication details

Passing credentials to `connect()` is the preferred way to authenticate, because it lets the SDK re-authenticate automatically when a WebSocket connection drops and reconnects. You can also pass a token, or a closure that returns credentials. See [Authentication](/docs/reference/php/v2/concepts/authentication.md) for the credential types.

### Reconnection behaviour

For WebSocket connections, the SDK reconnects automatically if the connection is lost. Set `reconnect` to `false` to disable this, leave it as `true` for the defaults, or pass a `ReconnectStrategyInterface` such as `ExponentialBackoffReconnect` to control the backoff.

```php
use SurrealDB\SDK\Reconnect\ExponentialBackoffReconnect;

$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    reconnect: new ExponentialBackoffReconnect(),
));
```

## Selecting a namespace and database

You can select the namespace and database on connect, or switch later with `use()`. Pass a namespace and an optional database.

```php
$db->use('surrealdb', 'docs');
```

The SDK emits a `using` event whenever the namespace or database changes, including on the initial connection.

## Connection status

The `status()` method returns a [`ConnectionStatus`](/docs/reference/php/v2/api/core.md#connectionstatus) enum with one of four values:

- `Disconnected` when there is no connection
- `Connecting` when a connection is being opened
- `Connected` when the SDK is ready to run queries
- `Reconnecting` when the connection dropped and the SDK is reconnecting

```php
use SurrealDB\SDK\Connection\ConnectionStatus;

if ($db->status() === ConnectionStatus::Connected) {
    // ready to query
}

// Shorthand for the check above
if ($db->isConnected()) {
    // ready to query
}
```

You can also subscribe to lifecycle events to react to status changes.

```php
$db->subscribe('connected', function (string $version): void {
    echo "Connected to SurrealDB {$version}";
});
```

The available events are `connecting`, `connected`, `reconnecting`, `disconnected`, `error`, `auth`, and `using`. The `subscribe()` method returns a closure that removes the listener when called.

## Closing a connection

Call `close()` when you are done. This releases the connection and its resources.

```php
$db->close();
```

## Checking the server

The `health()` method throws if the instance is unreachable, and `version()` returns the server version string.

```php
$db->health();

echo $db->version(); // "surrealdb-2.1.0"
```

## Testing for features

Some features depend on the transport or the server version. Use `isFeatureSupported()` to check before relying on one.

```php
use SurrealDB\SDK\Protocol\Features;

if ($db->isFeatureSupported(Features::liveQueries())) {
    // safe to run a live query
}
```

## Learn more

- [Surreal API reference](/docs/reference/php/v2/api/core.md) for the full connection interface
- [Authentication](/docs/reference/php/v2/concepts/authentication.md) for signing in and managing credentials
- [Error handling](/docs/reference/php/v2/concepts/error-handling.md) for connection and version errors

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/data-types

# Data types

How version 2 of the PHP SDK maps SurrealQL data types to native PHP types and custom value classes.

SurrealDB has types that PHP does not, such as record IDs, nanosecond datetimes, durations, and arbitrary-precision decimals. Version 2 of the SDK represents these with value classes in the `SurrealDB\SDK\Types` namespace. Native PHP types pass through unchanged.

## Type mapping

| SurrealQL type | PHP type |
|----------------|----------|
| `bool` | `bool` |
| `int`, `float` | `int`, `float` |
| `string` | `string` |
| `null` | `null` |
| `none` | [`None`](/docs/reference/php/v2/api/data-types.md#none) |
| `array` | `array` (list) |
| `object` | `array` (associative) |
| `set` | [`Set`](/docs/reference/php/v2/api/data-types.md#set) |
| `bytes` | [`Bytes`](/docs/reference/php/v2/api/data-types.md#bytes) |
| `datetime` | [`DateTime`](/docs/reference/php/v2/api/data-types.md#datetime) |
| `duration` | [`Duration`](/docs/reference/php/v2/api/data-types.md#duration) |
| `decimal` | [`Decimal`](/docs/reference/php/v2/api/data-types.md#decimal) |
| `uuid` | [`Uuid`](/docs/reference/php/v2/api/data-types.md#uuid) |
| `record` | [`RecordId`](/docs/reference/php/v2/api/data-types.md#recordid) |
| `range` | [`Range`](/docs/reference/php/v2/api/data-types.md#range) |
| `geometry` | [`Geometry`](/docs/reference/php/v2/api/data-types.md#geometry) types |
| `file` | [`File`](/docs/reference/php/v2/api/data-types.md#file) |

## Record IDs and tables

A [`RecordId`](/docs/reference/php/v2/api/data-types.md#recordid) is a table name plus an ID. A [`Table`](/docs/reference/php/v2/api/data-types.md#table) is a table reference on its own. The query builders accept either, so the SDK can tell a record from a table.

```php
use SurrealDB\SDK\Types\RecordId;
use SurrealDB\SDK\Types\Table;

$tobie = new RecordId('person', 'tobie');
$people = new Table('person');

$record = $db->select($tobie)->execute();
$all = $db->select($people)->execute();
```

The ID can be a string, integer, array, or object for composite keys.

```php
$metric = new RecordId('metric', ['service' => 'api', 'host' => 'server-01']);
```

To send a record ID that is already a string, wrap it in `StringRecordId` so the server parses it.

```php
use SurrealDB\SDK\Types\StringRecordId;

$db->select(new StringRecordId('person:tobie'))->execute();
```

## Datetimes and durations

A [`DateTime`](/docs/reference/php/v2/api/data-types.md#datetime) keeps nanosecond precision, which PHP's native `DateTime` does not. A [`Duration`](/docs/reference/php/v2/api/data-types.md#duration) follows SurrealQL duration syntax.

```php
use SurrealDB\SDK\Types\DateTime;
use SurrealDB\SDK\Types\Duration;

$now = DateTime::now();
$parsed = DateTime::fromString('2024-01-15T12:00:00.123456789Z');

$ttl = Duration::fromString('1h30m');
```

## Decimals

A [`Decimal`](/docs/reference/php/v2/api/data-types.md#decimal) holds a number without floating-point rounding. Construct it from a string when precision matters.

```php
use SurrealDB\SDK\Types\Decimal;

$price = new Decimal('19.99');
```

## UUIDs

A [`Uuid`](/docs/reference/php/v2/api/data-types.md#uuid) represents a universally unique identifier, with helpers for v4 (random) and v7 (time-ordered).

```php
use SurrealDB\SDK\Types\Uuid;

$random = Uuid::v4();
$timeOrdered = Uuid::v7();
```

## Geometries

The SDK provides classes for every [GeoJSON geometry type](/docs/reference/query-language/language-primitives/data-types/geometries.md): `GeometryPoint`, `GeometryLine`, `GeometryPolygon`, `GeometryMultiPoint`, `GeometryMultiLine`, `GeometryMultiPolygon`, and `GeometryCollection`.

```php
use SurrealDB\SDK\Types\GeometryPoint;
use SurrealDB\SDK\Types\GeometryLine;

$point = new GeometryPoint(-0.118092, 51.509865);
$line = new GeometryLine(
    new GeometryPoint(0, 0),
    new GeometryPoint(1, 1),
);
```

## Learn more

- [Data types API reference](/docs/reference/php/v2/api/data-types.md) for every value class and its methods
- [SurrealQL data model](/docs/reference/query-language/language-primitives/data-types.md) for the database type system

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/error-handling

# Error handling

Handle failures in version 2 of the PHP SDK with the typed exception hierarchy rooted at SurrealException.

Version 2 of the PHP SDK throws typed exceptions for different failures. They all extend `SurrealException`, which in turn extends PHP's `RuntimeException`. This lets you catch SDK errors broadly or target a specific failure with an `instanceof` check or a `catch` type.

The error kinds, wire codes and structured shape behind these are documented once in [Errors](/docs/reference/rest-api/errors.md), which applies to every SDK and protocol.

## Common exceptions

All exceptions live in the `SurrealDB\SDK\Exceptions` namespace.

| Exception | Thrown when |
|-----------|-------------|
| `SurrealException` | Base class for every SDK error |
| `ConnectionUnavailableException` | An operation runs without an active connection |
| `HttpConnectionException` | An HTTP request fails with a non-success status |
| `AuthenticationException` | Sign in, sign up, or token renewal fails |
| `MissingNamespaceDatabaseException` | An operation needs a namespace or database that is not selected |
| `ServerException` | The server reports an error (base for query errors) |
| `QueryException` | A statement fails to execute |
| `UnsupportedVersionException` | The server version is outside the supported range |
| `UnsupportedFeatureException` | The engine does not support a requested feature |
| `UnavailableFeatureException` | The server version does not support a requested feature |

## Catching exceptions

Catch a specific type for fine-grained handling, or `SurrealException` to handle any SDK error.

```php
use SurrealDB\SDK\Auth\RootAuth;
use SurrealDB\SDK\Exceptions\AuthenticationException;
use SurrealDB\SDK\Exceptions\ConnectionUnavailableException;
use SurrealDB\SDK\Exceptions\SurrealException;

try {
    $db->signin(new RootAuth('root', 'wrong'));
} catch (AuthenticationException $error) {
    echo 'Invalid credentials';
} catch (ConnectionUnavailableException $error) {
    echo 'Not connected to a database';
} catch (SurrealException $error) {
    echo 'SDK error: ' . $error->getMessage();
}
```

## Connection and HTTP errors

A `ConnectionUnavailableException` is thrown when you run an operation without a connection. Over HTTP, a failed request throws `HttpConnectionException`, which exposes the status code, status text, and response body.

```php
use SurrealDB\SDK\Exceptions\HttpConnectionException;

try {
    $db->run('SELECT * FROM person');
} catch (HttpConnectionException $error) {
    echo "HTTP {$error->status}: {$error->statusText}";
}
```

## Query errors

When a statement fails, the SDK throws a `ServerException` (or a subclass such as `QueryException`). The exception carries the server's `kind`, message, and any `details`.

```php
use SurrealDB\SDK\Exceptions\ServerException;

try {
    $db->run('SELECT * FROM');
} catch (ServerException $error) {
    echo "[{$error->kind}] {$error->getMessage()}";
}
```

## Version mismatches

The SDK checks the server version on connect. If it is outside the supported range, it throws `UnsupportedVersionException` with the reported version and the supported bounds. Disable the check with the `versionCheck` option on [`connect()`](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md#connection-options).

```php
use SurrealDB\SDK\Exceptions\UnsupportedVersionException;

try {
    $db->connect('ws://127.0.0.1:8000/rpc');
} catch (UnsupportedVersionException $error) {
    echo "Version {$error->version} is not supported " .
        "(requires >= {$error->minimum} < {$error->maximum})";
}
```

## Listening to connection errors

Errors that happen outside a direct method call, such as a failed reconnection, are delivered through the `error` event rather than thrown. Subscribe with `subscribe()`.

```php
$db->subscribe('error', function (\Throwable $error): void {
    error_log('Connection error: ' . $error->getMessage());
});
```

## Learn more

- [Surreal API reference](/docs/reference/php/v2/api/core.md) for the methods that raise these errors
- [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) for version and reconnection settings
- [Authentication](/docs/reference/php/v2/concepts/authentication.md) for handling sign-in failures

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/events

# Events

Observe connection lifecycle and RPC traffic in version 2 of the PHP SDK with the high-level subscribe() API and the lower-level PSR-14 event dispatcher.

Version 2 of the PHP SDK exposes two ways to observe what it is doing. The high-level `subscribe()` method covers connection lifecycle changes with a simple callback. The lower-level PSR-14 event dispatcher emits a richer stream that includes per-request events, and integrates with framework dispatchers.

## Lifecycle events with `subscribe()`

Use [`subscribe()`](/docs/reference/php/v2/api/core.md#subscribe) for quick hooks into the connection lifecycle. Pass an event name and a listener; the method returns a closure that removes the listener when called.

```php
$unsubscribe = $db->subscribe('connected', function (string $version): void {
    echo "Connected to SurrealDB {$version}";
});

// later
$unsubscribe();
```

The available event names are `connecting`, `connected`, `reconnecting`, `disconnected`, `error`, `auth`, and `using`. This is the simplest option when you only need to react to the connection coming up, dropping, or re-authenticating. See [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md#connection-status) and [Authentication](/docs/reference/php/v2/concepts/authentication.md#listening-to-authentication-changes) for lifecycle examples.

## PSR-14 events

For full observation, including every RPC request and response, the SDK dispatches typed event objects through a [PSR-14](https://www.php-fig.org/psr/psr-14/) event dispatcher. Unlike `subscribe()`, this stream reaches RPC-level events and lets you reuse a framework's dispatcher.

By default the SDK creates its own dispatcher. To receive events, either register listeners on a dispatcher you control and pass it through `DriverOptions`, or pass your framework's PSR-14 dispatcher.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;
use SurrealDB\SDK\Events\EventDispatcher;
use SurrealDB\SDK\Events\ListenerProvider;
use SurrealDB\SDK\Events\RpcResponseReceived;

$provider = new ListenerProvider();
$provider->on(RpcResponseReceived::class, function (RpcResponseReceived $event): void {
    error_log($event->request->method);
});

$db = new Surreal(new DriverOptions(
    events: new EventDispatcher($provider),
));
```

The bundled `ListenerProvider` matches a listener against the event class and any of its parents or interfaces, so you can listen to a single event type or a shared base.

### Using a framework dispatcher

Because the SDK depends only on the PSR-14 `EventDispatcherInterface`, you can pass the dispatcher from Laravel, Symfony, or any PSR-14 bridge. The SDK then dispatches its events into your application's existing listener setup.

```php
$db = new Surreal(new DriverOptions(
    events: $container->get(\Psr\EventDispatcher\EventDispatcherInterface::class),
));
```

### Event catalogue

All events live in the `SurrealDB\SDK\Events` namespace and are readonly value objects.

| Event | Dispatched when | Payload |
|-------|-----------------|---------|
| `Connecting` | A connection attempt starts | none |
| `Connected` | The connection is established and ready | `version` |
| `Disconnected` | The connection closes | none |
| `Reconnecting` | A dropped connection is being re-established | none |
| `ConnectionError` | A connection-level error occurs | `error` |
| `AuthChanged` | A session's authentication changes or clears | `tokens`, `session` |
| `NamespaceDatabaseSelected` | The namespace or database changes | the selection, `session` |
| `RpcRequestSent` | Just before a request is handed to the transport | `request` |
| `RpcResponseReceived` | After a response is received (success or error) | `request`, `response` |
| `LiveMessageReceived` | A live query notification arrives | `message` |

```php
use SurrealDB\SDK\Events\RpcRequestSent;
use SurrealDB\SDK\Events\AuthChanged;

$provider->on(RpcRequestSent::class, function (RpcRequestSent $event): void {
    // $event->request->method, $event->request->params
});

$provider->on(AuthChanged::class, function (AuthChanged $event): void {
    // $event->tokens is null when the session is signed out
});
```

> [!NOTE]
> `RpcRequestSent` and `RpcResponseReceived` fire for every call, so keep their listeners cheap. For tracing and metrics, prefer the dedicated [telemetry](/docs/reference/php/v2/concepts/observability.md) seam, which is built on this same pipeline.

## Choosing between them

- Use `subscribe()` for connection lifecycle hooks with the least setup.
- Use the PSR-14 dispatcher when you need per-request events, want to fan events into a framework's listeners, or are building observability tooling.

## Learn more

- [Surreal API reference](/docs/reference/php/v2/api/core.md#subscribe) for the `subscribe()` signature
- [Observability](/docs/reference/php/v2/concepts/observability.md) for tracing and metrics
- [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) for the lifecycle these events track

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/executing-queries

# Executing queries

Run raw SurrealQL or use the fluent query builders for select, create, update, and delete in version 2 of the PHP SDK.

Version 2 of the PHP SDK gives you two ways to query SurrealDB: raw SurrealQL through `run()`, and fluent query builders such as `select()`, `create()`, `update()`, and `delete()`. The builders compile to the same parameter-bound queries you could write by hand, so you can mix the two freely.

## API references

<table>
    <thead>
        <tr>
            <th scope="col">Method</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/query-builders.md#run"> <code> $db->run($surql, $bindings) </code></a></td>
            <td scope="row" data-label="Description">Executes raw SurrealQL statements</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/query-builders.md#select"> <code> $db->select($target) </code></a></td>
            <td scope="row" data-label="Description">Selects records from the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/query-builders.md#create"> <code> $db->create($target) </code></a></td>
            <td scope="row" data-label="Description">Creates a new record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/query-builders.md#insert"> <code> $db->insert($target, $data) </code></a></td>
            <td scope="row" data-label="Description">Inserts one or many records</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/query-builders.md#update"> <code> $db->update($target) </code></a></td>
            <td scope="row" data-label="Description">Updates existing records</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/query-builders.md#delete"> <code> $db->delete($target) </code></a></td>
            <td scope="row" data-label="Description">Deletes records from the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/php/v2/api/query-builders.md#relate"> <code> $db->relate($from, $edge, $to) </code></a></td>
            <td scope="row" data-label="Description">Creates graph relationships between records</td>
        </tr>
    </tbody>
</table>

## Running raw SurrealQL

The `run()` method executes raw [SurrealQL statements](/docs/reference/query-language/statements/overview.md). Pass bindings as the second argument to inject values safely. It returns one result per statement.

```php
[$adults] = $db->run(
    'SELECT * FROM person WHERE age > $min_age',
    ['min_age' => 18],
);
```

For a multi-statement query, each result keeps its position in the returned list.

```php
[$people, $posts] = $db->run('
    SELECT * FROM person;
    SELECT * FROM post;
');
```

## Builders, execute, and compile

Every builder method returns a builder object. Call `execute()` to run it and get the result of the single statement, or `compile()` to get the [`BoundQuery`](/docs/reference/php/v2/api/utilities.md#boundquery) without running it.

```php
$query = $db->select(new Table('person'))->where('age >= 18');

$result = $query->execute();   // runs the statement
$bound = $query->compile();    // BoundQuery: SurrealQL text + bindings
```

## Selecting records

The `select()` method reads records. Pass a [`Table`](/docs/reference/php/v2/api/data-types.md#table) to read all records, or a [`RecordId`](/docs/reference/php/v2/api/data-types.md#recordid) to read one. Chain `fields()`, `where()`, `start()`, `limit()`, and `fetch()` to refine the query.

```php
use SurrealDB\SDK\Types\Table;
use SurrealDB\SDK\Types\RecordId;

$everyone = $db->select(new Table('person'))->execute();

$tobie = $db->select(new RecordId('person', 'tobie'))->execute();

$page = $db->select(new Table('person'))
    ->fields('name', 'age')
    ->where('age >= 18')
    ->start(0)
    ->limit(10)
    ->fetch('posts')
    ->execute();
```

The `where()` method accepts a SurrealQL string for static conditions. For dynamic values, pass a `BoundQuery` so the values stay parameterised.

```php
use SurrealDB\SDK\Query\BoundQuery;

$db->select(new Table('person'))
    ->where(new BoundQuery('age >= $min', ['min' => 18]))
    ->execute();
```

## Creating records

The `create()` method starts a `CREATE`. Chain `content()` to set the record data. A `Table` generates a random ID; a `RecordId` creates the record with that ID.

```php
$db->create(new RecordId('person', 'tobie'))
    ->content(['name' => 'Tobie', 'age' => 32])
    ->execute();

$db->create(new Table('person'))
    ->content(['name' => 'Jaime'])
    ->execute();
```

## Inserting records

The `insert()` method inserts one or many records in a single statement. Pass a target table and the records, or pass the records alone when each contains its own ID.

```php
$db->insert(new Table('person'), [
    ['name' => 'Alice'],
    ['name' => 'Bob'],
])->execute();
```

Chain `relation()` to insert into a relation table (`INSERT RELATION`), or `ignore()` to skip records that already exist (`INSERT IGNORE`).

## Updating records

The `update()` and `upsert()` methods modify records. Choose a strategy by chaining `content()`, `merge()`, `replace()`, or `patch()`.

**Replace content**

Replace the record with new data. Fields not included are removed.

```php
$db->update(new RecordId('person', 'tobie'))
    ->content(['name' => 'Tobie', 'age' => 33])
    ->execute();
```

**Merge fields**

Merge new fields into the record. Existing fields stay unless overwritten.

```php
$db->update(new RecordId('person', 'tobie'))
    ->merge(['age' => 33])
    ->execute();
```

**JSON Patch**

Apply [JSON Patch](https://jsonpatch.com) operations for fine-grained edits.

```php
$db->update(new RecordId('person', 'tobie'))
    ->patch([
        ['op' => 'replace', 'path' => '/age', 'value' => 33],
        ['op' => 'add', 'path' => '/verified', 'value' => true],
    ])
    ->execute();
```

You can filter which records to update with `where()`.

```php
$db->update(new Table('person'))
    ->merge(['verified' => true])
    ->where('age >= 18')
    ->execute();
```

## Deleting records

The `delete()` method removes records. It defaults to `RETURN BEFORE`, so the deleted records are returned.

```php
$db->delete(new RecordId('person', 'tobie'))->execute();

$db->delete(new Table('person'))->execute();
```

## Creating graph relationships

The `relate()` method creates edges in SurrealDB's [graph model](/docs/reference/query-language/statements/relate.md). Pass the source, the edge table, and the target, with optional edge data.

```php
$db->relate(
    new RecordId('person', 'tobie'),
    new Table('likes'),
    new RecordId('post', 'surrealdb'),
    ['since' => 2024],
)->execute();
```

## Running functions

The `call()` method invokes a SurrealQL or SurrealML function by name. Pass an optional version and a list of arguments.

```php
$total = $db->call('fn::calculate_total', null, [100, 0.2])->execute();

$prediction = $db->call('ml::predict', '1.0.0', [$input])->execute();
```

## Setting session parameters

Use `let()` to define a parameter on the session and `unset()` to remove it. Session parameters are available in later queries as `$name`.

```php
$db->let('current_user', ['first' => 'Tobie']);

$db->run('CREATE post SET author = $current_user');

$db->unset('current_user');
```

## Learn more

- [Query Builders API reference](/docs/reference/php/v2/api/query-builders.md) for every builder method
- [Live queries](/docs/reference/php/v2/concepts/live-queries.md) for real-time subscriptions
- [Transactions](/docs/reference/php/v2/concepts/transactions.md) for atomic multi-statement operations
- [SurrealQL statements](/docs/reference/query-language/statements/overview.md) for the query language reference

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/live-queries

# Live queries

Subscribe to real-time changes from SurrealDB over a WebSocket connection with version 2 of the PHP SDK.

Live queries notify your application whenever records that match a query are created, updated, or deleted. You start a `LIVE SELECT` statement to get a query ID, then iterate the messages the server pushes for that ID.

> [!NOTE]
> Live queries require a WebSocket connection. They are not available over HTTP. Check support with [`isFeatureSupported()`](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md#testing-for-features) before relying on one.

## Starting a live query

Run a [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md) statement with `run()`. The result is the live query ID.

```php
[$queryId] = $db->run('LIVE SELECT * FROM person');
```

## Receiving messages

Pass the ID to `live()`. It returns an iterable of [`LiveMessage`](/docs/reference/php/v2/api/core.md#livemessage) objects, one per change. Each message carries the `action`, the affected `record` ID, and the new `value`.

```php
use SurrealDB\SDK\Live\LiveAction;

foreach ($db->live($queryId) as $message) {
    match ($message->action) {
        LiveAction::Create => handleCreate($message->value),
        LiveAction::Update => handleUpdate($message->value),
        LiveAction::Delete => handleDelete($message->record),
        LiveAction::Killed => break,
    };
}
```

## Running without blocking the application

A `foreach` over `live()` blocks the current PHP process while it waits for the next message. The standard PHP runtime (PHP-FPM or the CLI) handles one task per worker, so a process parked on a live query cannot serve anything else.

> [!WARNING]
> Consume live queries in a dedicated, long-running worker, and run the PHP runtime with several workers so that one worker blocked on a live query does not freeze the rest of your application. Avoid opening a live query inside a normal web request.

To run live queries alongside the rest of your application, choose an asynchronous runtime or run dedicated workers. See [Runtimes and workers](/docs/reference/php/v2/concepts/runtimes.md) for configuring PHP-FPM with dedicated workers, OpenSwoole, and FrankenPHP.

## Live actions

Every message has an action from the `LiveAction` enum:

| Action | Description |
|--------|-------------|
| `LiveAction::Create` | A new record matched the query |
| `LiveAction::Update` | A matching record was modified |
| `LiveAction::Delete` | A matching record was removed |
| `LiveAction::Killed` | The live query was stopped on the server |

## Stopping a live query

Stop a live query by running a [`KILL`](/docs/reference/query-language/statements/kill.md) statement with its ID. The loop receives a final `LiveAction::Killed` message and ends.

```php
$db->run('KILL $id', ['id' => $queryId]);
```

## Learn more

- [Surreal API reference](/docs/reference/php/v2/api/core.md#live) for the `live()` signature
- [LIVE SELECT](/docs/reference/query-language/statements/live-select.md) for the SurrealQL syntax
- [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) for WebSocket setup

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/middleware

# Middleware

Intercept every RPC in version 2 of the PHP SDK with middleware, including the built-in logging, telemetry, retry, and authentication steps.

Every call the SDK makes to SurrealDB passes through a middleware pipeline before it reaches the transport. Middleware is the SDK's primary extension seam: authentication, logging, telemetry, and retries all participate as middleware, and you can add your own.

The design is inspired by PSR-15, adapted to the SurrealDB RPC request and response. Each middleware receives the request and a `$next` callable, and returns a response. It can inspect or modify the request, short-circuit the call, retry it, or observe the result.

## The middleware interface

A middleware implements `SurrealDB\SDK\Contracts\MiddlewareInterface`, which has a single method.

```php title="Syntax"
public function process(RpcRequest $request, callable $next): RpcResponse
```

Call `$next($request)` to pass control to the next step in the pipeline. The innermost step is the engine's `send()` primitive, which performs the actual RPC.

```php
use SurrealDB\SDK\Contracts\MiddlewareInterface;
use SurrealDB\SDK\Rpc\RpcRequest;
use SurrealDB\SDK\Rpc\RpcResponse;

final class TimingMiddleware implements MiddlewareInterface
{
    public function process(RpcRequest $request, callable $next): RpcResponse
    {
        $start = hrtime(true);

        try {
            return $next($request);
        } finally {
            $elapsed = (hrtime(true) - $start) / 1_000_000;
            error_log("{$request->method} took {$elapsed} ms");
        }
    }
}
```

## Registering middleware

Add your middleware through the `middleware` option on [`DriverOptions`](/docs/reference/php/v2/api/utilities.md#driveroptions). The list is appended to the pipeline in order.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;

$db = new Surreal(new DriverOptions(
    middleware: [
        new TimingMiddleware(),
    ],
));
```

## Pipeline order

The pipeline is assembled when the engine connects. The first middleware in the pipeline runs outermost, so it sees the request first and the response last. The SDK builds the pipeline in this order:

1. **Logging**, when a PSR-3 `logger` is configured.
2. **Telemetry**, when a `tracer` or `meter` is configured.
3. **Your middleware**, in the order given to the `middleware` option.

The built-in steps are added only when their dependency is configured, so a default `new Surreal()` runs with an empty pipeline.

## Built-in middleware

The following middleware ships with the SDK in the `SurrealDB\SDK\Middleware` namespace.

### Logging

`LoggingMiddleware` logs every request, its outcome, and its timing to a PSR-3 logger. It is a pure observation step and never alters the request or response. The SDK adds it automatically when you set the `logger` option, so you rarely construct it by hand.

```php
$db = new Surreal(new DriverOptions(logger: $logger));
```

### Telemetry

`TelemetryMiddleware` emits a trace span and duration and count metrics for every RPC. The SDK adds it automatically when you set a `tracer` or `meter`. It accepts a `recordQueryText` flag (off by default) to include SurrealQL text on `query` spans.

```php
use SurrealDB\SDK\Middleware\TelemetryMiddleware;

// Register manually only when you need to capture query text.
$db = new Surreal(new DriverOptions(
    middleware: [
        new TelemetryMiddleware($tracer, $meter, recordQueryText: true),
    ],
));
```

See [Observability](/docs/reference/php/v2/concepts/observability.md) for what the telemetry step records.

### Retry

`RetryMiddleware` retries calls that fail with a transient connection error, using exponential backoff. It is opt-in rather than part of the default pipeline, because blindly retrying is unsafe for operations that are not idempotent.

```php title="Constructor"
new RetryMiddleware(
    int $maxAttempts = 3,
    float $baseDelaySeconds = 0.1,
    Scheduler $scheduler = new SyncScheduler(),
)
```

```php
use SurrealDB\SDK\Middleware\RetryMiddleware;

$db = new Surreal(new DriverOptions(
    middleware: [
        new RetryMiddleware(maxAttempts: 5),
    ],
));
```

It retries `HttpConnectionException`, `CallTerminatedException`, and `ConnectionUnavailableException`. On an [async runtime](/docs/reference/php/v2/concepts/runtimes.md), pass the matching scheduler so the backoff delay does not block other tasks.

### Authentication

`AuthMiddleware` re-authenticates and retries a call once when it fails with an authentication error, such as an expired token. The SDK wires this in itself when you provide credentials on [`connect()`](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md#authentication-details), so you do not register it manually.

## Learn more

- [Observability](/docs/reference/php/v2/concepts/observability.md) for the telemetry step in depth
- [Events](/docs/reference/php/v2/concepts/events.md) for observing requests without writing middleware
- [Runtimes and workers](/docs/reference/php/v2/concepts/runtimes.md) for the schedulers retry backoff uses
- [Utilities](/docs/reference/php/v2/api/utilities.md#driveroptions) for the `DriverOptions` fields

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/observability

# Observability

Emit OpenTelemetry traces and metrics from version 2 of the PHP SDK, with runtime-aware presets, a configurable provider factory, and vendor-neutral tracing and metrics seams.

Version 2 of the PHP SDK can emit a trace span and metrics for every RPC it sends. Tracing and metrics are exposed as vendor-neutral seams, in the same way logging is exposed through PSR-3: the SDK depends only on a `Tracer` and a `Meter` contract, and concrete backends plug in behind them.

Both seams default to a no-op, so the SDK never depends on a telemetry vendor unless you opt in. You enable them by setting a tracer and a meter, either through a [runtime preset](#runtime-presets) or directly on the [`DriverOptions`](/docs/reference/php/v2/api/utilities.md#driveroptions). When either is a real implementation, the SDK adds a telemetry step to its [middleware](/docs/reference/php/v2/concepts/middleware.md) pipeline automatically and instruments every RPC.

## Runtime presets {#runtime-presets}

The simplest way to enable OpenTelemetry is to pass an `ObservabilityOptions` to a [runtime preset](/docs/reference/php/v2/concepts/runtimes.md). The preset builds the OpenTelemetry providers, configures an OTLP exporter, and picks the export strategy that fits the runtime, then wires the tracer and meter onto the `DriverOptions` for you.

The presets need the OpenTelemetry SDK and the OTLP exporter.

```bash
composer require open-telemetry/sdk open-telemetry/exporter-otlp
```

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Runtime\Runtime;
use SurrealDB\SDK\Telemetry\OpenTelemetry\ObservabilityOptions;

$observability = new ObservabilityOptions(
    endpoint: 'http://localhost:4318', // OTLP collector
    serviceName: 'my-app',
);

// PHP-FPM / CLI: buffer spans, flush after the request.
$db = new Surreal(Runtime::sync(observability: $observability));

// OpenSwoole: direct, non-blocking export.
$db = new Surreal(Runtime::swoole(observability: $observability));

// FrankenPHP / Amp: direct export over a non-blocking client.
$db = new Surreal(Runtime::amp(observability: $observability));
```

Each preset chooses an export strategy suited to how the runtime handles long work.

| Preset | Strategy | Behaviour |
|--------|----------|-----------|
| `Runtime::sync()` | Batched | Buffers spans in memory and flushes once the request finishes, after `fastcgi_finish_request()` under PHP-FPM, so export stays off the user-visible request path |
| `Runtime::swoole()` | Direct | Exports each span as it ends; OpenSwoole's runtime hooks make that export non-blocking |
| `Runtime::amp()` | Direct | Exports each span as it ends over a non-blocking Amp PSR-18 client, so export yields on the event loop |

The Amp preset additionally needs `amphp/http-client-psr7` for its non-blocking exporter.

```bash
composer require amphp/http-client-psr7
```

## Configuring `ObservabilityOptions`

`ObservabilityOptions` is a value object in the `SurrealDB\SDK\Telemetry\OpenTelemetry` namespace. Every field has a default, so `new ObservabilityOptions()` works against a local collector.

| Option | Default | Description |
|--------|---------|-------------|
| `endpoint` | env, then `http://localhost:4318` | Base OTLP endpoint, with no signal path |
| `contentType` | `application/x-protobuf` | OTLP payload encoding, or `application/json` |
| `headers` | `[]` | Extra headers sent on every export, such as an ingest token |
| `serviceName` | `surrealdb-php` | The `service.name` resource attribute |
| `samplerRatio` | `null` | Head sampling probability in `[0, 1]`; `null` records every span |
| `traces` | `true` | Emit spans |
| `metrics` | `true` | Emit metrics |
| `finishRequestBeforeFlush` | `true` | Batched strategy: call `fastcgi_finish_request()` before draining the buffer |
| `maxQueueSize`, `scheduledDelayMillis`, `maxExportBatchSize` | `null` | Batched strategy tuning; `null` uses the OpenTelemetry defaults |

When `endpoint` is empty, the SDK falls back to the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable, then `http://localhost:4318`. The `/v1/traces` and `/v1/metrics` paths are appended automatically.

```php
$observability = new ObservabilityOptions(
    serviceName: 'checkout-api',
    headers: ['Authorization' => 'Bearer ' . $token],
    samplerRatio: 0.1, // sample 10% of traces
);
```

## Flushing in a framework

The `Runtime::sync()` preset registers a shutdown hook to flush buffered telemetry. When a framework owns the response lifecycle, such as Laravel, build the providers yourself with `OtelObservability` and flush in a terminable hook instead.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;
use SurrealDB\SDK\Telemetry\OpenTelemetry\ObservabilityOptions;
use SurrealDB\SDK\Telemetry\OpenTelemetry\OtelObservability;

$telemetry = OtelObservability::batched(new ObservabilityOptions(serviceName: 'my-app'));

$db = new Surreal(new DriverOptions(
    tracer: $telemetry->tracer(),
    meter: $telemetry->meter(),
));

// Laravel: flush after the response has been sent to the client.
app()->terminating(static fn () => $telemetry->forceFlush());
```

`OtelObservability` is the factory the presets use. Build it with `batched()` for the FPM/CLI strategy or `direct()` for async runtimes, pass `ampHttpClient()` as the `direct()` client on Amp, and call `forceFlush()` to drain buffered telemetry or `shutdown()` to flush and tear the providers down. Both factories accept optional span and metric exporters, mainly for tests and custom backends.

## Bridging an existing OpenTelemetry setup

If your application already configures the OpenTelemetry SDK, bridge its providers onto the SDK seams rather than building new ones. This needs only `open-telemetry/api`.

```bash
composer require open-telemetry/api
```

Use `fromGlobals()` to read the globally registered provider, so SDK spans nest under whatever ambient span is active in the calling code. You manage the exporters and flushing through your own provider.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;
use SurrealDB\SDK\Telemetry\OpenTelemetry\OtelTracer;
use SurrealDB\SDK\Telemetry\OpenTelemetry\OtelMeter;

$db = new Surreal(new DriverOptions(
    tracer: OtelTracer::fromGlobals(),
    meter: OtelMeter::fromGlobals(),
));
```

To bridge a provider you manage explicitly, pass its tracer or meter to the constructor.

```php
$db = new Surreal(new DriverOptions(
    tracer: new OtelTracer($tracerProvider->getTracer('surrealdb/surrealdb.php')),
    meter: new OtelMeter($meterProvider->getMeter('surrealdb/surrealdb.php')),
));
```

## PSR-3 logs

When you do not want an OpenTelemetry dependency, the PSR-3 adapter records spans and measurements as structured log lines. It is zero-dependency and useful for getting span timing into logs you already collect.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;
use SurrealDB\SDK\Telemetry\Psr\Psr3Tracer;
use SurrealDB\SDK\Telemetry\Psr\Psr3Meter;

$db = new Surreal(new DriverOptions(
    tracer: new Psr3Tracer($logger),
    meter: new Psr3Meter($logger),
));
```

Both PSR-3 adapters log at `debug` level by default. Pass a [PSR-3 log level](https://www.php-fig.org/psr/psr-3/#5-psrlogloglevel) as the second argument to change it.

## What is emitted

The telemetry step follows the [OpenTelemetry database semantic conventions](https://opentelemetry.io/docs/specs/semconv/database/). For each RPC it opens a span named `surrealdb.<method>` (for example `surrealdb.query`) with the `Client` span kind, then sets its status to `Ok` or `Error` and records any thrown exception.

| Attribute | Description |
|-----------|-------------|
| `db.system.name` | Always `surrealdb` |
| `db.operation.name` | The RPC method, such as `query` or `signin` |
| `db.surrealdb.session` | The session id, when the call targets a non-default session |
| `db.surrealdb.transaction` | The transaction id, when the call runs inside one |
| `db.query.text` | The SurrealQL text on `query` spans, only when query-text capture is enabled |
| `error.type` | The error kind or exception class, on failures |

Two metrics are recorded per call:

| Metric | Type | Unit | Description |
|--------|------|------|-------------|
| `db.client.operation.duration` | Histogram | `s` | How long each operation took |
| `db.client.operation.count` | Counter | `{operation}` | Number of operations, dimensioned by `outcome` |

> [!NOTE]
> SurrealQL text is not captured by default, because queries can carry sensitive data. The `db.query.text` attribute is only added when you construct a [`TelemetryMiddleware`](/docs/reference/php/v2/concepts/middleware.md) with `recordQueryText: true` and register it yourself.

## Custom backends

To send telemetry somewhere the bundled adapters do not cover, implement the contracts in the `SurrealDB\SDK\Contracts` namespace. A backend can implement tracing, metrics, or both; the two seams are independent.

| Contract | Methods |
|----------|---------|
| `Tracer` | `startSpan(string $name, SpanKind $kind = SpanKind::Client, array $attributes = []): Span` |
| `Span` | `setAttribute()`, `recordException()`, `setStatus()`, `end()` |
| `Meter` | `counter(string $name, ?string $unit, ?string $description): Counter`, `histogram(...): Histogram` |
| `Counter` | `add(int\|float $value = 1, array $attributes = []): void` |
| `Histogram` | `record(int\|float $value, array $attributes = []): void` |

Spans use two enums from `SurrealDB\SDK\Enum`. `SpanKind` has `Internal`, `Client`, `Server`, `Producer`, and `Consumer`. `SpanStatus` has `Unset`, `Ok`, and `Error`.

```php
use SurrealDB\SDK\Contracts\Tracer;
use SurrealDB\SDK\Contracts\Span;
use SurrealDB\SDK\Enum\SpanKind;

final class CustomTracer implements Tracer
{
    public function startSpan(string $name, SpanKind $kind = SpanKind::Client, array $attributes = []): Span
    {
        // return your own Span implementation
    }
}
```

## Logging

Telemetry is for traces and metrics. To log each RPC instead, pass a PSR-3 `logger` to `DriverOptions`. The SDK then adds its logging step to the pipeline automatically, recording every request, its outcome, and its timing. See [Middleware](/docs/reference/php/v2/concepts/middleware.md) for the built-in logging step.

```php
$db = new Surreal(new DriverOptions(logger: $logger));
```

## Learn more

- [Runtimes and workers](/docs/reference/php/v2/concepts/runtimes.md) for the presets that wire observability per runtime
- [Middleware](/docs/reference/php/v2/concepts/middleware.md) for the pipeline the telemetry step runs in
- [Events](/docs/reference/php/v2/concepts/events.md) for PSR-14 events you can observe per request
- [Utilities](/docs/reference/php/v2/api/utilities.md#driveroptions) for the `DriverOptions` fields

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/runtimes

# Runtimes and workers

Configure your PHP environment (PHP-FPM, OpenSwoole, or FrankenPHP) so live queries and other long-lived connections run without blocking your application.

By default the SDK runs on the standard synchronous PHP runtime. That suits request and response work: connect, run a query, return a result. Long-lived operations such as [live queries](/docs/reference/php/v2/concepts/live-queries.md) block the process that runs them until they end, so they need a runtime and worker setup that keeps the rest of your application responsive.

This page covers three setups: PHP-FPM with dedicated workers, OpenSwoole, and FrankenPHP.

## Selecting a runtime

The `Runtime` helper returns a `DriverOptions` preset that you pass to `new Surreal(...)`. It wires up the matching scheduler and transports.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Runtime\Runtime;

$db = new Surreal(Runtime::sync());   // default, synchronous
$db = new Surreal(Runtime::swoole()); // OpenSwoole coroutines
$db = new Surreal(Runtime::amp());    // Amp / Revolt event loop
```

The Swoole and Amp presets rely on optional packages, so install the matching dependency (listed under `suggest` in the SDK's `composer.json`) before using them.

### Enabling observability per runtime

Each preset optionally accepts an `ObservabilityOptions` to wire OpenTelemetry with the export strategy that fits the runtime: the synchronous preset buffers spans and flushes after the request, while the async presets export directly through a non-blocking transport.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Runtime\Runtime;
use SurrealDB\SDK\Telemetry\OpenTelemetry\ObservabilityOptions;

$db = new Surreal(Runtime::sync(
    observability: new ObservabilityOptions(serviceName: 'my-app'),
));
```

See [Observability](/docs/reference/php/v2/concepts/observability.md#runtime-presets) for the full setup and options.

> [!NOTE]
> A live query blocks whichever process runs its loop. Whatever runtime you choose, do not open a live query inside a normal web request. Run it in a dedicated worker.

## PHP-FPM

PHP-FPM is the common setup behind Nginx or Apache. Its pool serves web requests, and each worker handles one request at a time on the synchronous runtime. That is fine for queries and mutations, but a live query would hold a worker open indefinitely and eventually exhaust the pool.

Keep PHP-FPM for the web tier, and run live queries in separate, long-running CLI workers.

### Sizing the web pool

Tune the pool in your PHP-FPM config (for example `www.conf`) so the web tier has enough workers for your traffic.

```ini
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
```

### Running live query workers

Put the live query loop in a CLI script.

```php title="worker.php"
require __DIR__ . '/vendor/autoload.php';

use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\ConnectOptions;

$db = new Surreal();
$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    namespace: 'surrealdb',
    database: 'docs',
));

[$queryId] = $db->run('LIVE SELECT * FROM person');

foreach ($db->live($queryId) as $message) {
    // handle $message->action and $message->value
}
```

Run several copies under a process manager so that one worker blocked on a live query never affects the others. With [Supervisor](http://supervisord.org), the `numprocs` setting controls how many processes run in parallel.

```ini title="/etc/supervisor/conf.d/surreal-live.conf"
[program:surreal-live]
command=php /var/www/app/worker.php
numprocs=4
process_name=%(program_name)s_%(process_num)02d
autostart=true
autorestart=true
stopwaitsecs=10
```

A systemd template unit achieves the same. Define `surreal-live@.service`, then start as many instances as you need with `systemctl start surreal-live@{1..4}`.

## OpenSwoole

[OpenSwoole](https://openswoole.com) adds a coroutine runtime, so a single process can drive many concurrent tasks, including live queries, on an event loop without blocking.

Install the extension with PECL and enable it in your `php.ini`.

```bash
pecl install openswoole
```

Run your code inside a coroutine context and use the Swoole preset. The preset enables OpenSwoole's runtime hooks so the SDK's connection becomes non-blocking.

```php
use OpenSwoole\Coroutine as Co;
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\ConnectOptions;
use SurrealDB\SDK\Runtime\Runtime;

Co::run(function (): void {
    $db = new Surreal(Runtime::swoole());
    $db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
        namespace: 'surrealdb',
        database: 'docs',
    ));

    [$queryId] = $db->run('LIVE SELECT * FROM person');

    foreach ($db->live($queryId) as $message) {
        // other coroutines keep running while this waits
    }
});
```

In an OpenSwoole HTTP server, set the number of worker processes with the `worker_num` server setting.

## FrankenPHP

[FrankenPHP](https://frankenphp.dev) runs PHP as a long-lived application server. In worker mode the app stays booted between requests, and the Amp / Revolt event loop lets the SDK process live queries without blocking. Install `revolt/event-loop` and the Amp packages (see the SDK's `composer.json` suggestions), then use the Amp preset.

```php
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Runtime\Runtime;

$db = new Surreal(Runtime::amp());
```

Enable [worker mode](https://frankenphp.dev/docs/worker/) and set the worker count in your `Caddyfile` or through the environment variable. The trailing number is the number of workers.

```bash
FRANKENPHP_CONFIG="worker ./public/index.php 4"
```

> [!NOTE]
> For Laravel applications, [Laravel Octane](https://laravel.com/docs/octane) runs your app on Swoole, FrankenPHP, or RoadRunner with a configurable `--workers` count. See the [Laravel integration](/docs/reference/php/frameworks/laravel.md).

## Learn more

- [Live queries](/docs/reference/php/v2/concepts/live-queries.md) for the subscription API
- [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) for connection setup
- [Utilities](/docs/reference/php/v2/api/utilities.md#driveroptions) for the `DriverOptions` the presets configure

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/sessions

# Sessions

Run multiple independent sessions over a single WebSocket connection with version 2 of the PHP SDK, each with its own namespace, variables, and authentication.

A session is an independent context on a connection. It carries its own selected namespace and database, its own session variables, and its own authentication state. Version 2 of the PHP SDK opens one default session when you connect, and every method on the `Surreal` class runs against it.

For most applications the default session is all you need. Multiple sessions are useful when a single long-lived connection has to serve several independent contexts, for example different authenticated users on the same WebSocket.

> [!IMPORTANT]
> Multiple sessions require a WebSocket connection and SurrealDB `3.0.0` or later. Check support with `isFeatureSupported()` before relying on them.

```php
use SurrealDB\SDK\Protocol\Features;

if ($db->isFeatureSupported(Features::sessions())) {
    // safe to create extra sessions
}
```

## Managing sessions

Additional sessions are managed through the [`ConnectionController`](/docs/reference/php/v2/api/core.md#connectioncontroller), which you reach with `connection()`.

### Creating a session

`createSession()` opens a new session and returns its id. Pass the id of an existing session to `clone` to copy its namespace, database, and variables into the new one.

```php title="Syntax"
$db->connection()->createSession(?string $clone = null): string
```

```php
$session = $db->connection()->createSession();
```

### Listing sessions

`sessions()` returns the ids of the open sessions.

```php title="Syntax"
$db->connection()->sessions(): array
```

### Destroying a session

`destroySession()` closes a session and releases it. Passing an unknown id throws an `InvalidSessionException`.

```php title="Syntax"
$db->connection()->destroySession(?string $session): void
```

```php
$db->connection()->destroySession($session);
```

## Running queries in a session

The methods on `Surreal` always use the default session. To run a statement in another session, call `query()` on the controller and pass the session id. It returns an iterable of result chunks, one per statement; call `resultOrThrow()` on each to get its value or raise the server error.

```php
use SurrealDB\SDK\Query\BoundQuery;

$session = $db->connection()->createSession();

foreach ($db->connection()->query(
    new BoundQuery('SELECT * FROM person'),
    $session,
) as $chunk) {
    $people = $chunk->resultOrThrow();
}

$db->connection()->destroySession($session);
```

The same call accepts a transaction id as a third argument, so a session can run statements inside an explicit [transaction](/docs/reference/php/v2/concepts/transactions.md).

## Learn more

- [Surreal API reference](/docs/reference/php/v2/api/core.md#connectioncontroller) for the session methods
- [Transactions](/docs/reference/php/v2/concepts/transactions.md) for running statements atomically
- [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) for the default session and WebSocket setup

---

Source: https://surrealdb.com/docs/reference/php/v2/concepts/transactions

# Transactions

Run multiple statements atomically with version 2 of the PHP SDK, using a SurrealQL transaction block or explicit transaction handles.

A transaction groups statements so they either all succeed or all fail. This keeps related changes consistent, for example when transferring a value between two records.

## Transaction blocks

The simplest approach wraps statements in [`BEGIN TRANSACTION`](/docs/reference/query-language/statements/begin.md) and [`COMMIT TRANSACTION`](/docs/reference/query-language/statements/commit.md) and runs them as a single query with `run()`. SurrealDB rolls back the whole block if any statement fails. This works over both WebSocket and HTTP.

```php
$db->run('
    BEGIN TRANSACTION;
    UPDATE account:one SET balance -= 100;
    UPDATE account:two SET balance += 100;
    COMMIT TRANSACTION;
');
```

Use [`CANCEL TRANSACTION`](/docs/reference/query-language/statements/cancel.md) inside the block, or a [`THROW`](/docs/reference/query-language/statements/throw.md) expression, to abort and roll back from within SurrealQL.

## Explicit transaction handles

For finer control over a WebSocket connection, the [`ConnectionController`](/docs/reference/php/v2/api/core.md#connectioncontroller) exposes explicit transaction handles through `connection()`. Call `begin()` to start one, then `commit()` or `cancel()` with the returned handle.

```php
$txn = $db->connection()->begin();

try {
    // ... run statements bound to $txn ...
    $db->connection()->commit($txn);
} catch (\Throwable $error) {
    $db->connection()->cancel($txn);
    throw $error;
}
```

> [!NOTE]
> Explicit handles require the transactions feature, which depends on the WebSocket engine and a compatible server version. For most applications, a transaction block run with `run()` is simpler and works everywhere.

## Learn more

- [Surreal API reference](/docs/reference/php/v2/api/core.md#connectioncontroller) for the transaction methods
- [Executing queries](/docs/reference/php/v2/concepts/executing-queries.md) for running statements
- [BEGIN](/docs/reference/query-language/statements/begin.md) and [COMMIT](/docs/reference/query-language/statements/commit.md) for the SurrealQL statements

---

Source: https://surrealdb.com/docs/reference/php/v2/installation

# Installation

Install version 2 of the SurrealDB PHP SDK with Composer, including a PSR-18 HTTP client.

Version 2 of the PHP SDK is installed with [Composer](https://getcomposer.org/download/). It requires PHP `8.4` or later.

## Install the SDK

Version 2 is an alpha release, so you need to request the exact version. Composer will not select it under the default `stable` minimum stability.

```bash
composer require surrealdb/surrealdb.php:2.0.0-alpha.1
```

If you want Composer to keep tracking alpha releases, set the stability flag instead:

```bash
composer require "surrealdb/surrealdb.php:^2.0@alpha"
```

## Add an HTTP client

The HTTP engine uses [PSR-18](https://www.php-fig.org/psr/psr-18/) and [PSR-17](https://www.php-fig.org/psr/psr-17/) interfaces rather than a built-in client. Install a compatible client and factory implementation, plus discovery so the SDK can find them automatically.

```bash
composer require guzzlehttp/guzzle php-http/discovery
```

> [!NOTE]
> The WebSocket engine uses PHP's native stream functions and needs no extra packages. You only need a PSR-18 client when connecting over `http://` or `https://`, or when using import and export.

## Import the SDK

Include the Composer autoloader, then import the classes you need from the `SurrealDB\SDK` namespace.

```php
require __DIR__ . '/vendor/autoload.php';

use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\ConnectOptions;
use SurrealDB\SDK\Auth\RootAuth;
use SurrealDB\SDK\Types\RecordId;
use SurrealDB\SDK\Types\Table;
```

## Next steps

- [Getting started](/docs/reference/php/versions/v2-alpha.md) to connect and run your first queries
- [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) for connection options and reconnection
- [Migration guide](/docs/reference/php/v2/migration.md) if you are coming from v1

---

Source: https://surrealdb.com/docs/reference/php/v2/migration

# Migration guide

Move a project from version 1 to version 2 of the SurrealDB PHP SDK, with a method-by-method mapping of the breaking changes.

Version 2 is a rewrite of the PHP SDK. The namespace, the query API, the credential types, and the connection options all changed. This guide maps each v1 pattern to its v2 equivalent.

> [!IMPORTANT]
> Version 2 is published as `2.0.0-alpha.1` and requires PHP `8.4` or later. It is an alpha, so review the changes here and test before upgrading a production project. Version 1 remains the stable release.

## What changed

- The namespace moved from `Surreal\` to `SurrealDB\SDK\`.
- Mutations use fluent builders that end in `execute()`, instead of taking data as a second argument.
- Raw SurrealQL runs through `run()`. In v1, `query()` ran SurrealQL; in v2, `query()` takes a pre-built `BoundQuery`.
- Credentials are typed classes such as `RootAuth`, and `signin()` returns a `Tokens` object instead of a string.
- Connection settings are passed as a `ConnectOptions` object, and `use()` takes positional arguments.
- Data type classes moved to `SurrealDB\SDK\Types` and use named constructors such as `RecordId::from()`.
- Import and export moved to `$db->connection()`.

## 1. Install version 2

Update your PHP version to `8.4` or later, then require the alpha release and a PSR-18 HTTP client.

```bash
composer require surrealdb/surrealdb.php:2.0.0-alpha.1
composer require guzzlehttp/guzzle php-http/discovery
```

## 2. Update the namespace

```php
// v1
use Surreal\Surreal;

// v2
use SurrealDB\SDK\Surreal;
```

## 3. Update the connection

In v2, pass connection settings as a `ConnectOptions` object and select the namespace and database with positional arguments.

```php
// v1
$db->connect('ws://127.0.0.1:8000/rpc');
$db->use(['namespace' => 'surrealdb', 'database' => 'docs']);

// v2
use SurrealDB\SDK\Connection\ConnectOptions;

$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    namespace: 'surrealdb',
    database: 'docs',
));
```

## 4. Update authentication

Credentials are now typed classes, and `signin()` returns a `Tokens` object.

```php
// v1
$token = $db->signin(['user' => 'root', 'pass' => 'root']);

// v2
use SurrealDB\SDK\Auth\RootAuth;

$tokens = $db->signin(new RootAuth('root', 'root'));
$token = $tokens->access;
```

Record access uses `RecordAccessAuth`, and the `scope` key is now `access`.

```php
// v1
$db->signin([
    'namespace' => 'surrealdb',
    'database' => 'docs',
    'scope' => 'user',
    'email' => 'a@b.com',
    'pass' => 'secret',
]);

// v2
use SurrealDB\SDK\Auth\RecordAccessAuth;

$db->signin(new RecordAccessAuth(
    namespace: 'surrealdb',
    database: 'docs',
    access: 'user',
    variables: ['email' => 'a@b.com', 'pass' => 'secret'],
));
```

## 5. Update queries

Raw SurrealQL moves from `query()` to `run()`. Mutations become fluent builders.

```php
// v1
$people = $db->query('SELECT * FROM person WHERE age > $min', ['min' => 18]);
$person = $db->create('person:tobie', ['name' => 'Tobie']);
$db->merge('person:tobie', ['age' => 33]);

// v2
$people = $db->run('SELECT * FROM person WHERE age > $min', ['min' => 18]);
$person = $db->create(new RecordId('person', 'tobie'))
    ->content(['name' => 'Tobie'])
    ->execute();
$db->update(new RecordId('person', 'tobie'))
    ->merge(['age' => 33])
    ->execute();
```

## 6. Update data types

Data type classes moved to `SurrealDB\SDK\Types` and use named constructors.

```php
// v1
use Surreal\Cbor\Types\Record\RecordId;
$id = RecordId::create('person', 'tobie');

// v2
use SurrealDB\SDK\Types\RecordId;
$id = RecordId::from('person', 'tobie'); // or new RecordId('person', 'tobie')
```

The `RecordId` properties also changed: `->tb` is now `->table`.

## 7. Update import and export

```php
// v1
$db->import($sql, 'root', 'root');
$dump = $db->export('root', 'root');

// v2
$db->connection()->importSql($sql);
$dump = $db->connection()->exportSql();
```

## Method mapping

| v1 | v2 |
|----|----|
| `new \Surreal\Surreal()` | `new \SurrealDB\SDK\Surreal()` |
| `connect($url, ['namespace' => ..., 'database' => ...])` | `connect($url, new ConnectOptions(namespace: ..., database: ...))` |
| `use(['namespace' => $ns, 'database' => $db])` | `use($ns, $db)` |
| `signin([...]): string` | `signin(new RootAuth(...)): Tokens` |
| `query($sql, $vars)` | `run($sql, $vars)` |
| `select($thing)` | `select($thing)->execute()` |
| `create($thing, $data)` | `create($thing)->content($data)->execute()` |
| `update($thing, $data)` | `update($thing)->content($data)->execute()` |
| `merge($thing, $data)` | `update($thing)->merge($data)->execute()` |
| `patch($thing, $patches)` | `update($thing)->patch($patches)->execute()` |
| `insert($table, $data)` | `insert(new Table($table), $data)->execute()` |
| `insertRelation($table, $data)` | `insert(new Table($table), $data)->relation()->execute()` |
| `delete($thing)` | `delete($thing)->execute()` |
| `relate($from, $edge, $to, $data)` | `relate($from, $edge, $to, $data)->execute()` |
| `run('fn::foo', '1.0', $args)` | `call('fn::foo', '1.0', $args)->execute()` |
| `RecordId::create('t', 'id')` | `RecordId::from('t', 'id')` |
| `Table::create('t')` | `new Table('t')` |
| `import($sql, $user, $pass)` | `connection()->importSql($sql)` |
| `export($user, $pass)` | `connection()->exportSql()` |
| `status(): int` | `status(): ConnectionStatus` |

## Removed and changed APIs

- `info()` is not exposed on the v2 `Surreal` class. Use the `auth()` builder, which compiles to `SELECT * FROM ONLY $auth`.
- The SurrealML import and export helpers (`importML()`, `exportML()`) are not part of the v2 public API.
- `query()` no longer runs raw SurrealQL. It executes a `BoundQuery`; use `run()` for raw statements.
- `status()` returns a `ConnectionStatus` enum rather than an HTTP status integer.

## Learn more

- [v2 overview](/docs/reference/php/v2.md) for the new SDK
- [Executing queries](/docs/reference/php/v2/concepts/executing-queries.md) for the builder API
- [Authentication](/docs/reference/php/v2/concepts/authentication.md) for the credential types

---

Source: https://surrealdb.com/docs/reference/php/versions/v2-alpha

# v2 (alpha)

Connect to SurrealDB and run your first queries with version 2 (alpha) of the PHP SDK.

The PHP SDK for SurrealDB lets you connect to a database and query it from your application. This guide covers connecting, authenticating, and running your first queries with the v2 line of the SDK.

> [!IMPORTANT]
> Version 2.x is a rewrite with a fluent query builder, typed credentials, and a PSR-based transport layer. It is in **alpha** and introduces breaking changes, so pin the exact version when installing it. The current stable release is documented under [v1](/docs/reference/php/v1.md), with its [getting-started guide](/docs/languages/php.md) in the Start section.

## 1. Install the SDK

Follow the [installation guide](/docs/reference/php/v2/installation.md) to add the SDK to your project. Once installed, include the autoloader and import the classes you need.

```php
require __DIR__ . '/vendor/autoload.php';

use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\ConnectOptions;
use SurrealDB\SDK\Auth\RootAuth;
use SurrealDB\SDK\Types\RecordId;
use SurrealDB\SDK\Types\Table;

$db = new Surreal();
```

## 2. Connect to SurrealDB

Use the `connect()` method with a connection string and a `ConnectOptions` object. The options carry the namespace, database, and authentication details. Passing credentials here lets the SDK re-authenticate automatically after a reconnect.

Supported connection protocols include:
- **WebSocket** (`ws://`, `wss://`) for long-lived stateful connections that support live queries
- **HTTP** (`http://`, `https://`) for short-lived stateless connections

```php
$db->connect('ws://127.0.0.1:8000/rpc', new ConnectOptions(
    namespace: 'surrealdb',
    database: 'docs',
    authentication: new RootAuth('root', 'root'),
));
```

See [Connecting to SurrealDB](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) for the full set of options.

## 3. Inserting data into SurrealDB

The `create()` method starts a `CREATE` statement. Chain `content()` to set the record data, then call `execute()` to run it. Pass a [`RecordId`](/docs/reference/php/v2/api/data-types.md#recordid) for a specific ID, or a [`Table`](/docs/reference/php/v2/api/data-types.md#table) to let SurrealDB generate one.

```php
$person = $db->create(new RecordId('person', 'tobie'))
    ->content([
        'name' => 'Tobie',
        'age' => 32,
    ])
    ->execute();

$auto = $db->create(new Table('person'))
    ->content(['name' => 'Jaime'])
    ->execute();
```

## 4. Retrieving data from SurrealDB

### Selecting records

The `select()` method reads records. Pass a `Table` to read all records, or a `RecordId` to read one. Chain `fields()`, `where()`, and `limit()` to refine the query before `execute()`.

```php
$everyone = $db->select(new Table('person'))->execute();

$tobie = $db->select(new RecordId('person', 'tobie'))->execute();

$adults = $db->select(new Table('person'))
    ->fields('name', 'age')
    ->where('age >= 18')
    ->limit(10)
    ->execute();
```

### Running SurrealQL queries

For anything the builders do not cover, `run()` executes raw [SurrealQL](/docs/reference/query-language.md). Pass bindings as the second argument to inject values safely.

```php
[$cheapest] = $db->run(
    'SELECT name, age FROM person WHERE age < $max ORDER BY age',
    ['max' => 40],
);
```

`run()` returns one result per statement, so destructure the first entry for a single-statement query.

## 5. Closing the connection

Always close the connection when you are done to release resources.

```php
$db->close();
```

## Next steps

Beyond creating and retrieving records, the SDK supports updating and deleting records, authentication, live queries, and transactions.

- [Connection management](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md) - Learn how to manage your database connections, including protocols and configuration.

- [Authentication](/docs/reference/php/v2/concepts/authentication.md) - Sign in and sign up with root, namespace, database, and record access.

- [Executing queries](/docs/reference/php/v2/concepts/executing-queries.md) - Use the query builders and the raw query API in depth.

- [API Reference](/docs/reference/php/v2/api/core.md) - Complete reference for the core client, query builders, and types.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [PHP (v2) SDK reference](/docs/reference/php/v2.md).

---

Source: https://surrealdb.com/docs/reference/python

# Python SDK

The official SurrealDB SDK for Python. Simple and advanced querying of a remote or embedded database.

The SurrealDB SDK for Python lets you connect to SurrealDB from any Python application. It supports both synchronous and asynchronous workflows, connecting to remote instances over WebSocket or HTTP, and running embedded databases in-process. The SDK provides methods for querying, managing data and authentication, and subscribing to real-time updates with live queries.

> [!IMPORTANT]
> The SDK requires Python `3.10` or later, and is available as a [PyPI package](https://pypi.org/project/surrealdb/).

> [!NOTE]
> The latest version of the SDK is `2.0.0`.
> The SDK works with SurrealDB versions `v2.0.0` and later, including the current release, `v3.3.4`.

## Getting started

- [Installation](/docs/reference/python/installation.md) - Install the SDK and add it to your project.

- [Getting started guide](/docs/languages/python.md) - Connect to SurrealDB and run your first queries.

## Learn

- [Concepts](/docs/reference/python/concepts/connecting-to-surrealdb.md) - Guides for connecting, authenticating, querying, and working with data.

- [API Reference](/docs/reference/python/api/core/surreal.md) - Complete reference for the SDK's methods, types, and errors.

## Concepts

- [Connecting to SurrealDB](/docs/reference/python/concepts/connecting-to-surrealdb.md) - open a connection over HTTP or WebSocket
- [Authentication](/docs/reference/python/concepts/authentication.md) - sign up, sign in, and authenticate with a token
- [Multiple sessions](/docs/reference/python/concepts/multiple-sessions.md) - run isolated sessions over a single connection
- [Executing queries](/docs/reference/python/concepts/executing-queries.md) - send SurrealQL and read the results back
- [Data manipulation](/docs/reference/python/concepts/data-manipulation.md) - create, select, update, upsert and delete records
- [Value types](/docs/reference/python/concepts/value-types.md) - how SurrealDB's types map onto native ones
- [Transactions](/docs/reference/python/concepts/transactions.md) - group statements so they succeed or fail together
- [Live queries](/docs/reference/python/concepts/live-queries.md) - stream changes as they happen
- [Error handling](/docs/reference/python/concepts/error-handling.md) - what a failure looks like, and how to catch it
- [Embedded databases](/docs/reference/python/concepts/embedded-databases.md) - run SurrealDB in-process for tests and local use

## Contributing

To contribute to the SDK code, submit an Issue or Pull Request in the [surrealdb.py](https://github.com/surrealdb/surrealdb.py) repository. To contribute to this documentation, submit an Issue or Pull Request in the [docs.surrealdb.com](https://github.com/surrealdb/docs.surrealdb.com) repository.

## Sources

- [GitHub repository](https://github.com/surrealdb/surrealdb.py)
- [PyPI package](https://pypi.org/project/surrealdb/)

---

Source: https://surrealdb.com/docs/reference/python/api/core/surreal

# Surreal

The Surreal and AsyncSurreal factory functions are the main entry points for connecting to and interacting with a SurrealDB instance from Python.

The `Surreal` and `AsyncSurreal` factory functions create a connection to a SurrealDB instance. They inspect the URL scheme and return the appropriate connection class (WebSocket, HTTP, or embedded), so you use the same interface regardless of protocol.

`Surreal(url)` returns a blocking (synchronous) connection. `AsyncSurreal(url)` returns an asynchronous connection. Both expose the same set of methods; the async variants must be awaited.

**Source:** [surrealdb.py](https://github.com/surrealdb/surrealdb.py)

## Factory functions {#factory-functions}

### `Surreal(url)` {#surreal-sync}

Creates a synchronous connection based on the URL scheme.

```python title="Syntax"
from surrealdb import Surreal

db = Surreal(url)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>url</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The connection URL. The scheme determines the connection type.</td>
        </tr>
    </tbody>
</table>

**Returns:** `BlockingWsSurrealConnection | BlockingHttpSurrealConnection | BlockingEmbeddedSurrealConnection`

### `AsyncSurreal(url)` {#surreal-async}

Creates an asynchronous connection based on the URL scheme.

```python title="Syntax"
from surrealdb import AsyncSurreal

db = AsyncSurreal(url)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>url</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The connection URL. The scheme determines the connection type.</td>
        </tr>
    </tbody>
</table>

**Returns:** `AsyncWsSurrealConnection | AsyncHttpSurrealConnection | AsyncEmbeddedSurrealConnection`

### URL schemes

| Scheme | Connection Type | Description |
|---|---|---|
| `ws://`, `wss://` | WebSocket | Full-featured stateful connection. Supports live queries, sessions, and transactions. |
| `http://`, `https://` | HTTP | Stateless connection. Each request is independent. |
| `mem://`, `memory://` | Embedded (in-memory) | In-process database that does not persist data. |
| `file://`, `surrealkv://` | Embedded (on-disk) | In-process database backed by SurrealKV storage. |

### Examples

```python title="WebSocket"
from surrealdb import Surreal

db = Surreal("ws://localhost:8000")
```

```python title="HTTP"
from surrealdb import Surreal

db = Surreal("https://cloud.surrealdb.com")
```

```python title="Embedded in-memory"
from surrealdb import Surreal

db = Surreal("mem://")
```

```python title="Embedded on-disk"
from surrealdb import Surreal

db = Surreal("surrealkv://path/to/database")
```

```python title="Async WebSocket"
from surrealdb import AsyncSurreal

db = AsyncSurreal("ws://localhost:8000")
```

---

## Connection methods

### `.connect()` {#connect}

Opens the connection to the SurrealDB instance. The URL can optionally be overridden here.

```python title="Method Syntax"
db.connect(url)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>url</code> _(optional)_</td>
            <td><code>str | None</code></td>
            <td>An optional URL to override the one provided to the factory function. Defaults to <code>None</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `None`

#### Examples

```python title="Synchronous"
from surrealdb import Surreal

db = Surreal("ws://localhost:8000")
db.connect()
```

```python title="Asynchronous"
from surrealdb import AsyncSurreal

db = AsyncSurreal("ws://localhost:8000")
await db.connect()
```

```python title="Override URL"
db = Surreal("ws://localhost:8000")
db.connect("ws://other-host:8000")
```

### `.close()` {#close}

Closes the active connection and releases resources.

```python title="Method Syntax"
db.close()
```

**Returns:** `None`

#### Examples

```python title="Synchronous"
db.close()
```

```python title="Asynchronous"
await db.close()
```

### `.use()` {#use}

Switches to a specific namespace and database.

```python title="Method Syntax"
db.use(namespace, database)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>namespace</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The namespace to use.</td>
        </tr>
        <tr>
            <td><code>database</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The database to use.</td>
        </tr>
    </tbody>
</table>

**Returns:** `None`

#### Examples

```python title="Synchronous"
db.use("my_namespace", "my_database")
```

```python title="Asynchronous"
await db.use("my_namespace", "my_database")
```

### `.version()` {#version}

Returns the version string of the connected SurrealDB instance.

```python title="Method Syntax"
db.version()
```

**Returns:** `str`

#### Examples

```python title="Synchronous"
ver = db.version()
print(ver)  # e.g. "surrealdb-2.2.0"
```

```python title="Asynchronous"
ver = await db.version()
print(ver)
```

---

## Authentication methods

### `.signup()` {#signup}

Signs up a user to a specific access method.

```python title="Method Syntax"
db.signup(vars)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>vars</code> _(required)_</td>
            <td><code>dict[str, <a href="/docs/reference/python/api/types/#value">Value</a>]</code></td>
            <td>Variables used for signup, including <code>namespace</code>, <code>database</code>, <code>access</code>, and any additional fields required by the access method.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Tokens`](/docs/reference/python/api/types/#tokens)

#### Examples

```python title="Synchronous"
token = db.signup({
    "namespace": "my_namespace",
    "database": "my_database",
    "access": "user_access",
    "email": "user@example.com",
    "password": "s3cret",
})
```

```python title="Asynchronous"
token = await db.signup({
    "namespace": "my_namespace",
    "database": "my_database",
    "access": "user_access",
    "email": "user@example.com",
    "password": "s3cret",
})
```

### `.signin()` {#signin}

Signs in to the database with the given credentials.

```python title="Method Syntax"
db.signin(vars)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>vars</code> _(required)_</td>
            <td><code>dict[str, <a href="/docs/reference/python/api/types/#value">Value</a>]</code></td>
            <td>Credentials for authentication. For root access, provide <code>username</code> and <code>password</code>. For scoped access, also include <code>namespace</code>, <code>database</code>, and <code>access</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Tokens`](/docs/reference/python/api/types/#tokens)

#### Examples

```python title="Root signin (sync)"
token = db.signin({
    "username": "root",
    "password": "secret",
})
```

```python title="Root signin (async)"
token = await db.signin({
    "username": "root",
    "password": "secret",
})
```

```python title="Scoped signin"
token = db.signin({
    "namespace": "my_namespace",
    "database": "my_database",
    "access": "user_access",
    "email": "user@example.com",
    "password": "s3cret",
})
```

### `.authenticate()` {#authenticate}

Authenticates the current connection with a JWT token.

```python title="Method Syntax"
db.authenticate(token)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>token</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The JWT token to authenticate with.</td>
        </tr>
    </tbody>
</table>

**Returns:** `None`

#### Examples

```python title="Synchronous"
db.authenticate("eyJhbGciOiJIUzI1NiIs...")
```

```python title="Asynchronous"
await db.authenticate("eyJhbGciOiJIUzI1NiIs...")
```

### `.invalidate()` {#invalidate}

Invalidates the authentication for the current connection, removing the associated JWT token.

```python title="Method Syntax"
db.invalidate()
```

**Returns:** `None`

#### Examples

```python title="Synchronous"
db.invalidate()
```

```python title="Asynchronous"
await db.invalidate()
```

### `.info()` {#info}

Returns the record of the currently authenticated user.

```python title="Method Syntax"
db.info()
```

**Returns:** [`Value`](/docs/reference/python/api/types/#value)

#### Examples

```python title="Synchronous"
user = db.info()
print(user)  # e.g. {"id": "users:john", "email": "john@example.com"}
```

```python title="Asynchronous"
user = await db.info()
```

---

## Variables

### `.let()` {#let}

Defines a variable on the current connection that can be used in subsequent queries.

```python title="Method Syntax"
db.let(key, value)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>key</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The name of the variable (without the <code>$</code> prefix).</td>
        </tr>
        <tr>
            <td><code>value</code> _(required)_</td>
            <td><code><a href="/docs/reference/python/api/types/#value">Value</a></code></td>
            <td>The value to assign to the variable.</td>
        </tr>
    </tbody>
</table>

**Returns:** `None`

#### Examples

```python title="Synchronous"
db.let("user_id", RecordID("users", "john"))
result = db.query("SELECT * FROM users WHERE id = $user_id").first()
```

```python title="Asynchronous"
await db.let("user_id", RecordID("users", "john"))
result = await db.query("SELECT * FROM users WHERE id = $user_id").first()
```

### `.unset()` {#unset}

Removes a previously defined variable from the current connection.

```python title="Method Syntax"
db.unset(key)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>key</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The name of the variable to remove.</td>
        </tr>
    </tbody>
</table>

**Returns:** `None`

#### Examples

```python title="Synchronous"
db.unset("user_id")
```

```python title="Asynchronous"
await db.unset("user_id")
```

---

## Query methods

### `.query()` {#query}

Builds a set of [SurrealQL](/docs/reference/query-language.md) statements to run against the database. Returns an awaitable (async) or lazy (sync) builder - nothing is sent until you trigger it.

```python title="Method Syntax"
db.query(query, vars)
```

> [!IMPORTANT]
> **Behaviour change in v3.0.** `.query()` no longer returns a result directly - it returns a builder that you trigger explicitly with `.execute()`, `.first()` or `.into(cls)`. `.execute()` returns a `list[Value]` with **one entry per statement, always** - even when the query contains a single statement. This surfaces every statement result, fixing the silent-discard behaviour reported in [issue #232](https://github.com/surrealdb/surrealdb.py/issues/232). Use `.first()` when you only care about the first statement's result.

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>query</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The SurrealQL query string to execute.</td>
        </tr>
        <tr>
            <td><code>vars</code> _(optional)_</td>
            <td><code>dict[str, <a href="/docs/reference/python/api/types/#value">Value</a>] | None</code></td>
            <td>Variables to bind into the query. Defaults to <code>None</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** a builder. Nothing is sent to the database until you trigger it with one of the accessors below.

| Accessor | Returns |
|---|---|
| `.execute()` | `list[Value]` - one entry per statement, **always a list**, even for a single statement. See [Value](/docs/reference/python/api/types/#value). |
| `.first()` | The first statement's result, or `None` when the query contains no statements. |
| `.into(cls)` | The N statement results mapped positionally onto the fields of a dataclass (or any class accepting keyword arguments). |
| `.into(cls, rows=True)` | `list[cls]` - each **row** of the first statement's result mapped onto `cls`. |

On an async connection every accessor is awaitable - `await db.query(...).execute()`, `await db.query(...).first()`, `await db.query(...).into(Stats)` - and the builder itself is too, so `await db.query(...)` is shorthand for `await db.query(...).execute()`. The sync builder has no such shortcut: you must call an accessor.

> [!NOTE]
> A single-statement `SELECT` without `ONLY` produces **two** levels of nesting, and only the outer one comes from the SDK. The outer list is the per-statement envelope described above; the inner list is SurrealQL's own result set, because `SELECT ... FROM person:tobie` returns an *array of matching records* even when it targets one record. Use [`ONLY`](/docs/reference/query-language/statements/select.md#the-only-clause) to collapse the inner list server-side, and `.first()` to peel the outer one.
>
> ```python
> db.query("SELECT name FROM person:tobie").execute()            # [[{'name': 'Tobie'}]]
> db.query("SELECT name FROM person:tobie").first()              # [{'name': 'Tobie'}]
> db.query("SELECT VALUE name FROM person:tobie").first()        # ['Tobie']
> db.query("SELECT name FROM ONLY person:tobie").first()         # {'name': 'Tobie'}
> db.query("SELECT VALUE name FROM ONLY person:tobie").first()   # 'Tobie'
> ```

#### Examples

```python title="Single statement (async)"
result = await db.query(
    "SELECT * FROM users WHERE age > $min_age",
    {"min_age": 18},
).execute()
# [[{'id': RecordID(table_name=users, record_id='tobie'), 'age': 30}]]
#  ^ one entry, because the query has one statement

users = await db.query(
    "SELECT * FROM users WHERE age > $min_age",
    {"min_age": 18},
).first()
# [{'id': RecordID(table_name=users, record_id='tobie'), 'age': 30}]
```

```python title="Multi-statement: one entry per statement"
people, count = await db.query(
    "SELECT * FROM person; SELECT count() FROM person GROUP ALL"
).execute()
```

```python title="Map results onto a dataclass"
from dataclasses import dataclass

@dataclass
class Stats:
    people: list
    count: list

stats = await db.query(
    "SELECT * FROM person; SELECT count() FROM person GROUP ALL"
).into(Stats)
```

```python title="Synchronous lazy builder"
# The sync builder never auto-executes - it has no __len__, __iter__ or
# __getitem__. Always call an accessor, including for fire-and-forget
# statements.
builder = db.query("SELECT * FROM users")   # nothing has run yet
people = builder.first()
print(len(people))

db.query("DELETE temp_data;").execute()
```

### `.query_raw()` {#query-raw}

Runs a set of SurrealQL statements and returns the raw RPC response, including per-statement results, statuses, and execution times. Unlike `.query()`, errors in individual statements are returned in the response rather than raised as exceptions.

```python title="Method Syntax"
db.query_raw(query, vars)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>query</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The SurrealQL query string to execute.</td>
        </tr>
        <tr>
            <td><code>vars</code> _(optional)_</td>
            <td><code>dict[str, <a href="/docs/reference/python/api/types/#value">Value</a>] | None</code></td>
            <td>Variables to bind into the query. Defaults to <code>None</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `dict[str, Any]` - the RPC envelope. Its `result` key holds one entry per statement, each with `status`, `time`, and `result`.

#### Examples

```python title="Synchronous"
raw = db.query_raw(
    "CREATE users SET name = $name; SELECT * FROM users;",
    {"name": "John"},
)

for statement in raw["result"]:
    print(statement["status"], statement["time"])
```

```python title="Asynchronous"
raw = await db.query_raw(
    "CREATE users SET name = $name; SELECT * FROM users;",
    {"name": "John"},
)
```

---

## CRUD methods

> [!IMPORTANT]
> **v3.0 builder pattern.** `.create()`, `.update()`, `.upsert()`, `.delete()`, and `.insert()` return an awaitable (async) or lazy (sync) builder. The builder exposes chainable clause methods that map directly to SurrealQL clauses:
>
> - `.content(data)` -> `... CONTENT $data`
> - `.replace(data)` -> `... REPLACE $data`
> - `.merge(data)`   -> `... MERGE $data`
> - `.patch(data)`   -> `... PATCH $data`
>
> Calling `.create(record, data)` is sugar for `.create(record).content(data)`. The standalone `.merge()`, `.patch()`, and `.insert_relation()` methods from v2.x have been removed.

### `.select()` {#select}

Selects all records in a table, or a specific record by its ID.

```python title="Method Syntax"
db.select(record)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>record</code> _(required)_</td>
            <td><code><a href="/docs/reference/python/api/types/#recordidtype">RecordIdType</a></code></td>
            <td>A table name (<code>str</code>) or a <code>RecordID</code> to select.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Value`](/docs/reference/python/api/types/#value)

#### Examples

```python title="Synchronous"
users = db.select("users")

user = db.select(RecordID("users", "john"))
```

```python title="Asynchronous"
users = await db.select("users")

user = await db.select(RecordID("users", "john"))
```

### `.create()` {#create}

Creates a record in a table. If a `RecordID` is passed, the record is created with that specific ID. If a table name is passed, a random ID is generated.

```python title="Method Syntax"
db.create(record, data)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>record</code> _(required)_</td>
            <td><code><a href="/docs/reference/python/api/types/#recordidtype">RecordIdType</a></code></td>
            <td>The table name or <code>RecordID</code> to create.</td>
        </tr>
        <tr>
            <td><code>data</code> _(optional)_</td>
            <td><code><a href="/docs/reference/python/api/types/#value">Value</a> | None</code></td>
            <td>The record data. Defaults to <code>None</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Value`](/docs/reference/python/api/types/#value)

#### Examples

```python title="Synchronous"
user = db.create("users", {
    "name": "John",
    "email": "john@example.com",
})

product = db.create(RecordID("products", "apple"), {
    "name": "Apple",
    "price": 1.50,
})
```

```python title="Asynchronous"
user = await db.create("users", {
    "name": "John",
    "email": "john@example.com",
})
```

### `.update()` {#update}

Replaces the entire record with the given data. Fields not present in `data` are removed.

```python title="Method Syntax"
db.update(record, data)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>record</code> _(required)_</td>
            <td><code><a href="/docs/reference/python/api/types/#recordidtype">RecordIdType</a></code></td>
            <td>The table name or <code>RecordID</code> to update.</td>
        </tr>
        <tr>
            <td><code>data</code> _(optional)_</td>
            <td><code><a href="/docs/reference/python/api/types/#value">Value</a> | None</code></td>
            <td>The new record data. Defaults to <code>None</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Value`](/docs/reference/python/api/types/#value)

#### Examples

```python title="Synchronous"
db.update(RecordID("users", "john"), {
    "name": "John Doe",
    "email": "john.doe@example.com",
})
```

```python title="Asynchronous"
await db.update(RecordID("users", "john"), {
    "name": "John Doe",
    "email": "john.doe@example.com",
})
```

### `.upsert()` {#upsert}

Updates an existing record or creates a new one if it does not exist. Replaces the entire record content.

```python title="Method Syntax"
db.upsert(record, data)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>record</code> _(required)_</td>
            <td><code><a href="/docs/reference/python/api/types/#recordidtype">RecordIdType</a></code></td>
            <td>The table name or <code>RecordID</code> to upsert.</td>
        </tr>
        <tr>
            <td><code>data</code> _(optional)_</td>
            <td><code><a href="/docs/reference/python/api/types/#value">Value</a> | None</code></td>
            <td>The record data. Defaults to <code>None</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Value`](/docs/reference/python/api/types/#value)

#### Examples

```python title="Synchronous"
db.upsert(RecordID("users", "john"), {
    "name": "John",
    "email": "john@example.com",
})
```

```python title="Asynchronous"
await db.upsert(RecordID("users", "john"), {
    "name": "John",
    "email": "john@example.com",
})
```

### `.merge` clause {#merge}

`.merge(data)` is a builder clause method - chain it on `.update()`, `.upsert()`, or `.create()`. It compiles to `... MERGE $data` and preserves any existing fields not present in `data`.

```python title="Synchronous"
db.update(RecordID("users", "john")).merge({"age": 32})
```

```python title="Asynchronous"
await db.update(RecordID("users", "john")).merge({"age": 32})
```

### `.patch` clause {#patch}

`.patch(data)` is a builder clause method - chain it on `.update()`, `.upsert()`, or `.create()`. It compiles to `... PATCH $data` and applies JSON Patch operations.

```python title="Synchronous"
db.update(RecordID("users", "john")).patch([
    {"op": "replace", "path": "/email", "value": "new@example.com"},
    {"op": "add", "path": "/verified", "value": True},
])
```

```python title="Asynchronous"
await db.update(RecordID("users", "john")).patch([
    {"op": "replace", "path": "/email", "value": "new@example.com"},
])
```

### `.delete()` {#delete}

Deletes all records in a table, or a specific record by its ID.

```python title="Method Syntax"
db.delete(record)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>record</code> _(required)_</td>
            <td><code><a href="/docs/reference/python/api/types/#recordidtype">RecordIdType</a></code></td>
            <td>The table name or <code>RecordID</code> to delete.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Value`](/docs/reference/python/api/types/#value)

#### Examples

```python title="Synchronous"
db.delete(RecordID("users", "john"))

db.delete("temp_data")
```

```python title="Asynchronous"
await db.delete(RecordID("users", "john"))
```

---

## Insert methods

### `.insert()` {#insert}

Inserts one or more records into a table.

```python title="Method Syntax"
db.insert(table, data)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>str | Table</code></td>
            <td>The table to insert into.</td>
        </tr>
        <tr>
            <td><code>data</code> _(required)_</td>
            <td><code><a href="/docs/reference/python/api/types/#value">Value</a></code></td>
            <td>A single record dict or a list of record dicts to insert.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Value`](/docs/reference/python/api/types/#value)

#### Examples

```python title="Synchronous"
db.insert("users", {"name": "Alice", "email": "alice@example.com"})

db.insert("users", [
    {"name": "Bob", "email": "bob@example.com"},
    {"name": "Charlie", "email": "charlie@example.com"},
])
```

```python title="Asynchronous"
await db.insert("users", {"name": "Alice", "email": "alice@example.com"})
```

### Inserting relations {#insert-relation}

The standalone `.insert_relation()` method from v2.x has been removed. Use `.insert(table, data, relation=True)` or chain `.relation()` on the insert builder to issue an `INSERT RELATION INTO` statement.

```python title="Synchronous"
db.insert("likes", {
    "in": RecordID("users", "alice"),
    "out": RecordID("posts", "post1"),
}, relation=True)

# Or via the builder:
db.insert("likes").relation().content({
    "in": RecordID("users", "alice"),
    "out": RecordID("posts", "post1"),
})
```

```python title="Asynchronous"
await db.insert("likes", {
    "in": RecordID("users", "alice"),
    "out": RecordID("posts", "post1"),
}, relation=True)
```

---

## Calling functions

### `.run()` {#run}

Calls a SurrealDB function and returns its result. The function name typically uses the `fn::` prefix for user-defined functions or namespace prefixes for built-ins.

```python title="Method Syntax"
db.run(name, args, version)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>name</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The fully-qualified function name, e.g. <code>"fn::increment"</code>.</td>
        </tr>
        <tr>
            <td><code>args</code> _(optional)_</td>
            <td><code>list[<a href="/docs/reference/python/api/types/#value">Value</a>] | None</code></td>
            <td>Positional arguments forwarded to the function.</td>
        </tr>
        <tr>
            <td><code>version</code> _(optional)_</td>
            <td><code>str | None</code></td>
            <td>Optional function version selector.</td>
        </tr>
    </tbody>
</table>

**Returns:** [`Value`](/docs/reference/python/api/types/#value)

#### Examples

```python title="Synchronous"
result = db.run("fn::increment", [1])
```

```python title="Asynchronous"
greeting = await db.run("fn::greet", ["world"])
```

---

## Live queries

> [!NOTE]
> Live queries require a WebSocket connection (`ws://` or `wss://`). HTTP and embedded connections raise `UnsupportedFeatureError`.

### `.live()` {#live}

Initiates a live query for a table. Returns a UUID that identifies the live query and can be passed to `.subscribe_live()` and `.kill()`.

```python title="Method Syntax"
db.live(table, diff)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table</code> _(required)_</td>
            <td><code>str | Table</code></td>
            <td>The table to watch for changes.</td>
        </tr>
        <tr>
            <td><code>diff</code> _(optional)_</td>
            <td><code>bool</code></td>
            <td>If <code>True</code>, notifications include JSON Patch diffs instead of full records. Defaults to <code>False</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `UUID`

#### Examples

```python title="Synchronous"
query_uuid = db.live("users")

query_uuid = db.live("users", diff=True)
```

```python title="Asynchronous"
query_uuid = await db.live("users")
```

### `.subscribe_live()` {#subscribe-live}

Returns a generator that yields live query notifications for the given query UUID. Each notification is a dict containing the action (`"CREATE"`, `"UPDATE"`, `"DELETE"`), the record data, and the record ID.

```python title="Method Syntax"
db.subscribe_live(query_uuid)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>query_uuid</code> _(required)_</td>
            <td><code>str | UUID</code></td>
            <td>The UUID of the live query returned by <a href="#live"><code>.live()</code></a>.</td>
        </tr>
    </tbody>
</table>

**Returns (sync):** `Generator[dict[str, Value], None, None]`
**Returns (async):** `AsyncGenerator[dict[str, Value], None]`

#### Examples

```python title="Synchronous"
query_uuid = db.live("users")

for notification in db.subscribe_live(query_uuid):
    print(notification["action"], notification["result"])
```

```python title="Asynchronous"
query_uuid = await db.live("users")

async for notification in db.subscribe_live(query_uuid):
    print(notification["action"], notification["result"])
```

### `.kill()` {#kill}

Terminates a running live query by its UUID.

```python title="Method Syntax"
db.kill(query_uuid)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>query_uuid</code> _(required)_</td>
            <td><code>str | UUID</code></td>
            <td>The UUID of the live query to kill.</td>
        </tr>
    </tbody>
</table>

**Returns:** `None`

#### Examples

```python title="Synchronous"
db.kill(query_uuid)
```

```python title="Asynchronous"
await db.kill(query_uuid)
```

---

## Sessions

> [!NOTE]
> Sessions require a WebSocket connection (`ws://` or `wss://`). HTTP and embedded connections raise `UnsupportedFeatureError`.

Sessions allow you to create isolated contexts on a single connection, each with its own namespace, database, variables, and authentication state.

### `.new_session()` {#new-session}

Creates a new isolated session on the current connection.

```python title="Method Syntax"
db.new_session()
```

**Returns (sync):** `BlockingSurrealSession`
**Returns (async):** `AsyncSurrealSession`

#### Examples

```python title="Synchronous"
session = db.new_session()
session.use("other_ns", "other_db")
result = session.select("users")
```

```python title="Asynchronous"
session = await db.new_session()
await session.use("other_ns", "other_db")
result = await session.select("users")
```

### `.attach()` {#attach}

Attaches to the server-side session associated with this connection and returns its session ID.

```python title="Method Syntax"
db.attach()
```

**Returns:** `UUID`

#### Examples

```python title="Synchronous"
session_id = db.attach()
```

```python title="Asynchronous"
session_id = await db.attach()
```

### `.detach()` {#detach}

Detaches from a server-side session.

```python title="Method Syntax"
db.detach(session_id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>session_id</code> _(required)_</td>
            <td><code>UUID</code></td>
            <td>The session ID to detach from.</td>
        </tr>
    </tbody>
</table>

**Returns:** `None`

#### Examples

```python title="Synchronous"
db.detach(session_id)
```

```python title="Asynchronous"
await db.detach(session_id)
```

---

## Transactions

> [!NOTE]
> Transactions require a WebSocket connection (`ws://` or `wss://`). HTTP and embedded connections raise `UnsupportedFeatureError`.

Transactions let you group multiple operations into an atomic unit. Changes are only applied when the transaction is committed, and can be rolled back with cancel.

### `.begin()` {#begin}

Begins a new transaction, optionally within a specific session.

```python title="Method Syntax"
db.begin(session_id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>session_id</code> _(optional)_</td>
            <td><code>UUID | None</code></td>
            <td>The session to start the transaction in. If <code>None</code>, uses the default session. Defaults to <code>None</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `UUID` - the transaction ID

#### Examples

```python title="Synchronous"
txn_id = db.begin()
```

```python title="Asynchronous"
txn_id = await db.begin()
```

```python title="Within a session"
session_id = db.attach()
txn_id = db.begin(session_id)
```

### `.commit()` {#commit}

Commits a transaction, applying all changes made within it.

```python title="Method Syntax"
db.commit(txn_id, session_id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>txn_id</code> _(required)_</td>
            <td><code>UUID</code></td>
            <td>The transaction ID returned by <a href="#begin"><code>.begin()</code></a>.</td>
        </tr>
        <tr>
            <td><code>session_id</code> _(optional)_</td>
            <td><code>UUID | None</code></td>
            <td>The session the transaction belongs to. Defaults to <code>None</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `None`

#### Examples

```python title="Synchronous"
txn_id = db.begin()
db.query("CREATE users SET name = 'Alice'").execute()
db.commit(txn_id)
```

```python title="Asynchronous"
txn_id = await db.begin()
await db.query("CREATE users SET name = 'Alice'").execute()
await db.commit(txn_id)
```

### `.cancel()` {#cancel}

Cancels a transaction, discarding all changes made within it.

```python title="Method Syntax"
db.cancel(txn_id, session_id)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>txn_id</code> _(required)_</td>
            <td><code>UUID</code></td>
            <td>The transaction ID returned by <a href="#begin"><code>.begin()</code></a>.</td>
        </tr>
        <tr>
            <td><code>session_id</code> _(optional)_</td>
            <td><code>UUID | None</code></td>
            <td>The session the transaction belongs to. Defaults to <code>None</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `None`

#### Examples

```python title="Synchronous"
txn_id = db.begin()
db.query("DELETE users").execute()
db.cancel(txn_id)
```

```python title="Asynchronous"
txn_id = await db.begin()
await db.query("DELETE users").execute()
await db.cancel(txn_id)
```

---

## Context manager

Both `Surreal` and `AsyncSurreal` support the context manager protocol. The connection is automatically opened on entry and closed on exit.

### Synchronous

```python title="Synchronous context manager"
from surrealdb import Surreal

with Surreal("ws://localhost:8000") as db:
    db.use("my_namespace", "my_database")
    db.signin({"username": "root", "password": "secret"})
    users = db.select("users")
```

### Asynchronous

```python title="Asynchronous context manager"
from surrealdb import AsyncSurreal

async with AsyncSurreal("ws://localhost:8000") as db:
    await db.use("my_namespace", "my_database")
    await db.signin({"username": "root", "password": "secret"})
    users = await db.select("users")
```

---

## Complete example

```python title="Full workflow (sync)"
from surrealdb import Surreal, RecordID

with Surreal("ws://localhost:8000") as db:
    db.use("shop", "inventory")
    db.signin({"username": "root", "password": "secret"})

    db.create("products", {"name": "Laptop", "price": 999.99})
    db.create("products", {"name": "Mouse", "price": 29.99})

    products = db.select("products")
    print("All products:", products)

    cheap = db.query(
        "SELECT * FROM products WHERE price < $max",
        {"max": 100},
    ).first()
    print("Affordable:", cheap)

    db.update(RecordID("products", products[0]["id"].id)).merge({"stock": 50})

    db.delete(RecordID("products", products[1]["id"].id))
```

```python title="Full workflow (async)"
import asyncio
from surrealdb import AsyncSurreal, RecordID

async def main():
    async with AsyncSurreal("ws://localhost:8000") as db:
        await db.use("shop", "inventory")
        await db.signin({"username": "root", "password": "secret"})

        await db.create("products", {"name": "Laptop", "price": 999.99})

        products = await db.select("products")
        print("Products:", products)

asyncio.run(main())
```

---

## See also

- [SurrealSession](/docs/reference/python/api/core/surreal-session.md) - Session management reference
- [SurrealTransaction](/docs/reference/python/api/core/surreal-transaction.md) - Transaction reference
- [Data types](/docs/reference/python/api/types.md) - Type aliases and value types
- [Errors](/docs/reference/python/api/errors.md) - Error classes reference
- [Connecting to SurrealDB](/docs/reference/python/concepts/connecting-to-surrealdb.md) - Connection protocols and patterns

---

Source: https://surrealdb.com/docs/reference/python/api/core/surreal-session

# SurrealSession

Isolated session for running queries with independent namespace, database, and authentication state.

A session wraps a WebSocket connection with an isolated session ID. Each session maintains its own namespace, database, variables, and authentication state, independent of other sessions on the same connection.

Sessions are created by calling [`.new_session()`](/docs/reference/python/api/core/surreal.md#new-session) on a WebSocket connection. They are **not** available on HTTP or embedded connections.

> [!NOTE]
> Sessions require a WebSocket connection (`ws://` or `wss://`). Attempting to create a session on an HTTP or embedded connection raises `UnsupportedFeatureError`.

**Source:** [`async_ws.py`](https://github.com/surrealdb/surrealdb.py/blob/main/src/surrealdb/connections/async_ws.py) · [`blocking_ws.py`](https://github.com/surrealdb/surrealdb.py/blob/main/src/surrealdb/connections/blocking_ws.py)

---

## Creating a session {#creating}

```python title="Method Syntax"
session = db.new_session()
```

**Returns (sync):** `BlockingSurrealSession`
**Returns (async):** `AsyncSurrealSession`

### Examples

```python title="Synchronous"
from surrealdb import Surreal

db = Surreal("ws://localhost:8000")
db.connect()
db.signin({"username": "root", "password": "secret"})

session = db.new_session()
session.use("my_namespace", "my_database")
```

```python title="Asynchronous"
from surrealdb import AsyncSurreal

db = AsyncSurreal("ws://localhost:8000")
await db.connect()
await db.signin({"username": "root", "password": "secret"})

session = await db.new_session()
await session.use("my_namespace", "my_database")
```

---

## Inherited methods {#inherited-methods}

A session exposes the same interface as the parent connection. All methods below delegate to the underlying connection, scoped to this session's ID. For full parameter tables and examples, see the [Surreal](/docs/reference/python/api/core/surreal.md) reference.

| Method | Returns | Description |
|---|---|---|
| [`.use(namespace, database)`](/docs/reference/python/api/core/surreal.md#use) | `None` | Switch namespace and database for this session. |
| [`.query(query, vars)`](/docs/reference/python/api/core/surreal.md#query) | Awaitable / lazy builder -> `list[Value]`, one entry per statement (`.execute()`), or the first statement's result (`.first()`) | Execute one or more SurrealQL statements. |
| [`.signin(vars)`](/docs/reference/python/api/core/surreal.md#signin) | [`Tokens`](/docs/reference/python/api/types/#tokens) | Sign in within this session. |
| [`.signup(vars)`](/docs/reference/python/api/core/surreal.md#signup) | [`Tokens`](/docs/reference/python/api/types/#tokens) | Sign up within this session. |
| [`.authenticate(token)`](/docs/reference/python/api/core/surreal.md#authenticate) | `None` | Authenticate this session with a JWT. |
| [`.invalidate()`](/docs/reference/python/api/core/surreal.md#invalidate) | `None` | Invalidate this session's authentication. |
| [`.let(key, value)`](/docs/reference/python/api/core/surreal.md#let) | `None` | Define a session-scoped variable. |
| [`.unset(key)`](/docs/reference/python/api/core/surreal.md#unset) | `None` | Remove a session-scoped variable. |
| [`.select(record)`](/docs/reference/python/api/core/surreal.md#select) | [`Value`](/docs/reference/python/api/types/#value) | Select records. |
| [`.create(record, data)`](/docs/reference/python/api/core/surreal.md#create) | CRUD builder -> `dict[str, Value]` | Create a record (chain `.content/.replace/.merge/.patch`). |
| [`.update(record, data)`](/docs/reference/python/api/core/surreal.md#update) | CRUD builder -> `dict` or `list` | Update records (chain `.content/.replace/.merge/.patch`). |
| [`.upsert(record, data)`](/docs/reference/python/api/core/surreal.md#upsert) | CRUD builder -> `dict` or `list` | Upsert a record (chain `.content/.replace/.merge/.patch`). |
| [`.delete(record)`](/docs/reference/python/api/core/surreal.md#delete) | CRUD builder -> `dict` or `list` | Delete records. |
| [`.insert(table, data, relation=False)`](/docs/reference/python/api/core/surreal.md#insert) | Insert builder -> `list[Value]` | Insert records. Pass `relation=True` or chain `.relation()` for `INSERT RELATION`. |
| [`.run(name, args, version)`](/docs/reference/python/api/core/surreal.md#run) | [`Value`](/docs/reference/python/api/types/#value) | Call a SurrealDB function. |
| [`.live(table, diff)`](/docs/reference/python/api/core/surreal.md#live) | `UUID` | Start a live query. |
| [`.kill(query_uuid)`](/docs/reference/python/api/core/surreal.md#kill) | `None` | Kill a live query. |

---

## Session-specific methods

### `.begin_transaction()` {#begin-transaction}

Begins a new transaction scoped to this session. Returns a transaction object that provides query and CRUD methods within the transaction boundary.

```python title="Method Syntax"
txn = session.begin_transaction()
```

**Returns (sync):** `BlockingSurrealTransaction`
**Returns (async):** `AsyncSurrealTransaction`

#### Examples

```python title="Synchronous"
session = db.new_session()
session.use("my_namespace", "my_database")

txn = session.begin_transaction()
txn.create("users", {"name": "Alice", "email": "alice@example.com"})
txn.create("users", {"name": "Bob", "email": "bob@example.com"})
txn.commit()
```

```python title="Asynchronous"
session = await db.new_session()
await session.use("my_namespace", "my_database")

txn = await session.begin_transaction()
await txn.create("users", {"name": "Alice", "email": "alice@example.com"})
await txn.create("users", {"name": "Bob", "email": "bob@example.com"})
await txn.commit()
```

### `.close_session()` {#close-session}

Closes this session on the server, releasing its session ID and any associated state. After calling this method, the session object should not be used.

```python title="Method Syntax"
session.close_session()
```

**Returns:** `None`

#### Examples

```python title="Synchronous"
session = db.new_session()
session.use("my_namespace", "my_database")
result = session.select("users")

session.close_session()
```

```python title="Asynchronous"
session = await db.new_session()
await session.use("my_namespace", "my_database")
result = await session.select("users")

await session.close_session()
```

---

## Complete example

```python title="Isolated sessions (sync)"
from surrealdb import Surreal, RecordID

with Surreal("ws://localhost:8000") as db:
    db.use("shop", "inventory")
    db.signin({"username": "root", "password": "secret"})

    # Session A works with the "shop" namespace
    session_a = db.new_session()
    session_a.use("shop", "inventory")
    session_a.create("products", {"name": "Laptop", "price": 999.99})

    # Session B works with a different namespace independently
    session_b = db.new_session()
    session_b.use("analytics", "events")
    session_b.create("page_views", {"page": "/products", "count": 1})

    # Each session has its own authentication state
    session_a.signin({"username": "root", "password": "secret"})
    products = session_a.select("products")
    print("Products:", products)

    # Transactions within a session
    txn = session_a.begin_transaction()
    txn.create("products", {"name": "Mouse", "price": 29.99})
    txn.create("products", {"name": "Keyboard", "price": 59.99})
    txn.commit()

    session_a.close_session()
    session_b.close_session()
```

```python title="Isolated sessions (async)"
import asyncio
from surrealdb import AsyncSurreal, RecordID

async def main():
    async with AsyncSurreal("ws://localhost:8000") as db:
        await db.use("shop", "inventory")
        await db.signin({"username": "root", "password": "secret"})

        session = await db.new_session()
        await session.use("shop", "inventory")
        await session.signin({"username": "root", "password": "secret"})

        await session.create("products", {"name": "Laptop", "price": 999.99})

        txn = await session.begin_transaction()
        await txn.create("products", {"name": "Mouse", "price": 29.99})
        await txn.create("products", {"name": "Keyboard", "price": 59.99})
        await txn.commit()

        products = await session.select("products")
        print("Products:", products)

        await session.close_session()

asyncio.run(main())
```

---

## See also

- [Surreal](/docs/reference/python/api/core/surreal.md) - Connection reference with full method documentation
- [SurrealTransaction](/docs/reference/python/api/core/surreal-transaction.md) - Transaction reference
- [Data types](/docs/reference/python/api/types.md) - Type aliases and value types
- [Errors](/docs/reference/python/api/errors.md) - Error classes reference

---

Source: https://surrealdb.com/docs/reference/python/api/core/surreal-transaction

# SurrealTransaction

Transaction scope for executing multiple operations atomically.

A transaction wraps a connection with both a session ID and a transaction ID, scoping all operations to a single atomic unit. Changes are applied only when the transaction is committed, and can be rolled back with cancel.

Transactions are created by calling [`.begin_transaction()`](/docs/reference/python/api/core/surreal-session.md#begin-transaction) on a session. They are **not** available on HTTP or embedded connections.

> [!NOTE]
> Transactions require a WebSocket connection (`ws://` or `wss://`). They must be created from a session via `.begin_transaction()`.

**Source:** [`async_ws.py`](https://github.com/surrealdb/surrealdb.py/blob/main/src/surrealdb/connections/async_ws.py) · [`blocking_ws.py`](https://github.com/surrealdb/surrealdb.py/blob/main/src/surrealdb/connections/blocking_ws.py)

---

## Creating a transaction {#creating}

```python title="Method Syntax"
txn = session.begin_transaction()
```

**Returns (sync):** `BlockingSurrealTransaction`
**Returns (async):** `AsyncSurrealTransaction`

### Examples

```python title="Synchronous"
from surrealdb import Surreal

db = Surreal("ws://localhost:8000")
db.connect()
db.signin({"username": "root", "password": "secret"})
db.use("my_namespace", "my_database")

session = db.new_session()
session.use("my_namespace", "my_database")

txn = session.begin_transaction()
txn.create("users", {"name": "Alice"})
txn.commit()
```

```python title="Asynchronous"
from surrealdb import AsyncSurreal

db = AsyncSurreal("ws://localhost:8000")
await db.connect()
await db.signin({"username": "root", "password": "secret"})
await db.use("my_namespace", "my_database")

session = await db.new_session()
await session.use("my_namespace", "my_database")

txn = await session.begin_transaction()
await txn.create("users", {"name": "Alice"})
await txn.commit()
```

---

## Inherited methods {#inherited-methods}

A transaction exposes query and CRUD methods that mirror the parent connection's interface. All operations are scoped to this transaction and are not visible outside it until committed. For full parameter tables and examples, see the [Surreal](/docs/reference/python/api/core/surreal.md) reference.

| Method | Returns | Description |
|---|---|---|
| [`.query(query, vars)`](/docs/reference/python/api/core/surreal.md#query) | Awaitable / lazy builder -> `list[Value]`, one entry per statement (`.execute()`), or the first statement's result (`.first()`) | Execute one or more SurrealQL statements within the transaction. |
| [`.select(record)`](/docs/reference/python/api/core/surreal.md#select) | [`Value`](/docs/reference/python/api/types/#value) | Select records. |
| [`.create(record, data)`](/docs/reference/python/api/core/surreal.md#create) | CRUD builder -> `dict[str, Value]` | Create a record (chain `.content/.replace/.merge/.patch`). |
| [`.update(record, data)`](/docs/reference/python/api/core/surreal.md#update) | CRUD builder -> `dict` or `list` | Update records (chain `.content/.replace/.merge/.patch`). |
| [`.upsert(record, data)`](/docs/reference/python/api/core/surreal.md#upsert) | CRUD builder -> `dict` or `list` | Upsert a record (chain `.content/.replace/.merge/.patch`). |
| [`.delete(record)`](/docs/reference/python/api/core/surreal.md#delete) | CRUD builder -> `dict` or `list` | Delete records. |
| [`.insert(table, data, relation=False)`](/docs/reference/python/api/core/surreal.md#insert) | Insert builder -> `list[Value]` | Insert records. Pass `relation=True` or chain `.relation()` for `INSERT RELATION`. |
| [`.run(name, args, version)`](/docs/reference/python/api/core/surreal.md#run) | [`Value`](/docs/reference/python/api/types/#value) | Call a SurrealDB function within the transaction. |
| [`.let(key, value)`](/docs/reference/python/api/core/surreal.md#let) | `None` | Set a transaction-scoped variable. |
| [`.unset(key)`](/docs/reference/python/api/core/surreal.md#unset) | `None` | Unset a transaction-scoped variable. |

---

## Transaction-specific methods

### `.commit()` {#commit}

Commits the transaction, applying all changes made within it to the database. After committing, the transaction object should not be used.

```python title="Method Syntax"
txn.commit()
```

**Returns:** `None`

#### Examples

```python title="Synchronous"
txn = session.begin_transaction()
txn.create("users", {"name": "Alice", "email": "alice@example.com"})
txn.create("users", {"name": "Bob", "email": "bob@example.com"})
txn.commit()
```

```python title="Asynchronous"
txn = await session.begin_transaction()
await txn.create("users", {"name": "Alice", "email": "alice@example.com"})
await txn.create("users", {"name": "Bob", "email": "bob@example.com"})
await txn.commit()
```

### `.cancel()` {#cancel}

Cancels the transaction, discarding all changes made within it. No data is written to the database. After cancelling, the transaction object should not be used.

```python title="Method Syntax"
txn.cancel()
```

**Returns:** `None`

#### Examples

```python title="Synchronous"
txn = session.begin_transaction()
txn.delete("users")
txn.cancel()
```

```python title="Asynchronous"
txn = await session.begin_transaction()
await txn.delete("users")
await txn.cancel()
```

```python title="Rollback on error"
txn = session.begin_transaction()
try:
    txn.create("orders", {"product": "Laptop", "qty": 1})
    txn.update(RecordID("inventory", "laptop"), {"stock": -1})
    txn.commit()
except Exception:
    txn.cancel()
```

---

## Complete example

```python title="Atomic transfer (sync)"
from surrealdb import Surreal, RecordID

with Surreal("ws://localhost:8000") as db:
    db.use("bank", "ledger")
    db.signin({"username": "root", "password": "secret"})

    session = db.new_session()
    session.use("bank", "ledger")

    session.create(RecordID("accounts", "alice"), {"balance": 1000})
    session.create(RecordID("accounts", "bob"), {"balance": 500})

    txn = session.begin_transaction()
    try:
        txn.query(
            "UPDATE accounts:alice SET balance = balance - $amount",
            {"amount": 200},
        ).execute()
        txn.query(
            "UPDATE accounts:bob SET balance = balance + $amount",
            {"amount": 200},
        ).execute()
        txn.commit()
        print("Transfer committed")
    except Exception:
        txn.cancel()
        print("Transfer rolled back")

    accounts = session.select("accounts")
    print("Accounts:", accounts)

    session.close_session()
```

```python title="Atomic transfer (async)"
import asyncio
from surrealdb import AsyncSurreal, RecordID

async def main():
    async with AsyncSurreal("ws://localhost:8000") as db:
        await db.use("bank", "ledger")
        await db.signin({"username": "root", "password": "secret"})

        session = await db.new_session()
        await session.use("bank", "ledger")

        await session.create(RecordID("accounts", "alice"), {"balance": 1000})
        await session.create(RecordID("accounts", "bob"), {"balance": 500})

        txn = await session.begin_transaction()
        try:
            await txn.query(
                "UPDATE accounts:alice SET balance = balance - $amount",
                {"amount": 200},
            ).execute()
            await txn.query(
                "UPDATE accounts:bob SET balance = balance + $amount",
                {"amount": 200},
            ).execute()
            await txn.commit()
            print("Transfer committed")
        except Exception:
            await txn.cancel()
            print("Transfer rolled back")

        accounts = await session.select("accounts")
        print("Accounts:", accounts)

        await session.close_session()

asyncio.run(main())
```

---

## See also

- [SurrealSession](/docs/reference/python/api/core/surreal-session.md) - Session management reference
- [Surreal](/docs/reference/python/api/core/surreal.md) - Connection reference with full method documentation
- [Data types](/docs/reference/python/api/types.md) - Type aliases and value types
- [Errors](/docs/reference/python/api/errors.md) - Error classes reference

---

Source: https://surrealdb.com/docs/reference/python/api/errors

# Errors

Error classes for handling different types of failures in the Python SDK.

The SDK defines specific error classes for different failure scenarios. All error classes extend the base `SurrealError` class, allowing you to catch and handle specific error types with `isinstance` checks.

**Source:** [errors.py](https://github.com/surrealdb/surrealdb.py/blob/main/src/surrealdb/errors.py)

## Base error

### `SurrealError` {#surrealerror}

Base class for every error raised by the SurrealDB Python SDK. Extends Python's built-in `Exception`.

```python
from surrealdb import SurrealError

try:
    result = db.select("users")
except SurrealError as e:
    print("SDK error:", e)
```

## Server errors

Server errors originate from the SurrealDB server and carry structured information about the failure. All server errors extend `ServerError`.

### `ServerError` {#servererror}

**Extends:** [`SurrealError`](#surrealerror)

Error received from the SurrealDB server with structured kind, details, and cause chain.

#### Properties

<table>
    <thead>
        <tr>
            <th>Property</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>kind</code></td>
            <td><code>str</code></td>
            <td>The structured error kind (e.g. <code>"NotAllowed"</code>, <code>"NotFound"</code>). Match against <a href="#errorkind"><code>ErrorKind</code></a> constants.</td>
        </tr>
        <tr>
            <td><code>code</code></td>
            <td><code>int</code></td>
            <td>Legacy JSON-RPC error code. <code>0</code> when unavailable.</td>
        </tr>
        <tr>
            <td><code>details</code></td>
            <td><code>dict[str, Any] | None</code></td>
            <td>Kind-specific structured details. <code>None</code> when not provided.</td>
        </tr>
        <tr>
            <td><code>server_cause</code></td>
            <td><code>ServerError | None</code></td>
            <td>The underlying server error in the cause chain, if any.</td>
        </tr>
    </tbody>
</table>

#### Methods

#### `.has_kind()` {#has-kind}

Check if this error or any cause in the chain matches the given kind.

```python
has_kind(kind: str) -> bool
```

```python
from surrealdb import ErrorKind

try:
    db.query("INVALID").execute()
except ServerError as e:
    if e.has_kind(ErrorKind.VALIDATION):
        print("Validation error")
```

---

#### `.find_cause()` {#find-cause}

Find the first error in the cause chain matching the given kind.

```python
find_cause(kind: str) -> ServerError | None
```

```python
try:
    db.signin({"username": "user", "password": "wrong"})
except ServerError as e:
    auth_cause = e.find_cause(ErrorKind.NOT_ALLOWED)
    if auth_cause:
        print("Auth failure:", auth_cause)
```

---

### `ValidationError` {#validationerror}

**Extends:** [`ServerError`](#servererror)

Validation failure such as parse errors, invalid request parameters, or bad input.

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `is_parse_error` | `bool` | Whether this is a query parse error |
| `parameter_name` | `str \| None` | The invalid parameter name, if applicable |

---

### `ConfigurationError` {#configurationerror}

**Extends:** [`ServerError`](#servererror)

Feature or configuration not supported, such as live queries or GraphQL.

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `is_live_query_not_supported` | `bool` | Whether live queries are not supported |

---

### `ThrownError` {#thrownerror}

**Extends:** [`ServerError`](#servererror)

User-thrown error via the `THROW` statement in SurrealQL.

---

### `QueryError` {#queryerror}

**Extends:** [`ServerError`](#servererror)

Query execution failure such as timeout, cancellation, or not executed.

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `is_not_executed` | `bool` | Whether the query was not executed |
| `is_timed_out` | `bool` | Whether the query timed out |
| `is_cancelled` | `bool` | Whether the query was cancelled |
| `timeout` | `dict[str, Any] \| None` | The timeout duration as `{"secs": ..., "nanos": ...}` |

---

### `SerializationError` {#serializationerror}

**Extends:** [`ServerError`](#servererror)

Serialisation or deserialisation failure.

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `is_deserialization` | `bool` | Whether this is a deserialisation error |

---

### `NotAllowedError` {#notallowederror}

**Extends:** [`ServerError`](#servererror)

Permission denied, method not allowed, or function/scripting blocked.

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `is_token_expired` | `bool` | Whether the authentication token has expired |
| `is_invalid_auth` | `bool` | Whether the authentication credentials are invalid |
| `is_scripting_blocked` | `bool` | Whether scripting is blocked |
| `method_name` | `str \| None` | The disallowed method name |
| `function_name` | `str \| None` | The disallowed function name |
| `target_name` | `str \| None` | The disallowed target name |

```python
from surrealdb import NotAllowedError

try:
    db.signin({"username": "user", "password": "wrong"})
except NotAllowedError as e:
    if e.is_token_expired:
        print("Token expired, re-authenticate")
    elif e.is_invalid_auth:
        print("Invalid credentials")
```

---

### `NotFoundError` {#notfounderror}

**Extends:** [`ServerError`](#servererror)

Resource not found such as table, record, namespace, or method.

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `table_name` | `str \| None` | The missing table name |
| `record_id` | `str \| None` | The missing record ID |
| `method_name` | `str \| None` | The missing method name |
| `namespace_name` | `str \| None` | The missing namespace name |
| `database_name` | `str \| None` | The missing database name |
| `session_id` | `str \| None` | The missing session ID |

---

### `AlreadyExistsError` {#alreadyexistserror}

**Extends:** [`ServerError`](#servererror)

Duplicate resource such as record, table, or namespace.

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `record_id` | `str \| None` | The duplicate record ID |
| `table_name` | `str \| None` | The duplicate table name |
| `session_id` | `str \| None` | The duplicate session ID |
| `namespace_name` | `str \| None` | The duplicate namespace name |
| `database_name` | `str \| None` | The duplicate database name |

---

### `InternalError` {#internalerror}

**Extends:** [`ServerError`](#servererror)

Internal or unexpected server error. Used as a fallback when the error kind is not recognized.

## SDK-side Errors

These errors originate from the SDK itself, not the server.

### `ConnectionUnavailableError` {#connectionunavailableerror}

**Extends:** [`SurrealError`](#surrealerror)

Thrown when attempting an operation without an active connection.

```python
from surrealdb import Surreal, ConnectionUnavailableError

db = Surreal("ws://localhost:8000")

try:
    db.select("users")
except ConnectionUnavailableError:
    print("Not connected to database")
```

---

### `UnsupportedEngineError` {#unsupportedengineerror}

**Extends:** [`SurrealError`](#surrealerror)

Thrown when the URL protocol is not supported.

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `url` | `str` | The unsupported URL |

```python
from surrealdb import Surreal, UnsupportedEngineError

try:
    db = Surreal("ftp://localhost:8000")
except UnsupportedEngineError as e:
    print(f"Unsupported protocol: {e.url}")
```

---

### `UnsupportedFeatureError` {#unsupportedfeatureerror}

**Extends:** [`SurrealError`](#surrealerror)

Thrown when a feature is not supported by the current connection type. For example, sessions and transactions require a WebSocket connection.

```python
from surrealdb import AsyncSurreal, UnsupportedFeatureError

db = AsyncSurreal("http://localhost:8000")
await db.connect()

try:
    session = await db.new_session()
except UnsupportedFeatureError:
    print("Sessions require a WebSocket connection")
```

---

### `UnexpectedResponseError` {#unexpectedresponseerror}

**Extends:** [`SurrealError`](#surrealerror)

Thrown when the server returns an unexpected response format.

---

### `InvalidRecordIdError` {#invalidrecordiderror}

**Extends:** [`SurrealError`](#surrealerror)

Thrown when a `RecordID` string could not be parsed.

---

### `InvalidDurationError` {#invaliddurationerror}

**Extends:** [`SurrealError`](#surrealerror)

Thrown when a `Duration` string could not be parsed.

---

### `InvalidGeometryError` {#invalidgeometryerror}

**Extends:** [`SurrealError`](#surrealerror)

Thrown when geometry data is invalid.

---

### `InvalidTableError` {#invalidtableerror}

**Extends:** [`SurrealError`](#surrealerror)

Thrown when a table or record ID string is invalid.

## Error kind constants

### `ErrorKind` {#errorkind}

Known error kinds returned by the SurrealDB server. Use these constants for matching against `ServerError.kind`.

```python
from surrealdb import ErrorKind
```

| Constant | Value |
|----------|-------|
| `ErrorKind.VALIDATION` | `"Validation"` |
| `ErrorKind.CONFIGURATION` | `"Configuration"` |
| `ErrorKind.THROWN` | `"Thrown"` |
| `ErrorKind.QUERY` | `"Query"` |
| `ErrorKind.SERIALIZATION` | `"Serialization"` |
| `ErrorKind.NOT_ALLOWED` | `"NotAllowed"` |
| `ErrorKind.NOT_FOUND` | `"NotFound"` |
| `ErrorKind.ALREADY_EXISTS` | `"AlreadyExists"` |
| `ErrorKind.CONNECTION` | `"Connection"` |
| `ErrorKind.INTERNAL` | `"Internal"` |

### Detail kind constants {#detail-kind-constants}

Each server error kind has associated detail kind constants for more specific matching.

| Class | Constants |
|-------|-----------|
| `AuthDetailKind` | `TOKEN_EXPIRED`, `SESSION_EXPIRED`, `INVALID_AUTH`, `UNEXPECTED_AUTH`, `MISSING_USER_OR_PASS`, `NO_SIGNIN_TARGET`, `INVALID_PASS`, `TOKEN_MAKING_FAILED`, `INVALID_SIGNUP`, `INVALID_ROLE`, `NOT_ALLOWED` |
| `ValidationDetailKind` | `PARSE`, `INVALID_REQUEST`, `INVALID_PARAMS`, `NAMESPACE_EMPTY`, `DATABASE_EMPTY`, `INVALID_PARAMETER`, `INVALID_CONTENT`, `INVALID_MERGE` |
| `ConfigurationDetailKind` | `LIVE_QUERY_NOT_SUPPORTED`, `BAD_LIVE_QUERY_CONFIG`, `BAD_GRAPHQL_CONFIG` |
| `QueryDetailKind` | `NOT_EXECUTED`, `TIMED_OUT`, `CANCELLED` |
| `SerializationDetailKind` | `SERIALIZATION`, `DESERIALIZATION` |
| `NotAllowedDetailKind` | `SCRIPTING`, `AUTH`, `METHOD`, `FUNCTION`, `TARGET` |
| `NotFoundDetailKind` | `METHOD`, `SESSION`, `TABLE`, `RECORD`, `NAMESPACE`, `DATABASE`, `TRANSACTION` |
| `AlreadyExistsDetailKind` | `SESSION`, `TABLE`, `RECORD`, `NAMESPACE`, `DATABASE` |
| `ConnectionDetailKind` | `UNINITIALISED`, `ALREADY_CONNECTED` |

## See also

- [Error handling concept](/docs/reference/python/concepts/error-handling.md) for patterns and best practices
- [Surreal API reference](/docs/reference/python/api/core/surreal.md) for methods that may throw errors
- [Python types](/docs/reference/python/api/types/) for the `Value` and `Tokens` types

---

Source: https://surrealdb.com/docs/reference/python/api/types

# Python types

Type definitions and dataclasses used throughout the Python SDK.

The SDK provides type definitions and dataclasses for type-safe development. This page documents the key types used throughout the SDK.

**Source:** [types.py](https://github.com/surrealdb/surrealdb.py/blob/main/src/surrealdb/types.py), [models.py](https://github.com/surrealdb/surrealdb.py/blob/main/src/surrealdb/data/models.py), [url.py](https://github.com/surrealdb/surrealdb.py/blob/main/src/surrealdb/connections/url.py)

## Value types

### `Value` {#value}

Union type representing all possible values that can be sent to or returned from SurrealDB.

```python
Value = (
    str | int | float | bool | None | bytes | UUID | Decimal
    | Table | Range | RecordID | Duration | Datetime
    | GeometryPoint | GeometryLine | GeometryPolygon
    | GeometryMultiPoint | GeometryMultiLine | GeometryMultiPolygon
    | GeometryCollection
    | dict[str, "Value"] | list["Value"]
)
```

This type is recursive: dictionaries and lists can contain nested `Value` types. See the [Data types overview](/docs/reference/python/api/values/) for details on each SurrealDB-specific type.

---

### `RecordIdType` {#recordidtype}

Type alias for values accepted as record or table references by CRUD methods.

```python
RecordIdType = str | Table | RecordID
```

When a `str` is passed, it is treated as a table name. Pass a [`RecordID`](/docs/reference/python/api/values/record-id.md) for specific record targeting, or a [`Table`](/docs/reference/python/api/values/table.md) for explicit table references.

## Authentication types

### `Tokens` {#tokens}

Frozen dataclass returned by `.signin()` and `.signup()`. Contains the access token and an optional refresh token.

```python
@dataclass(frozen=True)
class Tokens:
    access: str | None = None
    refresh: str | None = None
```

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `access` | `str \| None` | The JWT access token |
| `refresh` | `str \| None` | The refresh token, if the access method was defined with `WITH REFRESH` |

**Example:**

```python
from surrealdb import Surreal

db = Surreal("ws://localhost:8000")
db.connect()
db.use("main", "main")

tokens = db.signin({"username": "root", "password": "secret"})
print(tokens.access)
print(tokens.refresh)
```

## Data model types

### `Patch` {#patch}

Dataclass representing a JSON Patch operation per the [JSON Patch specification](https://jsonpatch.com/).

```python
@dataclass
class Patch:
    op: str
    path: str
    value: Any
```

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `op` | `str` | The patch operation: `"add"`, `"remove"`, `"replace"`, `"move"`, `"copy"`, or `"test"` |
| `path` | `str` | The JSON pointer path |
| `value` | `Any` | The value for the operation |

---

### `QueryResponse` {#queryresponse}

Frozen dataclass representing an HTTP query response.

```python
@dataclass(frozen=True)
class QueryResponse:
    time: str
    status: str
    result: list[dict[str, Any]]
```

**Properties:**

| Property | Type | Description |
|----------|------|-------------|
| `time` | `str` | The time the request was processed |
| `status` | `str` | The status of the request |
| `result` | `list[dict[str, Any]]` | The query results |

## Connection types

### `UrlScheme` {#urlscheme}

Enum of supported connection URL schemes. The SDK uses this internally to select the appropriate connection class.

```python
class UrlScheme(Enum):
    HTTP = "http"
    HTTPS = "https"
    WS = "ws"
    WSS = "wss"
    MEM = "mem"
    FILE = "file"
    MEMORY = "memory"
    SURREALKV = "surrealkv"
```

| Scheme | Connection type | Description |
|--------|----------------|-------------|
| `HTTP`, `HTTPS` | HTTP | Short-lived stateless connections |
| `WS`, `WSS` | WebSocket | Long-lived stateful connections with full feature support |
| `MEM`, `MEMORY` | Embedded | In-memory database |
| `FILE` | Embedded | File-based persistent database |
| `SURREALKV` | Embedded | SurrealKV persistent database |

## See also

- [Data types](/docs/reference/python/api/values/) for the SurrealDB-specific type classes
- [Errors](/docs/reference/python/api/errors/) for error classes and constants
- [Surreal API reference](/docs/reference/python/api/core/surreal.md) for methods using these types

---

Source: https://surrealdb.com/docs/reference/python/api/values

# Python values

Custom data types for representing SurrealDB values in Python.

The Python SDK provides custom classes that map SurrealDB types to Python. These classes are used throughout the SDK for parameters and return values. Standard Python types like `str`, `int`, `float`, `bool`, `None`, `bytes`, `dict`, and `list` are also used directly where they map naturally to SurrealDB types.

## `Value` type {#value}

The `Value` union type represents any value that can be sent to or received from SurrealDB.

```python title="Type Definition"
Value = (
    str | int | float | bool | None | bytes | UUID | Decimal
    | Table | Range | RecordID | Duration | Datetime
    | GeometryPoint | GeometryLine | GeometryPolygon
    | GeometryMultiPoint | GeometryMultiLine | GeometryMultiPolygon
    | GeometryCollection | dict[str, "Value"] | list["Value"]
)
```

## Type mapping {#type-mapping}

| Python Type | SurrealDB Type | Notes |
|---|---|---|
| `str` | `string` | |
| `int` | `int` | |
| `float` | `float` | |
| `bool` | `bool` | |
| `None` | `NONE` / `NULL` | |
| `bytes` | `bytes` | |
| `UUID` | `uuid` | From `uuid` standard library |
| `Decimal` | `decimal` | From `decimal` standard library |
| `dict` | `object` | Keys must be strings |
| `list` | `array` | |
| [`RecordID`](/docs/reference/python/api/values/record-id.md) | `record` | Table name + identifier |
| [`Table`](/docs/reference/python/api/values/table.md) | table reference | Table name wrapper |
| [`Duration`](/docs/reference/python/api/values/duration.md) | `duration` | Nanosecond precision |
| [`Datetime`](/docs/reference/python/api/values/datetime.md) | `datetime` | ISO 8601 string |
| [`Range`](/docs/reference/python/api/values/range.md) | `range` | Inclusive/exclusive bounds |
| [`Geometry*`](/docs/reference/python/api/values/geometry.md) | `geometry` | GeoJSON-compatible types |

## `RecordIdType` type {#recordidtype}

Many SDK methods accept a `RecordIdType`, which allows passing a table name, a `Table` object, or a `RecordID`.

```python title="Type Definition"
RecordIdType = str | Table | RecordID
```

## `Tokens` type {#tokens}

Authentication methods return a `Tokens` string alias.

```python title="Type Definition"
Tokens = str
```

## Custom types

- [`RecordID`](/docs/reference/python/api/values/record-id.md) - Record identifier with table name and ID components
- [`Table`](/docs/reference/python/api/values/table.md) - Table name wrapper for type-safe references
- [`Datetime`](/docs/reference/python/api/values/datetime.md) - Datetime wrapper for SurrealDB datetime values
- [`Duration`](/docs/reference/python/api/values/duration.md) - Duration with nanosecond precision and unit conversion
- [`Range`](/docs/reference/python/api/values/range.md) - Range type with inclusive and exclusive bounds
- [`Geometry`](/docs/reference/python/api/values/geometry.md) - GeoJSON-compatible geometry types for spatial data

---

## See also

- [Surreal](/docs/reference/python/api/core/surreal.md) - Connection and query methods
- [Errors](/docs/reference/python/api/errors.md) - Error classes reference

---

Source: https://surrealdb.com/docs/reference/python/api/values/datetime

# Datetime

Datetime wrapper for SurrealDB datetime values.

A `Datetime` wraps an ISO 8601 datetime string for use with SurrealDB's `datetime` type. It preserves the original string representation through serialisation and deserialisation.

```python title="Import"
from surrealdb import Datetime
```

---

## Constructor {#constructor}

```python title="Syntax"
Datetime(dt)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>dt</code> _(required)_</td>
            <td><code>str</code></td>
            <td>An ISO 8601 datetime string.</td>
        </tr>
    </tbody>
</table>

### Examples

```python
dt = Datetime("2025-01-15T10:30:00Z")
```

```python
dt = Datetime("2025-06-01T14:00:00.000+02:00")
```

---

## Properties {#properties}

| Property | Type | Description |
|---|---|---|
| `dt` | `str` | The ISO 8601 datetime string. |

```python
dt = Datetime("2025-01-15T10:30:00Z")
print(dt.dt)  # "2025-01-15T10:30:00Z"
```

---

## Usage {#usage}

```python
from surrealdb import Surreal, RecordID, Datetime

db = Surreal("ws://localhost:8000")
db.connect()
db.use("my_ns", "my_db")
db.signin({"username": "root", "password": "secret"})

db.create("events", {
    "title": "Launch",
    "scheduled_at": Datetime("2025-06-01T09:00:00Z"),
})
```

---

## See also

- [Data types](/docs/reference/python/api/values.md), All SDK data types
- [Duration](/docs/reference/python/api/values/duration.md), Duration type with unit conversion

---

Source: https://surrealdb.com/docs/reference/python/api/values/duration

# Duration

Duration type with nanosecond precision and unit conversion properties.

A `Duration` stores a time duration with nanosecond precision. It supports parsing from human-readable strings (including compound formats like `"1h30m"`) and provides properties for converting to common time units.

`Duration` is a Python dataclass.

```python title="Import"
from surrealdb import Duration
```

---

## Fields {#fields}

| Field | Type | Description |
|---|---|---|
| `elapsed` | `int` | The duration in nanoseconds. |

---

## Constructor {#constructor}

```python title="Syntax"
Duration(elapsed)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>elapsed</code> _(required)_</td>
            <td><code>int</code></td>
            <td>The duration in nanoseconds.</td>
        </tr>
    </tbody>
</table>

```python
d = Duration(5_000_000_000)
print(d.seconds)  # 5.0
```

---

## Static methods {#static-methods}

### `Duration.parse()` {#parse}

Parses a duration from a string or integer value. String values support SurrealDB duration syntax, including compound durations.

```python title="Syntax"
Duration.parse(value, nanoseconds=0)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>value</code> _(required)_</td>
            <td><code>str | int</code></td>
            <td>A duration string (e.g. <code>"1h30m"</code>, <code>"500ms"</code>) or an integer in nanoseconds.</td>
        </tr>
        <tr>
            <td><code>nanoseconds</code> _(optional)_</td>
            <td><code>int</code></td>
            <td>Additional nanoseconds to add. Defaults to <code>0</code>.</td>
        </tr>
    </tbody>
</table>

**Returns:** `Duration`

### Examples

```python
d = Duration.parse("1h30m")
print(d.minutes)  # 90.0

d = Duration.parse("500ms")
print(d.milliseconds)  # 500.0

d = Duration.parse("2d12h")
print(d.hours)  # 60.0

d = Duration.parse(1_000_000_000)
print(d.seconds)  # 1.0
```

---

## Properties {#properties}

All unit properties return a `float`.

| Property | Type | Description |
|---|---|---|
| `nanoseconds` | `float` | Duration in nanoseconds. |
| `microseconds` | `float` | Duration in microseconds. |
| `milliseconds` | `float` | Duration in milliseconds. |
| `seconds` | `float` | Duration in seconds. |
| `minutes` | `float` | Duration in minutes. |
| `hours` | `float` | Duration in hours. |
| `days` | `float` | Duration in days. |
| `weeks` | `float` | Duration in weeks. |
| `years` | `float` | Duration in years (365-day). |

```python
d = Duration.parse("2h30m")
print(d.hours)    # 2.5
print(d.minutes)  # 150.0
print(d.seconds)  # 9000.0
```

---

## Methods {#methods}

### `to_string()` {#to-string}

Returns the duration as a human-readable string.

```python title="Syntax"
duration.to_string()
```

**Returns:** `str`

```python
d = Duration.parse("1h30m")
print(d.to_string())  # "1h30m"
```

### `to_compact()` {#to-compact}

Returns the duration as a compact list of integer values.

```python title="Syntax"
duration.to_compact()
```

**Returns:** `list[int]`

```python
d = Duration.parse("1h30m")
print(d.to_compact())
```

---

## Usage {#usage}

```python
from surrealdb import Surreal, Duration

db = Surreal("ws://localhost:8000")
db.connect()
db.use("my_ns", "my_db")
db.signin({"username": "root", "password": "secret"})

db.create("tasks", {
    "title": "Backup",
    "interval": Duration.parse("6h"),
})
```

---

## See also

- [Data types](/docs/reference/python/api/values.md) - All SDK data types
- [Datetime](/docs/reference/python/api/values/datetime.md) - Datetime wrapper

---

Source: https://surrealdb.com/docs/reference/python/api/values/geometry

# Geometry

GeoJSON-compatible geometry types for spatial data.

The SDK provides GeoJSON-compatible geometry types for working with SurrealDB's spatial data. All geometry classes extend the `Geometry` base class.

```python title="Import"
from surrealdb import (
    GeometryPoint,
    GeometryLine,
    GeometryPolygon,
    GeometryMultiPoint,
    GeometryMultiLine,
    GeometryMultiPolygon,
    GeometryCollection,
)
```

Individual types can also be imported from `surrealdb.data.types.geometry`.

---

## `GeometryPoint` {#geometrypoint}

A single geographic point defined by longitude and latitude.

### Constructor {#point-constructor}

```python title="Syntax"
GeometryPoint(longitude, latitude)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>longitude</code> _(required)_</td>
            <td><code>float</code></td>
            <td>The longitude coordinate.</td>
        </tr>
        <tr>
            <td><code>latitude</code> _(required)_</td>
            <td><code>float</code></td>
            <td>The latitude coordinate.</td>
        </tr>
    </tbody>
</table>

### Examples

```python
point = GeometryPoint(-0.1278, 51.5074)
```

---

## `GeometryLine` {#geometryline}

A line defined by two or more points.

### Constructor {#line-constructor}

```python title="Syntax"
GeometryLine(points)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>points</code> _(required)_</td>
            <td><code>list[GeometryPoint]</code></td>
            <td>An ordered list of points that define the line.</td>
        </tr>
    </tbody>
</table>

### Examples

```python
line = GeometryLine([
    GeometryPoint(-0.1278, 51.5074),
    GeometryPoint(-3.1883, 55.9533),
])
```

---

## `GeometryPolygon` {#geometrypolygon}

A polygon defined by one or more linear rings. The first ring is the exterior boundary; any subsequent rings are interior holes. Rings must be closed - the first and last point must be identical, following the GeoJSON specification.

### Constructor {#polygon-constructor}

```python title="Syntax"
GeometryPolygon(rings)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>rings</code> _(required)_</td>
            <td><code>list[GeometryLine]</code></td>
            <td>A list of linear rings. The first is the exterior ring; others are holes.</td>
        </tr>
    </tbody>
</table>

### Examples

```python
polygon = GeometryPolygon([
    GeometryLine([
        GeometryPoint(0.0, 0.0),
        GeometryPoint(1.0, 0.0),
        GeometryPoint(1.0, 1.0),
        GeometryPoint(0.0, 1.0),
        GeometryPoint(0.0, 0.0),
    ]),
])
```

---

## `GeometryMultiPoint` {#geometrymultipoint}

A collection of points.

### Constructor {#multipoint-constructor}

```python title="Syntax"
GeometryMultiPoint(points)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>points</code> _(required)_</td>
            <td><code>list[GeometryPoint]</code></td>
            <td>A list of points.</td>
        </tr>
    </tbody>
</table>

### Examples

```python
multi_point = GeometryMultiPoint([
    GeometryPoint(-0.1278, 51.5074),
    GeometryPoint(-3.1883, 55.9533),
    GeometryPoint(-1.8904, 52.4862),
])
```

---

## `GeometryMultiLine` {#geometrymultiline}

A collection of lines.

### Constructor {#multiline-constructor}

```python title="Syntax"
GeometryMultiLine(lines)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>lines</code> _(required)_</td>
            <td><code>list[GeometryLine]</code></td>
            <td>A list of lines.</td>
        </tr>
    </tbody>
</table>

### Examples

```python
multi_line = GeometryMultiLine([
    GeometryLine([
        GeometryPoint(0.0, 0.0),
        GeometryPoint(1.0, 1.0),
    ]),
    GeometryLine([
        GeometryPoint(2.0, 2.0),
        GeometryPoint(3.0, 3.0),
    ]),
])
```

---

## `GeometryMultiPolygon` {#geometrymultipolygon}

A collection of polygons.

### Constructor {#multipolygon-constructor}

```python title="Syntax"
GeometryMultiPolygon(polygons)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>polygons</code> _(required)_</td>
            <td><code>list[GeometryPolygon]</code></td>
            <td>A list of polygons.</td>
        </tr>
    </tbody>
</table>

### Examples

```python
multi_polygon = GeometryMultiPolygon([
    GeometryPolygon([
        GeometryLine([
            GeometryPoint(0.0, 0.0),
            GeometryPoint(1.0, 0.0),
            GeometryPoint(1.0, 1.0),
            GeometryPoint(0.0, 1.0),
            GeometryPoint(0.0, 0.0),
        ]),
    ]),
])
```

---

## `GeometryCollection` {#geometrycollection}

A heterogeneous collection of geometry objects. Unlike the other multi-types, a `GeometryCollection` can contain a mix of different geometry types.

### Constructor {#collection-constructor}

```python title="Syntax"
GeometryCollection(geometries)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>geometries</code> _(required)_</td>
            <td><code>list[Geometry]</code></td>
            <td>A list of geometry objects of any type.</td>
        </tr>
    </tbody>
</table>

### Examples

```python
collection = GeometryCollection([
    GeometryPoint(-0.1278, 51.5074),
    GeometryLine([
        GeometryPoint(0.0, 0.0),
        GeometryPoint(1.0, 1.0),
    ]),
])
```

---

## Usage {#usage}

```python
from surrealdb import Surreal, GeometryPoint

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    db.create("locations", {
        "name": "London",
        "coordinates": GeometryPoint(-0.1278, 51.5074),
    })

    locations = db.query("""
        SELECT * FROM locations
        WHERE geo::distance(coordinates, $point) < 50000
    """, {
        "point": GeometryPoint(-0.1180, 51.5099),
    }).first()
```

---

## See also

- [Data types](/docs/reference/python/api/values.md) - All SDK data types
- [RecordID](/docs/reference/python/api/values/record-id.md) - Record identifier
- [SurrealQL Geometry Functions](/docs/reference/query-language/functions/database-functions/geo.md) - Geospatial query functions

---

Source: https://surrealdb.com/docs/reference/python/api/values/range

# Range

Range type with inclusive and exclusive bound support.

A `Range` represents a SurrealDB range value with a begin and end bound. Each bound can be inclusive or exclusive. `Range` is a Python dataclass.

```python title="Import"
from surrealdb import Range
from surrealdb.data.types.range import BoundIncluded, BoundExcluded
```

---

## `Bound` classes {#bounds}

The `Bound` base class has two subclasses that define whether a boundary value is included or excluded.

### `BoundIncluded` {#boundincluded}

An inclusive bound. The boundary value is part of the range.

<table>
    <thead>
        <tr>
            <th>Property</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>value</code></td>
            <td><code><a href="/docs/reference/python/api/values/#value">Value</a></code></td>
            <td>The boundary value.</td>
        </tr>
    </tbody>
</table>

### `BoundExcluded` {#boundexcluded}

An exclusive bound. The boundary value is not part of the range.

<table>
    <thead>
        <tr>
            <th>Property</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>value</code></td>
            <td><code><a href="/docs/reference/python/api/values/#value">Value</a></code></td>
            <td>The boundary value.</td>
        </tr>
    </tbody>
</table>

---

## `Range` dataclass {#range-dataclass}

### Fields {#fields}

| Field | Type | Description |
|---|---|---|
| `begin` | `Bound` | The start bound of the range. |
| `end` | `Bound` | The end bound of the range. |

### Constructor {#constructor}

```python title="Syntax"
Range(begin, end)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>begin</code> _(required)_</td>
            <td><code>Bound</code></td>
            <td>The start bound (inclusive or exclusive).</td>
        </tr>
        <tr>
            <td><code>end</code> _(required)_</td>
            <td><code>Bound</code></td>
            <td>The end bound (inclusive or exclusive).</td>
        </tr>
    </tbody>
</table>

---

## Examples {#examples}

```python title="Inclusive range 1 to 10"
from surrealdb import Range
from surrealdb.data.types.range import BoundIncluded

r = Range(
    begin=BoundIncluded(1),
    end=BoundIncluded(10),
)
```

```python title="Half-open range 1 to 10"
from surrealdb.data.types.range import BoundIncluded, BoundExcluded

r = Range(
    begin=BoundIncluded(1),
    end=BoundExcluded(10),
)
```

```python title="Using with queries"
from surrealdb import Surreal, Range
from surrealdb.data.types.range import BoundIncluded

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    events = db.query(
        "SELECT * FROM events WHERE year IN $range",
        {"range": Range(BoundIncluded(2020), BoundIncluded(2025))},
    ).first()
```

---

## See also

- [Data types](/docs/reference/python/api/values.md) - All SDK data types
- [RecordID](/docs/reference/python/api/values/record-id.md) - Record identifier

---

Source: https://surrealdb.com/docs/reference/python/api/values/record-id

# RecordID

Record identifier with table name and ID components.

A `RecordID` represents a unique record identifier in SurrealDB, combining a table name with an ID value. It is the Python equivalent of SurrealDB's `record` type.

```python title="Import"
from surrealdb import RecordID
```

**Source:** [record_id.py](https://github.com/surrealdb/surrealdb.py/blob/main/src/surrealdb/data/types/record_id.py)

---

## Constructor {#constructor}

```python title="Syntax"
RecordID(table_name, identifier)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table_name</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The name of the table this record belongs to.</td>
        </tr>
        <tr>
            <td><code>identifier</code> _(required)_</td>
            <td><code>Any</code></td>
            <td>The unique identifier for the record within the table.</td>
        </tr>
    </tbody>
</table>

### Examples

```python title="String ID"
record = RecordID("users", "john")
```

```python title="Numeric ID"
record = RecordID("products", 42)
```

```python title="List ID"
record = RecordID("events", ["2025", "01", "01"])
```

---

## Static methods {#static-methods}

### `RecordID.parse()` {#parse}

Parses a record ID from its string representation.

```python title="Syntax"
RecordID.parse(record_str)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>record_str</code> _(required)_</td>
            <td><code>str</code></td>
            <td>A record ID string in <code>table_name:id</code> format.</td>
        </tr>
    </tbody>
</table>

**Returns:** `RecordID`

```python
record = RecordID.parse("users:john")
print(record.table_name)  # "users"
print(record.id)          # "john"
```

---

## Properties {#properties}

| Property | Type | Description |
|---|---|---|
| `table_name` | `str` | The table name component of the record ID. |
| `id` | [`Value`](/docs/reference/python/api/values/#value) | The identifier component of the record ID. |

```python
record = RecordID("users", "john")
print(record.table_name)  # "users"
print(record.id)          # "john"
```

---

## Methods {#methods}

### `__str__()` {#str}

Returns the string representation in `table_name:id` format.

```python
record = RecordID("users", "john")
print(str(record))  # "users:john"
```

### `__eq__()` {#eq}

Compares two `RecordID` instances for equality based on both `table_name` and `id`.

```python
a = RecordID("users", "john")
b = RecordID("users", "john")
print(a == b)  # True
```

---

## Pydantic support {#pydantic}

When the `pydantic` extra is installed (`pip install surrealdb[pydantic]`), `RecordID` can be used as a field type in Pydantic models with automatic validation and serialisation.

```python
from pydantic import BaseModel
from surrealdb import RecordID

class User(BaseModel):
    id: RecordID
    name: str

user = User(id=RecordID("users", "john"), name="John")
```

---

## `RecordIdType` {#recordidtype}

Methods that accept a record or table reference use the `RecordIdType` alias, which accepts a plain string, a [`Table`](/docs/reference/python/api/values/table.md), or a `RecordID`.

```python title="Type Definition"
RecordIdType = str | Table | RecordID
```

See the [Data types overview](/docs/reference/python/api/values/#recordidtype) for details.

---

## See also

- [Data types](/docs/reference/python/api/values.md) - All SDK data types
- [Table](/docs/reference/python/api/values/table.md) - Table name wrapper
- [Surreal](/docs/reference/python/api/core/surreal.md) - Connection and query methods

---

Source: https://surrealdb.com/docs/reference/python/api/values/table

# Table

Table name wrapper for type-safe table references.

A `Table` wraps a table name string, providing a type-safe way to reference SurrealDB tables. It can be used anywhere a [`RecordIdType`](/docs/reference/python/api/values/#recordidtype) is accepted.

```python title="Import"
from surrealdb import Table
```

---

## Constructor {#constructor}

```python title="Syntax"
Table(table_name)
```

<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>table_name</code> _(required)_</td>
            <td><code>str</code></td>
            <td>The name of the table.</td>
        </tr>
    </tbody>
</table>

### Examples

```python
table = Table("users")
print(table.table_name)  # "users"
```

---

## Properties {#properties}

| Property | Type | Description |
|---|---|---|
| `table_name` | `str` | The table name string. |

---

## Usage {#usage}

`Table` is interchangeable with a plain `str` in most SDK methods. It is useful when you want to distinguish table references from arbitrary strings at the type level.

```python
from surrealdb import Surreal, Table

db = Surreal("ws://localhost:8000")
db.connect()
db.use("my_ns", "my_db")
db.signin({"username": "root", "password": "secret"})

users = db.select(Table("users"))
```

---

## See also

- [Data types](/docs/reference/python/api/values.md) - All SDK data types
- [RecordID](/docs/reference/python/api/values/record-id.md) - Record identifier with table and ID components

---

Source: https://surrealdb.com/docs/reference/python/concepts/authentication

# Authentication

The Python SDK supports multiple levels of authentication for signing in and managing user credentials.

The Python SDK supports signing in as a root, namespace, database, or record-level user. After signing in, the connection is authenticated for all subsequent operations until the session is invalidated or the connection is closed.

This page covers how to sign in, sign up, manage tokens, and inspect the current user.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#signin"><code>db.signin(vars)</code></a></td>
			<td scope="row" data-label="Description">Signs in as a root, namespace, database, or record user</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#signup"><code>db.signup(vars)</code></a></td>
			<td scope="row" data-label="Description">Signs up a new record user via an access method</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#authenticate"><code>db.authenticate(token)</code></a></td>
			<td scope="row" data-label="Description">Authenticates the connection with an existing JWT token</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#invalidate"><code>db.invalidate()</code></a></td>
			<td scope="row" data-label="Description">Invalidates the current authentication session</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#info"><code>db.info()</code></a></td>
			<td scope="row" data-label="Description">Returns the record data for the currently authenticated record user</td>
		</tr>
	</tbody>
</table>

## Signing in users

The `.signin()` method authenticates the connection. The fields you pass determine the authentication level. The method returns [Tokens](/docs/reference/python/api/types/#tokens) on success, which contain the JWT access token and optional refresh token.

Refer to the [API reference](/docs/reference/python/api/core/surreal.md#signin) for the full list of parameters at each level.

**Root user**

A root user has full access to the SurrealDB instance. Only `username` and `password` are required.

		```python
		from surrealdb import Surreal

		db = Surreal("ws://localhost:8000")
		db.connect()

		tokens = db.signin({
		    "username": "root",
		    "password": "secret",
		})
		```

**Namespace user**

A namespace user has access to all databases within a specific namespace. Provide the `namespace` alongside credentials.

		```python
		from surrealdb import Surreal

		db = Surreal("ws://localhost:8000")
		db.connect()

		tokens = db.signin({
		    "namespace": "surrealdb",
		    "username": "tobie",
		    "password": "123456",
		})
		```

**Database user**

A database user has access to a single database. Provide both `namespace` and `database` alongside credentials.

		```python
		from surrealdb import Surreal

		db = Surreal("ws://localhost:8000")
		db.connect()

		tokens = db.signin({
		    "namespace": "surrealdb",
		    "database": "docs",
		    "username": "tobie",
		    "password": "123456",
		})
		```

**Record access**

A record access user authenticates against a [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) method defined on a database. Provide `namespace`, `database`, `access`, and any variables required by the access definition.

		```python
		from surrealdb import Surreal

		db = Surreal("ws://localhost:8000")
		db.connect()

		tokens = db.signin({
		    "namespace": "surrealdb",
		    "database": "docs",
		    "access": "account",
		    "variables": {
		        "email": "info@surrealdb.com",
		        "password": "123456",
		    },
		})
		```

All examples above use the synchronous API. The async variant works the same way - prefix each call with `await`.

## Signing up users

The `.signup()` method registers a new record user through a [record access method](/docs/reference/query-language/statements/define/access/record.md) and returns [Tokens](/docs/reference/python/api/types/#tokens). Signup is only available for record-level access.

You must provide the `namespace`, `database`, and `access` fields, along with any `variables` expected by the access definition.

**Synchronous**

		```python
		from surrealdb import Surreal

		db = Surreal("ws://localhost:8000")
		db.connect()

		tokens = db.signup({
		    "namespace": "surrealdb",
		    "database": "docs",
		    "access": "account",
		    "variables": {
		        "email": "newuser@surrealdb.com",
		        "password": "s3cureP@ss",
		    },
		})
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		db = AsyncSurreal("ws://localhost:8000")
		await db.connect()

		tokens = await db.signup({
		    "namespace": "surrealdb",
		    "database": "docs",
		    "access": "account",
		    "variables": {
		        "email": "newuser@surrealdb.com",
		        "password": "s3cureP@ss",
		    },
		})
		```

## Authenticating with an existing token

If you already have a JWT token - for example, one returned from a previous `.signin()` or stored in a cookie - you can authenticate the connection directly with `.authenticate()`.

**Synchronous**

		```python
		db.authenticate("eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...")
		```

**Asynchronous**

		```python
		await db.authenticate("eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...")
		```

This is useful in server-side applications where the token is passed from a client request and needs to be forwarded to the database connection.

## Retrieving user information

The `.info()` method returns the record data for the currently authenticated record user. This is only available when signed in as a record-level user.

**Synchronous**

		```python
		user = db.info()
		print(user)
		```

**Asynchronous**

		```python
		user = await db.info()
		print(user)
		```

The return value is a [Value](/docs/reference/python/api/types/#value) containing the fields of the authenticated user's record. If no record user is authenticated, the method returns `None`.

## Signing out

The `.invalidate()` method clears the authentication state for the current connection. After invalidation, subsequent operations will execute as an unauthenticated user.

**Synchronous**

		```python
		db.invalidate()
		```

**Asynchronous**

		```python
		await db.invalidate()
		```

## Learn more

- [Surreal API reference](/docs/reference/python/api/core/surreal.md) for complete method signatures and parameters
- [Tokens type reference](/docs/reference/python/api/types/#tokens) for the structure of the returned tokens
- [Authentication in SurrealDB](/docs/learn/security/authentication/users.md) for an overview of authentication concepts
- [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md) for defining record and JWT access methods
- [Connecting to SurrealDB](/docs/reference/python/concepts/connecting-to-surrealdb.md) for connection setup and protocol options

---

Source: https://surrealdb.com/docs/reference/python/concepts/connecting-to-surrealdb

# Connecting to SurrealDB

The Python SDK provides synchronous and asynchronous connections to local, remote, or embedded SurrealDB instances.

The Python SDK supports connecting to SurrealDB over WebSocket, HTTP, or as an embedded in-process database. Connections can be synchronous or asynchronous, and the SDK automatically selects the correct connection class based on the URL scheme you provide.

This page covers how to create, configure, and manage connections to a SurrealDB instance.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#surreal-sync"><code>Surreal(url)</code></a></td>
			<td scope="row" data-label="Description">Creates a synchronous connection based on the URL scheme</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#surreal-sync"><code>AsyncSurreal(url)</code></a></td>
			<td scope="row" data-label="Description">Creates an asynchronous connection based on the URL scheme</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#connect"><code>db.connect(url?)</code></a></td>
			<td scope="row" data-label="Description">Opens the connection to the SurrealDB instance</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#close"><code>db.close()</code></a></td>
			<td scope="row" data-label="Description">Closes the active connection and releases resources</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#use"><code>db.use(namespace, database)</code></a></td>
			<td scope="row" data-label="Description">Switches to a specific namespace and database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#version"><code>db.version()</code></a></td>
			<td scope="row" data-label="Description">Returns the version of the connected SurrealDB instance</td>
		</tr>
	</tbody>
</table>

## Choosing between synchronous and asynchronous

The SDK provides two factory functions: `Surreal` for synchronous (blocking) connections and `AsyncSurreal` for asynchronous connections. Both return a connection object with the same set of methods, the async variants must be awaited.

Use `Surreal` when your application is synchronous or when you are writing scripts, CLI tools, or other non-async code. Use `AsyncSurreal` when working with async frameworks such as FastAPI, aiohttp, or any `asyncio`-based application.

**Synchronous**

		```python
		from surrealdb import Surreal

		db = Surreal("ws://localhost:8000")
		db.connect()
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		db = AsyncSurreal("ws://localhost:8000")
		await db.connect()
		```

## Opening a connection

The `Surreal` and `AsyncSurreal` factory functions accept a URL and return the appropriate connection class. The SDK inspects the URL scheme to determine whether to use a WebSocket, HTTP, or embedded connection.

You can connect to a remote SurrealDB instance over WebSocket or HTTP.

**Synchronous**

		```python
		from surrealdb import Surreal

		ws_db = Surreal("ws://localhost:8000")
		ws_db.connect()

		http_db = Surreal("https://cloud.surrealdb.com")
		http_db.connect()
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		ws_db = AsyncSurreal("ws://localhost:8000")
		await ws_db.connect()

		http_db = AsyncSurreal("https://cloud.surrealdb.com")
		await http_db.connect()
		```

You can also run SurrealDB as an embedded in-process database, which is useful for testing or standalone applications.

```python
from surrealdb import Surreal

mem_db = Surreal("mem://")
mem_db.connect()

disk_db = Surreal("surrealkv://path/to/database")
disk_db.connect()
```

The `.connect()` method optionally accepts a URL to override the one provided to the factory. See the [API reference](/docs/reference/python/api/core/surreal.md#connect) for parameter details.

## Connection string protocols

The URL scheme determines the connection type and its capabilities.

| Scheme | Connection type | Description |
|---|---|---|
| `ws://` | WebSocket | Unencrypted stateful connection |
| `wss://` | WebSocket | TLS-encrypted stateful connection |
| `http://` | HTTP | Unencrypted stateless connection |
| `https://` | HTTP | TLS-encrypted stateless connection |
| `mem://` | Embedded (in-memory) | In-process database that does not persist data |
| `file://` | Embedded (on-disk) | In-process database backed by SurrealKV storage |
| `surrealkv://` | Embedded (on-disk) | In-process database backed by SurrealKV storage |

WebSocket and HTTP are used to connect to remote SurrealDB instances, while the embedded protocols run the database engine directly in your Python process. See [Embedded databases](/docs/reference/python/concepts/embedded-databases.md) for more on in-process connections.

## Selecting a namespace and database

After connecting, use `.use()` to select the namespace and database you want to work with. Most operations require a namespace and database to be selected first.

**Synchronous**

		```python
		db.use("surrealdb", "docs")
		```

**Asynchronous**

		```python
		await db.use("surrealdb", "docs")
		```

You can call `.use()` multiple times to switch between namespaces and databases on the same connection.

## Using context managers

Python's context manager protocol (`with` / `async with`) provides a convenient way to manage connection lifecycle. The connection is automatically opened when entering the context and closed when exiting, even if an exception occurs.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("ws://localhost:8000") as db:
		    db.use("surrealdb", "docs")
		    db.signin({"username": "root", "password": "secret"})
		    results = db.select("users")
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("surrealdb", "docs")
		    await db.signin({"username": "root", "password": "secret"})
		    results = await db.select("users")
		```

Using context managers is the recommended approach, as it guarantees resources are released even when errors occur.

## Effect of connection protocol on token and session duration

The connection protocol affects how authentication tokens and sessions behave.

WebSocket connections (`ws://`, `wss://`) are long-lived and stateful. After the initial authentication, the session persists for the lifetime of the connection. The session duration defaults to `NONE`, meaning it never expires unless configured otherwise.

HTTP connections (`http://`, `https://`) are short-lived and stateless. Each request is independent and requires its own authentication token. The token duration defaults to 1 hour.

You can configure token and session durations using the `DURATION` clause in the [`DEFINE ACCESS METHOD`](/docs/reference/query-language/statements/define/access.md) or [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md) statements.

> [!NOTE]
> Learn more about token and session duration in the [security best practices](/docs/learn/security/best-practices/security-best-practices.md#expiration) documentation.

## Closing a connection

When you are done with a connection, call `.close()` to release the underlying resources. If you use a context manager, this happens automatically.

**Synchronous**

		```python
		db.close()
		```

**Asynchronous**

		```python
		await db.close()
		```

## Learn more

- [Surreal API reference](/docs/reference/python/api/core/surreal.md) for complete method signatures and parameters
- [Authentication](/docs/reference/python/concepts/authentication.md) for signing in and managing user sessions
- [Embedded databases](/docs/reference/python/concepts/embedded-databases.md) for running SurrealDB in-process
- [Error handling](/docs/reference/python/concepts/error-handling.md) for handling connection and authentication errors

---

Source: https://surrealdb.com/docs/reference/python/concepts/data-manipulation

# Data manipulation

The Python SDK provides methods for selecting, creating, updating, and deleting records in SurrealDB.

The Python SDK provides dedicated methods for common CRUD operations on records and tables. These methods offer a structured alternative to writing raw SurrealQL, with built-in parameter handling and type safety.

This page covers how to target tables and records, and how to select, create, insert, update, merge, patch, and delete data.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#select"><code>db.select(record)</code></a></td>
			<td scope="row" data-label="Description">Selects all records from a table, or a specific record</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#create"><code>db.create(record, data?)</code></a></td>
			<td scope="row" data-label="Description">Creates a new record with an optional data payload</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#insert"><code>db.insert(table, data)</code></a></td>
			<td scope="row" data-label="Description">Inserts one or multiple records into a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#insert-relation"><code>db.insert_relation(table, data)</code></a></td>
			<td scope="row" data-label="Description">Inserts one or multiple relation records</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#update"><code>db.update(record, data?)</code></a></td>
			<td scope="row" data-label="Description">Replaces the entire content of a record or all records in a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#upsert"><code>db.upsert(record, data?)</code></a></td>
			<td scope="row" data-label="Description">Creates a record if it does not exist, or replaces it entirely</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#merge"><code>db.merge(record, data?)</code></a></td>
			<td scope="row" data-label="Description">Merges data into an existing record, preserving unmentioned fields</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#patch"><code>db.patch(record, data?)</code></a></td>
			<td scope="row" data-label="Description">Applies JSON Patch operations to a record or all records in a table</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#delete"><code>db.delete(record)</code></a></td>
			<td scope="row" data-label="Description">Deletes a specific record or all records from a table</td>
		</tr>
	</tbody>
</table>

## Targeting tables and records

Most data manipulation methods accept a `record` parameter that determines the scope of the operation. You can pass a table name as a string to target all records in that table, or a [RecordID](/docs/reference/python/api/values/record-id.md) to target a specific record.

```python
from surrealdb import RecordID

db.select("users")

db.select(RecordID("users", "tobie"))
```

When a string is passed, the operation applies to the entire table. When a `RecordID` is passed, it applies to the single record identified by that ID. See the [RecordID reference](/docs/reference/python/api/values/record-id.md) for more on constructing record identifiers.

The ID part of a `RecordID` must have the same type as the ID of the stored record. A record created with a UUID, such as `users:u"019d764a-c692-7631-a182-de9d745eb127"`, has a `uuid.UUID` as its ID. A string with the same characters names a different record, so a lookup with the string finds nothing. `RecordID.parse()` reads the ID as a string as well, so it does not find such a record either.

```python
import uuid
from surrealdb import RecordID

# Finds the record
db.select(RecordID("users", uuid.UUID("019d764a-c692-7631-a182-de9d745eb127")))

# Returns [], because the ID is a string rather than a UUID
db.select(RecordID("users", "019d764a-c692-7631-a182-de9d745eb127"))
```

The same applies to numeric IDs: `RecordID("posts", 123)` and `RecordID("posts", "123")` are different records.

## Selecting records

The `.select()` method retrieves records from the database. Pass a table name to get all records, or a `RecordID` to get a single record.

**Synchronous**

		```python
		from surrealdb import Surreal, RecordID

		with Surreal("ws://localhost:8000") as db:
		    db.use("surrealdb", "docs")
		    db.signin({"username": "root", "password": "secret"})

		    all_users = db.select("users")

		    tobie = db.select(RecordID("users", "tobie"))
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal, RecordID

		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("surrealdb", "docs")
		    await db.signin({"username": "root", "password": "secret"})

		    all_users = await db.select("users")

		    tobie = await db.select(RecordID("users", "tobie"))
		```

The method always returns a list. When selecting a specific record, the list holds that record, or is empty if the record does not exist.

## Creating records

The `.create()` method creates a new record. Pass a table name to generate a random ID, or a `RecordID` to specify the ID explicitly.

**Synchronous**

		```python
		from surrealdb import RecordID

		user = db.create("users", {
		    "name": "Alice",
		    "email": "alice@example.com",
		    "age": 30,
		})

		specific = db.create(RecordID("users", "tobie"), {
		    "name": "Tobie",
		    "email": "tobie@surrealdb.com",
		    "age": 35,
		})
		```

**Asynchronous**

		```python
		from surrealdb import RecordID

		user = await db.create("users", {
		    "name": "Alice",
		    "email": "alice@example.com",
		    "age": 30,
		})

		specific = await db.create(RecordID("users", "tobie"), {
		    "name": "Tobie",
		    "email": "tobie@surrealdb.com",
		    "age": 35,
		})
		```

The method returns the created record, including any server-generated fields such as the `id`.

## Inserting records

The `.insert()` method inserts one or more records into a table. This is useful for bulk operations where you need to add multiple records at once.

**Synchronous**

		```python
		db.insert("users", {"name": "Alice", "age": 30})

		db.insert("users", [
		    {"name": "Bob", "age": 25},
		    {"name": "Charlie", "age": 40},
		])
		```

**Asynchronous**

		```python
		await db.insert("users", {"name": "Alice", "age": 30})

		await db.insert("users", [
		    {"name": "Bob", "age": 25},
		    {"name": "Charlie", "age": 40},
		])
		```

The `.insert_relation()` method works the same way but is designed for creating graph edges between records. Each record must include `in` and `out` fields pointing to the connected records.

**Synchronous**

		```python
		from surrealdb import RecordID

		db.insert_relation("likes", {
		    "in": RecordID("users", "tobie"),
		    "out": RecordID("posts", 123),
		})

		db.insert_relation("likes", [
		    {"in": RecordID("users", "tobie"), "out": RecordID("posts", 123)},
		    {"in": RecordID("users", "jaime"), "out": RecordID("posts", 456)},
		])
		```

**Asynchronous**

		```python
		from surrealdb import RecordID

		await db.insert_relation("likes", {
		    "in": RecordID("users", "tobie"),
		    "out": RecordID("posts", 123),
		})

		await db.insert_relation("likes", [
		    {"in": RecordID("users", "tobie"), "out": RecordID("posts", 123)},
		    {"in": RecordID("users", "jaime"), "out": RecordID("posts", 456)},
		])
		```

## Replacing records

The `.update()` method replaces the entire content of a record or all records in a table. Any fields not included in the new data are removed.

**Synchronous**

		```python
		from surrealdb import RecordID

		db.update(RecordID("users", "tobie"), {
		    "name": "Tobie",
		    "email": "tobie@surrealdb.com",
		    "active": True,
		})

		db.update("users", {"active": False})
		```

**Asynchronous**

		```python
		from surrealdb import RecordID

		await db.update(RecordID("users", "tobie"), {
		    "name": "Tobie",
		    "email": "tobie@surrealdb.com",
		    "active": True,
		})

		await db.update("users", {"active": False})
		```

> [!NOTE]
> Because `.update()` performs a full replacement, omitted fields are deleted from the record. Use `.merge()` if you want to preserve existing fields.

## Upserting records

The `.upsert()` method creates a record if it does not already exist, or replaces it entirely if it does. This combines the behaviour of `.create()` and `.update()` in a single operation.

**Synchronous**

		```python
		from surrealdb import RecordID

		db.upsert(RecordID("users", "tobie"), {
		    "name": "Tobie",
		    "email": "tobie@surrealdb.com",
		    "active": True,
		})
		```

**Asynchronous**

		```python
		from surrealdb import RecordID

		await db.upsert(RecordID("users", "tobie"), {
		    "name": "Tobie",
		    "email": "tobie@surrealdb.com",
		    "active": True,
		})
		```

## Merging data

The `.merge()` method deep-merges the provided data into the existing record, preserving any fields that are not mentioned in the merge payload. This is useful for partial updates.

**Synchronous**

		```python
		from surrealdb import RecordID

		db.merge(RecordID("users", "tobie"), {
		    "settings": {"active": True},
		})

		db.merge("users", {
		    "updated_at": "2026-02-25T12:00:00Z",
		})
		```

**Asynchronous**

		```python
		from surrealdb import RecordID

		await db.merge(RecordID("users", "tobie"), {
		    "settings": {"active": True},
		})

		await db.merge("users", {
		    "updated_at": "2026-02-25T12:00:00Z",
		})
		```

In the example above, only the `settings.active` field is changed on the specific record. All other fields on the record remain untouched.

## Applying patches

The `.patch()` method applies [JSON Patch (RFC 6902)](https://jsonpatch.com/) operations to a record or all records in a table. Each operation is a dictionary with `op`, `path`, and optionally `value` fields.

**Synchronous**

		```python
		from surrealdb import RecordID

		db.patch(RecordID("users", "tobie"), [
		    {"op": "replace", "path": "/settings/active", "value": False},
		    {"op": "add", "path": "/tags", "value": ["developer", "admin"]},
		    {"op": "remove", "path": "/temp"},
		])
		```

**Asynchronous**

		```python
		from surrealdb import RecordID

		await db.patch(RecordID("users", "tobie"), [
		    {"op": "replace", "path": "/settings/active", "value": False},
		    {"op": "add", "path": "/tags", "value": ["developer", "admin"]},
		    {"op": "remove", "path": "/temp"},
		])
		```

Supported operations include `add`, `remove`, `replace`, `move`, `copy`, and `test`.

## Deleting records

The `.delete()` method removes a specific record or all records from a table. The method returns the deleted record(s).

**Synchronous**

		```python
		from surrealdb import RecordID

		deleted = db.delete(RecordID("users", "tobie"))

		all_deleted = db.delete("users")
		```

**Asynchronous**

		```python
		from surrealdb import RecordID

		deleted = await db.delete(RecordID("users", "tobie"))

		all_deleted = await db.delete("users")
		```

## Learn more

- [Surreal API reference](/docs/reference/python/api/core/surreal.md) for complete method signatures and parameters
- [Executing queries](/docs/reference/python/concepts/executing-queries.md) for running SurrealQL statements directly
- [Value types](/docs/reference/python/api/types.md) for the types used by data manipulation methods
- [RecordID reference](/docs/reference/python/api/values/record-id.md) for constructing record identifiers
- [SurrealQL CRUD statements](/docs/reference/query-language/statements/overview.md) for the underlying query language

---

Source: https://surrealdb.com/docs/reference/python/concepts/embedded-databases

# Embedded databases

The Python SDK supports running SurrealDB as an embedded database directly within your application.

The Python SDK can run SurrealDB directly within your application process, eliminating the need for a separate server. Embedded databases are useful for testing, development, desktop applications, and scenarios where a standalone database server is impractical.

To use an embedded database, pass a `mem://`, `file://`, or `surrealkv://` URL to the `Surreal` or `AsyncSurreal` factory function.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#surreal-sync"><code>Surreal(url)</code></a></td>
			<td scope="row" data-label="Description">Creates a synchronous embedded connection based on the URL scheme</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#surreal-sync"><code>AsyncSurreal(url)</code></a></td>
			<td scope="row" data-label="Description">Creates an asynchronous embedded connection based on the URL scheme</td>
		</tr>
	</tbody>
</table>

## Connection URL schemes

The URL scheme determines whether the embedded database stores data in memory or on disk.

| Scheme | Storage | Persistence |
|---|---|---|
| `mem://` | In-memory | Data is lost when the process exits |
| `file://path` | On-disk (SurrealKV) | Data persists to the specified directory |
| `surrealkv://path` | On-disk (SurrealKV) | Data persists to the specified directory |

See the [UrlScheme](/docs/reference/python/api/types/#urlscheme) type reference for the full list of supported schemes.

## In-memory databases

An in-memory database runs entirely in RAM and does not persist data between process restarts. This is well suited for unit tests, temporary workloads, and prototyping where durability is not required.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("mem://") as db:
		    db.use("main", "main")
		    db.signin({"username": "root", "password": "secret"})
		    db.create("users", {"name": "Alice", "age": 30})
		    print(db.select("users"))
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("mem://") as db:
		    await db.use("main", "main")
		    await db.signin({"username": "root", "password": "secret"})
		    await db.create("users", {"name": "Alice", "age": 30})
		    print(await db.select("users"))
		```

Each `mem://` connection creates an independent database instance. Data is not shared between separate connections.

## Persistent databases

The `file://` and `surrealkv://` schemes persist data to a directory on disk. This is useful for desktop applications, local-first architectures, and development workflows where you want data to survive restarts.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("surrealkv://data/mydb") as db:
		    db.use("app", "main")
		    db.signin({"username": "root", "password": "secret"})
		    db.create("settings", {"theme": "dark", "lang": "en"})
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("file://data/mydb") as db:
		    await db.use("app", "main")
		    await db.signin({"username": "root", "password": "secret"})
		    await db.create("settings", {"theme": "dark", "lang": "en"})
		```

The path is relative to the working directory of your process. Use an absolute path for a fixed location on disk.

## Feature limitations

Embedded connections do not support the full set of features available with WebSocket or HTTP connections. Attempting to use an unsupported feature raises an [`UnsupportedFeatureError`](/docs/reference/python/api/errors/#unsupportedfeatureerror).

The following features are **not available** with embedded connections:

- **Sessions**: `.new_session()` is not supported
- **Transactions**: `.new_transaction()` is not supported
- **Live queries**: `.live()` and `.subscribe_live()` are not supported

> [!NOTE]
> If your application requires sessions, transactions, or live queries, use a WebSocket connection (`ws://` or `wss://`) instead.

All other operations - queries, CRUD methods, authentication, and parameter binding - work as expected with embedded connections.

## Learn more

- [Surreal API reference](/docs/reference/python/api/core/surreal.md) for complete method signatures and parameters
- [Connecting to SurrealDB](/docs/reference/python/concepts/connecting-to-surrealdb.md) for an overview of all connection protocols
- [UrlScheme type reference](/docs/reference/python/api/types/#urlscheme) for the full list of URL schemes

---

Source: https://surrealdb.com/docs/reference/python/concepts/error-handling

# Error handling

The Python SDK provides a structured error hierarchy for handling server and client-side failures.

All errors raised by the Python SDK extend [`SurrealError`](/docs/reference/python/api/errors/#surrealerror), so you can catch every SDK error with a single `except` clause. Server-originated errors use the [`ServerError`](/docs/reference/python/api/errors/#servererror) subtree with structured kinds, details, and cause chains. SDK-side errors cover connection, parsing, and feature support failures.

The error kinds, wire codes and structured shape behind these are documented once in [Errors](/docs/reference/rest-api/errors.md), which applies to every SDK and protocol.

See the [Errors reference](/docs/reference/python/api/errors/) for the complete error hierarchy and all available properties.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Error class</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#surrealerror"><code>SurrealError</code></a></td>
			<td scope="row" data-label="Description">Base class for all SDK errors</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#servererror"><code>ServerError</code></a></td>
			<td scope="row" data-label="Description">Structured server error with kind, details, and cause</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#notallowederror"><code>NotAllowedError</code></a></td>
			<td scope="row" data-label="Description">Thrown when permission is denied</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#notfounderror"><code>NotFoundError</code></a></td>
			<td scope="row" data-label="Description">Thrown when a resource is not found</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#connectionunavailableerror"><code>ConnectionUnavailableError</code></a></td>
			<td scope="row" data-label="Description">Thrown when no connection is active</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#unsupportedfeatureerror"><code>UnsupportedFeatureError</code></a></td>
			<td scope="row" data-label="Description">Thrown for features not supported by the connection type</td>
		</tr>
	</tbody>
</table>

## Where an error surfaces

A query travels through two layers, and a failure can come from either one.

The first is the request itself: a connection that is unavailable, a rejected sign-in, a query that will not parse. The second is the individual statements inside the query, which can fail while the request as a whole succeeds.

`.execute()` collapses both into an exception. It raises on the first statement that fails, so a query whose earlier statements succeeded returns nothing at all: those results are lost along with the error.

Where the per-statement outcome matters, [`query_raw()`](/docs/reference/python/api/core/surreal.md#query-raw) returns every statement instead of raising. Each entry carries a `status` of `OK` or `ERR`, and a failing one also carries the `kind`.

```python
from surrealdb import Surreal, ThrownError

with Surreal("ws://localhost:8000") as db:
    db.signin({"username": "root", "password": "secret"})
    db.use("test", "test")

    # .execute() raises on the first statement that fails, so the result of
    # the statement that succeeded is not returned.
    try:
        db.query("RETURN 1; THROW 'second'").execute()
    except ThrownError as e:
        print(f"{e.kind}: {e}")

    # query_raw() reports every statement instead of raising.
    response = db.query_raw("RETURN 1; THROW 'second'")
    for index, statement in enumerate(response["result"]):
        print(index, statement["status"], statement["result"])
```

```python title="Output"
Thrown: An error occurred: second
0 OK 1
1 ERR An error occurred: second
```

## Error kinds

Every server error carries a `.kind`, and the SDK raises a dedicated class for each of the kinds below. The meaning of each kind is described in [Errors](/docs/reference/rest-api/errors.md#error-kinds). Match on the kind or the class rather than on the message text, which is free to change between releases.

| Kind | Exception class |
| --- | --- |
| `Validation` | `ValidationError` |
| `Configuration` | `ConfigurationError` |
| `Query` | `QueryError` |
| `Serialization` | `SerializationError` |
| `NotAllowed` | `NotAllowedError` |
| `NotFound` | `NotFoundError` |
| `AlreadyExists` | `AlreadyExistsError` |
| `Thrown` | `ThrownError` |
| `Internal` | `InternalError` |

Any kind without a dedicated class, including one added by a newer server, arrives as the base `ServerError` with its `.kind` intact. Catching `ServerError` therefore stays correct as the server grows new kinds, and the `ErrorKind` enum can be used with `.has_kind()` to test for one without importing its class.

## Catching all SDK errors

The simplest way to handle errors is to catch `SurrealError`, which is the base class for every exception the SDK raises.

```python
from surrealdb import Surreal, SurrealError

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    try:
        result = db.query("SELECT * FROM users").execute()
    except SurrealError as e:
        print("SDK error:", e)
```

This pattern is useful at the top level of your application where you want to ensure no SDK error goes unhandled.

## Handling server errors

Server errors carry structured information beyond the error message. A `ServerError` has a `.kind` string, an optional `.details` dictionary, and an optional `.server_cause` linking to the underlying error in the chain.

You can check whether an error is a `ServerError` and then inspect its kind using the constants defined on [`ErrorKind`](/docs/reference/python/api/errors/#errorkind).

```python
from surrealdb import ServerError, ErrorKind

try:
    result = db.query("INVALID QUERY").execute()
except ServerError as e:
    print("Kind:", e.kind)
    print("Details:", e.details)

    if e.kind == ErrorKind.VALIDATION:
        print("The query has a validation issue")
    elif e.kind == ErrorKind.NOT_ALLOWED:
        print("Permission denied")
```

The `ErrorKind` constants include `VALIDATION`, `CONFIGURATION`, `THROWN`, `QUERY`, `SERIALIZATION`, `NOT_ALLOWED`, `NOT_FOUND`, `ALREADY_EXISTS`, `CONNECTION`, and `INTERNAL`.

## Inspecting the error cause chain

Server errors can form a chain where one error caused another. The `.has_kind()` method checks whether this error or any error in its cause chain matches a given kind. The `.find_cause()` method returns the first matching error in the chain.

```python
from surrealdb import ServerError, ErrorKind

try:
    db.signin({"username": "user", "password": "wrong"})
except ServerError as e:
    if e.has_kind(ErrorKind.NOT_ALLOWED):
        print("Authentication failure somewhere in the chain")

    auth_cause = e.find_cause(ErrorKind.NOT_ALLOWED)
    if auth_cause:
        print("Root auth error:", auth_cause)
        print("Details:", auth_cause.details)
```

These methods are especially useful when a high-level error wraps a more specific cause, such as a query error that was ultimately caused by a permission denial.

## Catching specific error types

For fine-grained control, catch the specific error subclass you need. The SDK maps server error kinds to dedicated Python classes such as `ValidationError`, [`NotAllowedError`](/docs/reference/python/api/errors/#notallowederror), and [`NotFoundError`](/docs/reference/python/api/errors/#notfounderror).

```python
from surrealdb import NotAllowedError

try:
    db.signin({
        "namespace": "surrealdb",
        "database": "docs",
        "access": "account",
        "variables": {
            "email": "user@example.com",
            "password": "wrong_password",
        },
    })
except NotAllowedError as e:
    if e.is_invalid_auth:
        print("Invalid credentials")
    elif e.is_token_expired:
        print("Token expired, please re-authenticate")
```

You can also catch `NotFoundError` to handle missing resources.

```python
from surrealdb import NotFoundError, RecordID

try:
    user = db.select(RecordID("users", "nonexistent"))
except NotFoundError as e:
    if e.table_name:
        print(f"Table not found: {e.table_name}")
    elif e.record_id:
        print(f"Record not found: {e.record_id}")
```

## Handling SDK-side errors

Some errors originate from the SDK itself rather than the server. These cover situations like missing connections and unsupported features.

A [`ConnectionUnavailableError`](/docs/reference/python/api/errors/#connectionunavailableerror) is raised when you try to perform an operation before establishing a connection.

```python
from surrealdb import Surreal, ConnectionUnavailableError

db = Surreal("ws://localhost:8000")

try:
    db.select("users")
except ConnectionUnavailableError:
    print("Not connected - call db.connect() first")
```

An [`UnsupportedFeatureError`](/docs/reference/python/api/errors/#unsupportedfeatureerror) is raised when you attempt to use a feature that requires a specific connection type. For example, sessions and transactions require a WebSocket connection.

```python
from surrealdb import Surreal, UnsupportedFeatureError

with Surreal("http://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    try:
        session = db.new_session()
    except UnsupportedFeatureError:
        print("Sessions require a WebSocket connection")
```

An [`UnsupportedEngineError`](/docs/reference/python/api/errors/#unsupportedengineerror) is raised when the URL scheme is not recognized.

```python
from surrealdb import Surreal, UnsupportedEngineError

try:
    db = Surreal("ftp://localhost:8000")
except UnsupportedEngineError as e:
    print(f"Unsupported protocol: {e.url}")
```

## Learn more

- [Errors reference](/docs/reference/python/api/errors/) for complete error hierarchy
- [ErrorKind constants](/docs/reference/python/api/errors/#errorkind) for error kind matching
- [Authentication](/docs/reference/python/concepts/authentication.md) for auth-related error patterns
- [Connecting to SurrealDB](/docs/reference/python/concepts/connecting-to-surrealdb.md) for connection error patterns

---

Source: https://surrealdb.com/docs/reference/python/concepts/executing-queries

# Executing queries

The Python SDK provides methods for executing SurrealQL queries with parameter binding support.

The Python SDK lets you execute [SurrealQL](/docs/reference/query-language.md) statements directly against the database. You can run ad-hoc queries with parameter binding, retrieve processed results, or access the full raw response for advanced use cases.

This page covers how to run queries, bind variables, and work with raw results.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#query"><code>db.query(query, vars?)</code></a></td>
			<td scope="row" data-label="Description">Builds a SurrealQL query; trigger it with <code>.execute()</code>, <code>.first()</code> or <code>.into(cls)</code></td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#query-raw"><code>db.query_raw(query, vars?)</code></a></td>
			<td scope="row" data-label="Description">Executes a SurrealQL query and returns the full raw response</td>
		</tr>
	</tbody>
</table>

## Running a query

The `.query()` method builds a SurrealQL query. It does not talk to the database on its own - you trigger it explicitly:

- `.execute()` returns a `list` of [Value](/docs/reference/python/api/types/#value) with **one entry per statement**, always a list, even when the query contains a single statement.
- `.first()` returns just the first statement's result, which is what you usually want for a one-statement query.

On an async connection you can also `await` the builder directly, which is the same as awaiting `.execute()`.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("ws://localhost:8000") as db:
		    db.use("surrealdb", "docs")
		    db.signin({"username": "root", "password": "secret"})

		    statements = db.query("SELECT * FROM users").execute()
		    print(statements)  # [[{...}, {...}]] - one entry, one statement

		    users = db.query("SELECT * FROM users").first()
		    print(users)       # [{...}, {...}]
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("surrealdb", "docs")
		    await db.signin({"username": "root", "password": "secret"})

		    statements = await db.query("SELECT * FROM users").execute()
		    print(statements)  # [[{...}, {...}]] - one entry, one statement

		    users = await db.query("SELECT * FROM users").first()
		    print(users)       # [{...}, {...}]
		```

## Passing variables

You can pass a dictionary of variables as the second argument to `.query()`. Variables are referenced in SurrealQL using the `$` prefix and are safely bound, preventing injection attacks.

**Synchronous**

		```python
		users = db.query(
		    "SELECT * FROM users WHERE age > $min_age AND active = $active",
		    {"min_age": 18, "active": True},
		).first()
		```

**Asynchronous**

		```python
		users = await db.query(
		    "SELECT * FROM users WHERE age > $min_age AND active = $active",
		    {"min_age": 18, "active": True},
		).first()
		```

You can bind any Python value supported by the SDK, including strings, numbers, booleans, lists, dictionaries, and SurrealDB-specific types such as [RecordID](/docs/reference/python/api/values/record-id.md).

```python
from surrealdb import RecordID

users = db.query(
    "SELECT * FROM users WHERE id = $user_id",
    {"user_id": RecordID("users", "tobie")},
).first()
```

## Getting raw query results

The `.query_raw()` method returns the full response from the server, including metadata such as execution time and status for each statement. This is useful for debugging or when you need to inspect how the server processed the query.

**Synchronous**

		```python
		response = db.query_raw("SELECT * FROM users; SELECT * FROM products")

		for statement in response["result"]:
		    print(statement["status"])
		    print(statement["time"])
		    print(statement["result"])
		```

**Asynchronous**

		```python
		response = await db.query_raw("SELECT * FROM users; SELECT * FROM products")

		for statement in response["result"]:
		    print(statement["status"])
		    print(statement["time"])
		    print(statement["result"])
		```

The response is a `dict` containing the RPC envelope. Its `result` key holds one entry per statement in the query, each with `status`, `time`, and `result` fields. Unlike `.query()`, a failed statement is reported as `"status": "ERR"` rather than raised.

## Handling multiple statements

When a query string contains multiple semicolon-separated statements, `.execute()` returns one entry per statement, in order. Nothing is discarded, so you can unpack the results directly.

**Synchronous**

		```python
		created, users = db.query("""
		    CREATE users CONTENT {"name": "Alice", "age": 30};
		    SELECT * FROM users;
		""").execute()

		# .first() would return only the CREATE result
		```

**Asynchronous**

		```python
		created, users = await db.query("""
		    CREATE users CONTENT {"name": "Alice", "age": 30};
		    SELECT * FROM users;
		""").execute()

		# .first() would return only the CREATE result
		```

`.query_raw()` returns those same per-statement results wrapped in the raw RPC envelope, with each statement's `status` and `time` alongside its `result`. Use it when you need that metadata, or when you want failed statements reported rather than raised.

## Learn more

- [Surreal API reference](/docs/reference/python/api/core/surreal.md) for complete method signatures and parameters
- [Data manipulation](/docs/reference/python/concepts/data-manipulation.md) for CRUD operations using dedicated methods
- [SurrealQL reference](/docs/reference/query-language.md) for the full query language documentation
- [Value types](/docs/reference/python/api/types.md) for the types returned by query methods

---

Source: https://surrealdb.com/docs/reference/python/concepts/live-queries

# Live queries

The Python SDK supports real-time live queries that stream changes from the database to your application.

The Python SDK supports real-time live queries that stream changes from the database directly to your application. When records matching a live query are created, updated, or deleted, the SDK delivers notifications through a generator that you can iterate over.

This page covers how to start live queries, subscribe to change notifications, and stop queries when they are no longer needed.

> [!NOTE]
> Live queries require a WebSocket connection (`ws://` or `wss://`). HTTP and embedded connections do not support live queries.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#live"><code>db.live(table, diff?)</code></a></td>
			<td scope="row" data-label="Description">Starts a live query on a table and returns a query UUID</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#subscribe-live"><code>db.subscribe_live(query_uuid)</code></a></td>
			<td scope="row" data-label="Description">Subscribes to notifications from a live query</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#kill"><code>db.kill(query_uuid)</code></a></td>
			<td scope="row" data-label="Description">Stops a running live query</td>
		</tr>
	</tbody>
</table>

## Starting a live query

The `.live()` method registers a live query on a table and returns a UUID that identifies the query. You can optionally pass `diff=True` to receive changes in [JSON Patch (RFC 6902)](https://jsonpatch.com/) format instead of full record snapshots.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("ws://localhost:8000") as db:
		    db.use("surrealdb", "docs")
		    db.signin({"username": "root", "password": "secret"})

		    query_uuid = db.live("users")
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("surrealdb", "docs")
		    await db.signin({"username": "root", "password": "secret"})

		    query_uuid = await db.live("users")
		```

To receive JSON Patch diffs instead of full records, pass `diff=True`.

```python
query_uuid = db.live("users", diff=True)
```

## Subscribing to changes

After starting a live query, call `.subscribe_live()` with the returned UUID to obtain a stream of notifications. The async variant returns an `AsyncGenerator` and the sync variant returns a `Generator`. Each notification is a dictionary containing `action` and `result` keys.

The `action` field is one of `"CREATE"`, `"UPDATE"`, or `"DELETE"`, and the `result` field contains the affected record (or the JSON Patch operations when `diff=True` was used).

To receive only some actions, filter on the server instead of checking `action` in the loop. A live query started from SurrealQL, as in [Subscribing to live queries from SurrealQL](#subscribing-to-live-queries-from-surrealql), can filter on `$event`, so `LIVE SELECT * FROM person WHERE $event = 'CREATE'` sends create notifications only.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("ws://localhost:8000") as db:
		    db.use("surrealdb", "docs")
		    db.signin({"username": "root", "password": "secret"})

		    query_uuid = db.live("users")

		    for notification in db.subscribe_live(query_uuid):
		        print(notification["action"])
		        print(notification["result"])
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("surrealdb", "docs")
		    await db.signin({"username": "root", "password": "secret"})

		    query_uuid = await db.live("users")

		    async for notification in db.subscribe_live(query_uuid):
		        print(notification["action"])
		        print(notification["result"])
		```

## Stopping a live query

When you no longer need to receive notifications, call `.kill()` with the query UUID to stop the live query on the server. This also ends the generator returned by `.subscribe_live()`.

**Synchronous**

		```python
		db.kill(query_uuid)
		```

**Asynchronous**

		```python
		await db.kill(query_uuid)
		```

## Subscribing to live queries from SurrealQL

You can also initiate a live query using a [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md) statement through the `.query()` method. Because `.query()` returns a builder whose `.execute()` yields one entry per statement, use `.first()` to get the bare UUID, which can then be passed to `.subscribe_live()` in the same way as a UUID returned by `.live()`.

This approach is useful when you need the filtering capabilities of SurrealQL, such as selecting specific fields or applying `WHERE` clauses.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("ws://localhost:8000") as db:
		    db.use("surrealdb", "docs")
		    db.signin({"username": "root", "password": "secret"})

		    query_uuid = db.query("LIVE SELECT * FROM users WHERE age > 18").first()

		    for notification in db.subscribe_live(query_uuid):
		        print(notification["action"])
		        print(notification["result"])
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("surrealdb", "docs")
		    await db.signin({"username": "root", "password": "secret"})

		    query_uuid = await db.query("LIVE SELECT * FROM users WHERE age > 18").first()

		    async for notification in db.subscribe_live(query_uuid):
		        print(notification["action"])
		        print(notification["result"])
		```

## Learn more

- [Surreal API reference](/docs/reference/python/api/core/surreal.md) for method signatures and parameters
- [Connecting to SurrealDB](/docs/reference/python/concepts/connecting-to-surrealdb.md) for WebSocket connection setup
- [SurrealQL LIVE SELECT](/docs/reference/query-language/statements/live-select.md) for the query language syntax

---

Source: https://surrealdb.com/docs/reference/python/concepts/multiple-sessions

# Multiple sessions

The Python SDK supports creating multiple isolated sessions within a single WebSocket connection.

The Python SDK supports creating multiple isolated sessions within a single WebSocket connection. Each session maintains its own namespace, database, and authentication state, allowing you to perform independent operations without opening additional connections.

This page covers how to create sessions, isolate their scope, authenticate independently, and close them when no longer needed.

> [!NOTE]
> Multiple sessions require a WebSocket connection (`ws://` or `wss://`). HTTP and embedded connections do not support sessions.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal.md#new-session"><code>db.new_session()</code></a></td>
			<td scope="row" data-label="Description">Creates a new isolated session on the current connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal-session.md#close-session"><code>session.close_session()</code></a></td>
			<td scope="row" data-label="Description">Closes the session and detaches it from the connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal-session.md#inherited-methods"><code>session.use(namespace, database)</code></a></td>
			<td scope="row" data-label="Description">Switches the session to a specific namespace and database</td>
		</tr>
	</tbody>
</table>

## Creating a session

Call `.new_session()` on an existing connection to create a new session. The async variant returns an `AsyncSurrealSession` and the sync variant returns a `BlockingSurrealSession`. Each session operates independently from the parent connection and other sessions.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("ws://localhost:8000") as db:
		    db.use("surrealdb", "docs")
		    db.signin({"username": "root", "password": "secret"})

		    session = db.new_session()
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("surrealdb", "docs")
		    await db.signin({"username": "root", "password": "secret"})

		    session = await db.new_session()
		```

A new session starts with the parent connection's identity, but nothing else from it. If the connection holds a token, `.new_session()` authenticates the session with that token, so the session begins as the same user. As the connection's namespace and database are not carried over, `.use()` can be called on the session, as the examples below do. The exception is a token whose own claims name a namespace and database, since those travel with the identity rather than with the connection.

## Isolating namespace and database

Each session can target a different namespace and database by calling `.use()`. Changes to one session's scope do not affect the parent connection or any other session.

**Synchronous**

		```python
		session_a = db.new_session()
		session_a.use("surrealdb", "docs")

		session_b = db.new_session()
		session_b.use("surrealdb", "staging")

		docs_users = session_a.select("users")
		staging_users = session_b.select("users")
		```

**Asynchronous**

		```python
		session_a = await db.new_session()
		await session_a.use("surrealdb", "docs")

		session_b = await db.new_session()
		await session_b.use("surrealdb", "staging")

		docs_users = await session_a.select("users")
		staging_users = await session_b.select("users")
		```

In the example above, `session_a` reads from the `docs` database while `session_b` reads from `staging`, both over the same underlying WebSocket connection.

## Independent authentication

Each session can authenticate as a different user. This is useful when you need to perform operations on behalf of multiple users without managing separate connections.

Signing in on a session replaces the identity it started with, so a session only acts as the connection's user until you sign it in as someone else.

**Synchronous**

		```python
		session_admin = db.new_session()
		session_admin.use("surrealdb", "docs")
		session_admin.signin({"username": "root", "password": "secret"})

		session_user = db.new_session()
		session_user.use("surrealdb", "docs")
		session_user.signin({
		    "namespace": "surrealdb",
		    "database": "docs",
		    "access": "account",
		    "variables": {
		        "email": "info@surrealdb.com",
		        "password": "123456",
		    },
		})

		all_records = session_admin.select("users")

		own_record = session_user.info()
		```

**Asynchronous**

		```python
		session_admin = await db.new_session()
		await session_admin.use("surrealdb", "docs")
		await session_admin.signin({"username": "root", "password": "secret"})

		session_user = await db.new_session()
		await session_user.use("surrealdb", "docs")
		await session_user.signin({
		    "namespace": "surrealdb",
		    "database": "docs",
		    "access": "account",
		    "variables": {
		        "email": "info@surrealdb.com",
		        "password": "123456",
		    },
		})

		all_records = await session_admin.select("users")

		own_record = await session_user.info()
		```

In the example above, `session_admin` has root-level access while `session_user` is authenticated as a record user. Each session's permissions are enforced independently.

## Closing a session

When a session is no longer needed, call `.close_session()` to detach it from the connection and release its resources. The parent connection and other sessions remain active.

**Synchronous**

		```python
		session.close_session()
		```

**Asynchronous**

		```python
		await session.close_session()
		```

Closing the parent connection automatically closes all sessions associated with it.

## Learn more

- [SurrealSession API reference](/docs/reference/python/api/core/surreal-session.md) for session method signatures
- [Transactions](/docs/reference/python/concepts/transactions.md) for transaction support within sessions
- [Authentication](/docs/reference/python/concepts/authentication.md) for signing in within sessions
- [Connecting to SurrealDB](/docs/reference/python/concepts/connecting-to-surrealdb.md) for WebSocket connection setup

---

Source: https://surrealdb.com/docs/reference/python/concepts/transactions

# Transactions

The Python SDK supports client-side transactions for executing multiple operations atomically over WebSocket connections.

The Python SDK supports client-side transactions that group multiple operations into a single atomic unit. All operations within a transaction either succeed together when committed or are rolled back entirely when cancelled. Transactions are scoped to a session and execute over a WebSocket connection.

This page covers how to create, execute, commit, cancel, and handle errors within transactions.

> [!NOTE]
> Transactions require a WebSocket connection (`ws://` or `wss://`) and must be created from a session. HTTP and embedded connections do not support transactions.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Method</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal-session.md#begin-transaction"><code>session.begin_transaction()</code></a></td>
			<td scope="row" data-label="Description">Begins a new transaction within the session</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal-transaction.md#commit"><code>txn.commit()</code></a></td>
			<td scope="row" data-label="Description">Commits all operations in the transaction, making changes permanent</td>
		</tr>
		<tr>
			<td scope="row" data-label="Method"><a href="/docs/reference/python/api/core/surreal-transaction.md#cancel"><code>txn.cancel()</code></a></td>
			<td scope="row" data-label="Description">Cancels the transaction and rolls back all changes</td>
		</tr>
	</tbody>
</table>

## Creating a transaction

To create a transaction, first open a session with `.new_session()` on the connection, then call `.begin_transaction()` on the session. The returned transaction object provides the same data manipulation methods as the main connection, but all operations are held until the transaction is committed or cancelled.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("ws://localhost:8000") as db:
		    db.use("surrealdb", "docs")
		    db.signin({"username": "root", "password": "secret"})

		    session = db.new_session()
		    session.use("surrealdb", "docs")

		    txn = session.begin_transaction()
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("surrealdb", "docs")
		    await db.signin({"username": "root", "password": "secret"})

		    session = await db.new_session()
		    await session.use("surrealdb", "docs")

		    txn = await session.begin_transaction()
		```

## Executing operations within a transaction

Once a transaction is created, use its methods - such as `.query()`, `.create()`, `.select()`, `.update()`, and `.delete()` - to perform operations. `.query()` returns a lazy builder, so remember to finish it with `.execute()` or `.first()`; nothing is sent to the server until you do. These operations are buffered within the transaction scope and are not visible to other connections or sessions until the transaction is committed.

**Synchronous**

		```python
		txn.create("users", {
		    "name": "Alice",
		    "email": "alice@example.com",
		    "age": 30,
		})

		txn.create("users", {
		    "name": "Bob",
		    "email": "bob@example.com",
		    "age": 25,
		})

		users = txn.query("SELECT * FROM users").first()
		```

**Asynchronous**

		```python
		await txn.create("users", {
		    "name": "Alice",
		    "email": "alice@example.com",
		    "age": 30,
		})

		await txn.create("users", {
		    "name": "Bob",
		    "email": "bob@example.com",
		    "age": 25,
		})

		users = await txn.query("SELECT * FROM users").first()
		```

Refer to the [SurrealTransaction API reference](/docs/reference/python/api/core/surreal-transaction.md) for the full list of methods available on the transaction object.

## Committing a transaction

Calling `.commit()` makes all operations in the transaction permanent. After committing, the changes become visible to other connections and sessions.

**Synchronous**

		```python
		txn.commit()
		```

**Asynchronous**

		```python
		await txn.commit()
		```

A transaction can only be committed once. After committing, the transaction object should not be reused.

## Cancelling a transaction

Calling `.cancel()` discards all operations in the transaction and rolls back any changes. The database state is restored to what it was before the transaction began.

**Synchronous**

		```python
		txn.cancel()
		```

**Asynchronous**

		```python
		await txn.cancel()
		```

## Handling errors in transactions

Use a `try`/`except` block to ensure that a transaction is cancelled if any operation fails. This prevents partial changes from being committed to the database.

**Synchronous**

		```python
		from surrealdb import Surreal

		with Surreal("ws://localhost:8000") as db:
		    db.use("surrealdb", "docs")
		    db.signin({"username": "root", "password": "secret"})

		    session = db.new_session()
		    session.use("surrealdb", "docs")

		    txn = session.begin_transaction()

		    try:
		        txn.create("users", {"name": "Alice", "age": 30})
		        txn.create("users", {"name": "Bob", "age": 25})
		        txn.commit()
		    except Exception:
		        txn.cancel()
		        raise
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("surrealdb", "docs")
		    await db.signin({"username": "root", "password": "secret"})

		    session = await db.new_session()
		    await session.use("surrealdb", "docs")

		    txn = await session.begin_transaction()

		    try:
		        await txn.create("users", {"name": "Alice", "age": 30})
		        await txn.create("users", {"name": "Bob", "age": 25})
		        await txn.commit()
		    except Exception:
		        await txn.cancel()
		        raise
		```

The `raise` at the end re-raises the original exception after cancelling the transaction, so the error is still visible to the caller.

## Learn more

- [SurrealTransaction API reference](/docs/reference/python/api/core/surreal-transaction.md) for transaction method signatures
- [SurrealSession API reference](/docs/reference/python/api/core/surreal-session.md) for session management
- [Multiple sessions](/docs/reference/python/concepts/multiple-sessions.md) for session setup
- [Error handling](/docs/reference/python/concepts/error-handling.md) for error recovery patterns

---

Source: https://surrealdb.com/docs/reference/python/concepts/value-types

# Value types

The Python SDK provides custom types for representing SurrealDB-specific values like record identifiers, durations, and geometry.

The Python SDK maps SurrealDB data types to Python types automatically. Standard Python types like `str`, `int`, `float`, `bool`, `None`, `dict`, and `list` map directly to their SurrealDB equivalents. For SurrealDB-specific types such as record identifiers, durations, and geometry, the SDK provides dedicated Python classes.

See the [Data types reference](/docs/reference/python/api/values/) for the full API details of each type.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Class</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/python/api/values/record-id.md"><code>RecordID(table_name, identifier)</code></a></td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/python/api/values/table.md"><code>Table(table_name)</code></a></td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/python/api/values/duration.md"><code>Duration(elapsed)</code></a></td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/python/api/values/datetime.md"><code>Datetime(dt)</code></a></td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/python/api/values/range.md"><code>Range(begin, end)</code></a></td>
		</tr>
		<tr>
			<td scope="row" data-label="Class"><a href="/docs/reference/python/api/values/geometry.md"><code>GeometryPoint(...)</code></a></td>
		</tr>
	</tbody>
</table>

## Type mapping between Python and SurrealDB

The following table shows how Python types correspond to SurrealDB types. Standard library types are used directly, while SurrealDB-specific types use the custom classes listed above.

| Python Type | SurrealDB Type |
|---|---|
| `str` | `string` |
| `int` | `int` |
| `float` | `float` |
| `bool` | `bool` |
| `None` | `NONE` / `NULL` |
| `bytes` | `bytes` |
| `UUID` | `uuid` |
| `Decimal` | `decimal` |
| `dict` | `object` |
| `list` | `array` |
| [`RecordID`](/docs/reference/python/api/values/record-id.md) | `record` |
| [`Table`](/docs/reference/python/api/values/table.md) | `table` |
| [`Duration`](/docs/reference/python/api/values/duration.md) | `duration` |
| [`Datetime`](/docs/reference/python/api/values/datetime.md) | `datetime` |
| [`Range`](/docs/reference/python/api/values/range.md) | `range` |
| [`Geometry*`](/docs/reference/python/api/values/geometry.md) | `geometry` |

## Working with record identifiers

A [`RecordID`](/docs/reference/python/api/values/record-id.md) uniquely identifies a single record in a table by combining a table name with an identifier value. You can construct one directly or parse it from a string.

```python
from surrealdb import RecordID

record = RecordID("users", "tobie")
print(record.table_name)  # "users"
print(record.id)          # "tobie"

parsed = RecordID.parse("users:tobie")
print(parsed.table_name)  # "users"
```

The identifier can be a string, integer, list, or any other supported value type. Use `RecordID` wherever the SDK expects a record reference, such as in `.select()`, `.create()`, or `.delete()`.

```python
from surrealdb import Surreal, RecordID

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    db.create(RecordID("users", "tobie"), {
        "name": "Tobie",
        "email": "tobie@surrealdb.com",
    })

    user = db.select(RecordID("users", "tobie"))
```

See the [RecordID reference](/docs/reference/python/api/values/record-id.md) for the complete API, including equality checks and Pydantic support.

## Working with durations

A [`Duration`](/docs/reference/python/api/values/duration.md) represents a time span with nanosecond precision. The most common way to create one is by parsing a human-readable string using SurrealDB's duration syntax.

```python
from surrealdb import Duration

d = Duration.parse("1h30m")
print(d.hours)    # 1.5
print(d.minutes)  # 90.0

d2 = Duration.parse("500ms")
print(d2.milliseconds)  # 500.0
```

You can also construct a `Duration` directly from nanoseconds and convert it back to a string.

```python
d = Duration(5_000_000_000)
print(d.seconds)      # 5.0
print(d.to_string())  # "5s"
```

Durations are useful for setting intervals, timeouts, and TTLs in your data.

```python
from surrealdb import Surreal, Duration

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    db.create("tasks", {
        "title": "Backup",
        "interval": Duration.parse("6h"),
    })
```

See the [Duration reference](/docs/reference/python/api/values/duration.md) for all unit properties and methods.

## Working with datetime values

A [`Datetime`](/docs/reference/python/api/values/datetime.md) wraps an ISO 8601 datetime string for use with SurrealDB's `datetime` type. It preserves the original string representation through serialisation and deserialisation.

```python
from surrealdb import Datetime

dt = Datetime("2025-06-01T09:00:00Z")
print(dt.dt)  # "2025-06-01T09:00:00Z"
```

Use `Datetime` when creating records that contain date or time fields.

```python
from surrealdb import Surreal, Datetime

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    db.create("events", {
        "title": "Launch",
        "scheduled_at": Datetime("2025-06-01T09:00:00Z"),
    })
```

See the [Datetime reference](/docs/reference/python/api/values/datetime.md) for the full API.

## Working with ranges

A [`Range`](/docs/reference/python/api/values/range.md) represents a bounded interval with inclusive or exclusive endpoints. Each bound is wrapped in a `BoundIncluded` or `BoundExcluded` to specify whether the boundary value is part of the range.

```python
from surrealdb import Range
from surrealdb.data.types.range import BoundIncluded, BoundExcluded

inclusive = Range(
    begin=BoundIncluded(1),
    end=BoundIncluded(10),
)

half_open = Range(
    begin=BoundIncluded(1),
    end=BoundExcluded(10),
)
```

Ranges are commonly used in query parameters to filter results within a specific interval.

```python
from surrealdb import Surreal, Range
from surrealdb.data.types.range import BoundIncluded

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    events = db.query(
        "SELECT * FROM events WHERE year IN $range",
        {"range": Range(BoundIncluded(2020), BoundIncluded(2025))},
    ).first()
```

See the [Range reference](/docs/reference/python/api/values/range.md) for the full API and bound types.

## Working with geometry types

The SDK provides seven GeoJSON-compatible geometry types for working with SurrealDB's spatial data: `GeometryPoint`, `GeometryLine`, `GeometryPolygon`, `GeometryMultiPoint`, `GeometryMultiLine`, `GeometryMultiPolygon`, and `GeometryCollection`.

The most common type is `GeometryPoint`, which represents a single geographic coordinate.

```python
from surrealdb import Surreal, GeometryPoint

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    db.create("locations", {
        "name": "London",
        "coordinates": GeometryPoint(-0.1278, 51.5074),
    })
```

More complex types compose from simpler ones - a `GeometryLine` is built from a list of points, a `GeometryPolygon` from a list of lines, and so on. See the [Geometry reference](/docs/reference/python/api/values/geometry.md) for constructors and examples of all seven types.

## Learn more

- [Data types reference](/docs/reference/python/api/values/) for complete API details
- [RecordID reference](/docs/reference/python/api/values/record-id.md) for record identifier API
- [Duration reference](/docs/reference/python/api/values/duration.md) for duration API
- [Python types](/docs/reference/python/api/types/) for Value and RecordIdType definitions
- [Data manipulation](/docs/reference/python/concepts/data-manipulation.md) for using types in queries

---

Source: https://surrealdb.com/docs/reference/python/installation

# Installation

In this section, you will learn how to install the Python SDK in your project.

This page shows how to install the Python SDK and import it into a project.

## Install the SDK

Install the [SurrealDB SDK](https://pypi.org/project/surrealdb/) from PyPI:

```bash
pip install surrealdb
```

If you want [pydantic](https://docs.pydantic.dev/) validation and serialisation support for `RecordID`, install the optional extra:

```bash
pip install surrealdb[pydantic]
```

## Import the SDK into your project

The SDK provides two entry points depending on whether you need synchronous or asynchronous access.

```python
from surrealdb import Surreal
```

For asynchronous applications using `asyncio`:

```python
from surrealdb import AsyncSurreal
```

Both `Surreal` and `AsyncSurreal` are factory functions that accept a connection URL and return the appropriate connection class based on the protocol scheme.

## Next steps

- [Getting started](/docs/languages/python.md) for a complete working example
- [Connecting to SurrealDB](/docs/reference/python/concepts/connecting-to-surrealdb.md) for connection options and protocols
- [Authentication](/docs/reference/python/concepts/authentication.md) for signing in and managing credentials

---

Source: https://surrealdb.com/docs/reference/query-language

# SurrealQL

SurrealQL statements, clauses, functions and language primitives. The full reference for the SurrealDB query language.

SurrealQL is SurrealDB's query language. Syntax is broadly SQL-like, with extensions for nested fields, graph edges, record IDs, and other SurrealDB-specific constructs.

For a guided introduction, see [Learn: Querying](/docs/learn/querying.md).

## Examples

A minimal query can follow a familiar SQL shape:

```surql
SELECT name,
       metadata
FROM   user
WHERE  age >= 18; 
```

Projections can include nested objects and graph traversals:

```surql
SELECT name,
       metadata.{
          date_registered,
          last_login
       },
       ->wrote->post AS posts
FROM user
WHERE age >= 18;
```

Further detail is organised under [Statements](/docs/reference/query-language/statements/overview.md) and the other sections in this reference.

## Clauses

- [Clauses](/docs/reference/query-language/clauses/overview.md) - the clauses statements share, such as `WHERE`, `LIMIT`, `START` and `OMIT`

## Language primitives

Reference pages for the pieces a query is built from:

- [Statements](/docs/reference/query-language/language-primitives/statements.md) - what a statement is, and how several combine into one query
- [Comments](/docs/reference/query-language/language-primitives/comments.md) - the three comment forms SurrealQL accepts
- [Data types](/docs/reference/query-language/language-primitives/data-types.md) - every value type, from records to geometries
- [Operators](/docs/reference/query-language/language-primitives/operators.md) - comparison, arithmetic, set and graph operators
- [Casting](/docs/reference/query-language/language-primitives/casting.md) - converting a value from one type to another
- [Idioms](/docs/reference/query-language/language-primitives/idioms.md) - path syntax for reaching into records and graphs
- [Parameters](/docs/reference/query-language/language-primitives/parameters.md) - built-in parameters and your own
- [Formatters](/docs/reference/query-language/language-primitives/formatters.md) - formatting datetimes and numbers
- [Record links](/docs/reference/query-language/language-primitives/record-links.md) - pointing at another record
- [Record references](/docs/reference/query-language/language-primitives/record-references.md) - links the database keeps in step for you

## Machine learning

- [ML functions](/docs/reference/query-language/functions/ml-functions/functions.md) - call a stored model from a query

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/explain

# EXPLAIN

The `EXPLAIN` clause is used to explain the plan used for a query.

The `EXPLAIN` clause is used to explain the plan used for a query. It is particularly useful when you want to understand how a query is executed and how it is optimised by the database.

When `EXPLAIN` is used, the statement returns the execution plan instead of the query result, showing how the query will be performed.

## Syntax

```syntax title="Clause Syntax"
@query EXPLAIN [FULL]
```

Using the `EXPLAIN` clause in addition to the `FULL` keyword is especially useful when you want to understand the performance of a query and can provide more details when debugging.

## Examples

For example, consider the performance of the following query when the field `email` is not indexed. We can see that the execution plan will iterate over the whole table.

```surql title="Index not used"
CREATE person:tobie SET
	name = "Tobie",
	address = "1 Bagshot Row",
	email = "tobie@surrealdb.com";

SELECT * FROM person WHERE email='tobie@surrealdb.com' EXPLAIN;
SELECT * FROM person WHERE email='tobie@surrealdb.com' EXPLAIN FULL;
```

```surql title="Output"
-------- Query --------

{
	attributes: {
		projections: '*'
	},
	children: [
		{
			attributes: {
				direction: 'Forward',
				predicate: "email = 'tobie@surrealdb.com'",
				table: 'person'
			},
			context: 'Db',
			operator: 'TableScan'
		}
	],
	context: 'Db',
	operator: 'SelectProject'
}

-------- Query --------

{
	attributes: {
		projections: '*'
	},
	children: [
		{
			attributes: {
				direction: 'Forward',
				predicate: "email = 'tobie@surrealdb.com'",
				table: 'person'
			},
			context: 'Db',
			metrics: {
				elapsed_ns: 66543,
				output_batches: 1,
				output_rows: 1
			},
			operator: 'TableScan'
		}
	],
	context: 'Db',
	metrics: {
		elapsed_ns: 8251,
		output_batches: 1,
		output_rows: 1
	},
	operator: 'SelectProject',
	total_rows: 1
}
```

On the other hand, here is the result when the field `email` is indexed. We can see that the execution plan will use the index to retrieve the record.

```surql title="Index used"
DEFINE INDEX fast_email ON TABLE person FIELDS email;

CREATE person:tobie SET
	name = "Tobie",
	address = "1 Bagshot Row",
	email = "tobie@surrealdb.com";

SELECT * FROM person WHERE email='tobie@surrealdb.com' EXPLAIN;
SELECT * FROM person WHERE email='tobie@surrealdb.com' EXPLAIN FULL;
```

```surql title="Output"
-------- Query --------

{
	attributes: {
		projections: '*'
	},
	children: [
		{
			attributes: {
				access: "= 'tobie@surrealdb.com'",
				direction: 'Forward',
				index: 'fast_email'
			},
			context: 'Db',
			operator: 'IndexScan'
		}
	],
	context: 'Db',
	operator: 'SelectProject'
}

-------- Query --------

{
	attributes: {
		projections: '*'
	},
	children: [
		{
			attributes: {
				access: "= 'tobie@surrealdb.com'",
				direction: 'Forward',
				index: 'fast_email'
			},
			context: 'Db',
			metrics: {
				elapsed_ns: 48624,
				output_batches: 1,
				output_rows: 1
			},
			operator: 'IndexScan'
		}
	],
	context: 'Db',
	metrics: {
		elapsed_ns: 4999,
		output_batches: 1,
		output_rows: 1
	},
	operator: 'SelectProject',
	total_rows: 1
}
```

### Bitmap index fusion

_(since v3.3.0)_

When a `WHERE` clause combines conditions that different indexes can answer, the plan can use all of those indexes instead of driving the query from one of them. Each index is read into a bitmap of the records that match it, the bitmaps are combined with set operations, and only the records left at the end are fetched. [How SurrealDB combines indexes](/docs/learn/querying/concepts-and-guides/query-optimisation.md#how-surrealdb-combines-indexes) explains when the planner chooses this plan.

The example below uses the [`EXPLAIN ANALYZE` statement](/docs/reference/query-language/statements/explain.md), because its text format shows the plan tree compactly. The `EXPLAIN` clause returns the same operators as nested objects.

```surql
DEFINE FIELD category ON article TYPE string;
DEFINE FIELD published ON article TYPE bool;
DEFINE INDEX idx_category ON article FIELDS category;
DEFINE INDEX idx_published ON article FIELDS published;

INSERT INTO article [
	{ id: 1, category: 'databases', published: true },
	{ id: 2, category: 'databases', published: false },
	{ id: 3, category: 'networking', published: true },
	{ id: 4, category: 'databases', published: true }
];

EXPLAIN ANALYZE SELECT id FROM article WHERE category = 'databases' AND published = true;
```

```surql title="Sample output"
"SelectProject [ctx: Db] [projections: id] {rows: 2, batches: 1, elapsed: 10.17µs}
    Filter [ctx: Db] [predicate: category = 'databases' AND published = true] {rows: 2, batches: 1, elapsed: 33.58µs}
        BitmapResolve [ctx: Db] [table: article] {rows: 2, batches: 1, elapsed: 322.25µs}
            BitmapAnd [ctx: Db] {rows: 2, batches: 0, elapsed: 0ns}
                BitmapIndexScan [ctx: Db] [index: idx_category, access: = 'databases'] {rows: 3, batches: 0, elapsed: 0ns}
                BitmapIndexScan [ctx: Db] [index: idx_published, access: = true] {rows: 3, batches: 0, elapsed: 0ns}

Total rows: 2"
```

Under `EXPLAIN ANALYZE`, the `rows` of a bitmap operator is the number of candidate records it produced. Each index matched three articles, the intersection left two, and those two are the only records that `BitmapResolve` fetched. The `Filter` above it checks the whole `WHERE` clause again on each fetched record, so a condition that was left out of the bitmaps is still applied.

| Operator | What it produces |
| --- | --- |
| `BitmapResolve` | Evaluates the bitmap tree beneath it, then fetches the remaining records of `table` for the rest of the plan. |
| `BitmapIndexScan` | The records that match the `access` condition in a standard or unique `index`. |
| `BitmapFullTextScan` | The records that match the `query` in a full-text `index`, for a `@@` condition. |
| `BitmapGraphScan` | The records connected to the `source` record through the `edges` tables in one hop, for a condition such as `->wrote->post CONTAINS post:one`. |
| `BitmapAnd` | The records present in every child. |
| `BitmapOr` | The records present in any child. |
| `BitmapAndNot` | The records in the first child that are not in the second. |

The same bitmap operators appear beneath `IndexCountScan` when a `count()` is answered from indexes alone, and beneath `KnnScan` when a vector search is [pre-filtered](#pre-filtered-knn-searches). A branch that stopped reading its index because it passed the `SURREAL_BITMAP_BRANCH_BUDGET` limit reports `dropped: true` under `EXPLAIN ANALYZE`, and a top-level `OR` plan that fell back to reading its indexes separately reports `union_tier: fallback` on `BitmapResolve`.

### K-nearest neighbours (KNN) searches

_(since v3.1.5)_

When a [KNN search](/docs/reference/query-language/language-primitives/operators.md#knn) over an indexed vector field is combined with an additional non-KNN condition, that condition is pushed *into* the index search so that non-matching candidates are rejected during the search rather than afterwards. The plan shows this as a `predicate` attribute on the `KnnScan` operator. To display the query plan, add the `EXPLAIN` clause to the end of a query as shown in the example below.

```surql title="Filtered KNN"
DEFINE INDEX idx_pt ON pts FIELDS point HNSW DIMENSION 4;
INSERT INTO pts [
	{ point: [1, 2, 3, 4], flag: true },
	{ point: [4, 3, 2, 1], flag: false },
	{ point: [3, 3, 3, 3], flag: true }
];

SELECT id, flag, vector::distance::knn() AS distance FROM pts
	WHERE flag = true AND point <|2,40|> [2, 3, 4, 5]
	ORDER BY distance EXPLAIN;
```

```surql title="Output"
{
	attributes: {
		projections: 'id, flag, distance'
	},
	children: [
		{
			attributes: {
				sort_keys: 'distance ASC'
			},
			children: [
				{
					attributes: {
						fields: 'distance = vector::distance::knn(...)'
					},
					children: [
						{
							attributes: {
								predicate: 'flag = true'
							},
							children: [
								{
									attributes: {
										dimension: '4',
										ef: '40',
										index: 'idx_pt',
										k: '2',
										predicate: 'flag = true'
									},
									context: 'Db',
									operator: 'KnnScan'
								}
							],
							context: 'Db',
							expressions: [
								{
									role: 'predicate',
									sql: 'flag = true'
								}
							],
							operator: 'Filter'
						}
					],
					context: 'Db',
					expressions: [
						{
							role: 'distance',
							sql: 'vector::distance::knn(...)'
						}
					],
					operator: 'Compute'
				}
			],
			context: 'Db',
			operator: 'SortByKey'
		}
	],
	context: 'Db',
	operator: 'SelectProject'
}
```

The innermost `KnnScan` operator carries the `predicate: 'flag = true'` attribute - the condition that is evaluated inside the index search. The same condition also appears on the `Filter` operator above it. Adding `EXPLAIN FULL` includes per-operator `metrics`. The query plan is identical for a DISKANN index. For a guided walkthrough, see [Filtering through vector search](/docs/learn/data-models/vector-search/similarity-search.md#how-the-filter-is-applied).

#### Pre-filtered KNN searches

_(since v3.3.0)_

When another index gives exactly the records that the extra condition matches, the plan pre-filters instead. That index is read before the search into an allow-list of matching records, which appears as a bitmap operator beneath `KnnScan`, and the vector search only admits records on the list. The `predicate` attribute then holds only the conditions that are still checked on each candidate. Under `EXPLAIN ANALYZE`, `KnnScan` also reports the `prefilter_tier` it used: `exact`, `graph`, `graph_unboosted` or `fallback`.

In the example above, `flag` has no index. Giving it a type and an index is enough for the condition to be pre-filtered:

```surql
DEFINE FIELD flag ON pts TYPE bool;
DEFINE INDEX idx_flag ON pts FIELDS flag;

EXPLAIN ANALYZE SELECT id, flag, vector::distance::knn() AS distance FROM pts
	WHERE flag = true AND point <|2,40|> [2, 3, 4, 5]
	ORDER BY distance;
```

```surql title="Sample output"
'SelectProject [ctx: Db] [projections: id, flag, distance] {rows: 2, batches: 1, elapsed: 3.96ms}
    SortByKey [ctx: Db] [sort_keys: distance ASC] {rows: 2, batches: 1, elapsed: 3.93ms}
        Compute [ctx: Db] [fields: distance = vector::distance::knn(...)] {rows: 2, batches: 1, elapsed: 3.50ms}
            Filter [ctx: Db] [predicate: flag = true] {rows: 2, batches: 1, elapsed: 3.46ms}
                KnnScan [ctx: Db] [index: idx_pt, k: 2, ef: 40, dimension: 4, prefilter_tier: exact] {rows: 2, batches: 1, elapsed: 3.43ms}
                    BitmapIndexScan [ctx: Db] [index: idx_flag, access: = true] {rows: 2, batches: 0, elapsed: 0ns}

Total rows: 2'
```

The `BitmapIndexScan` found the two records with `flag = true`, and `KnnScan` has no `predicate` because no condition is left to check during the search. An allow-list this small takes the `exact` tier, which computes the distance to each allowed record without using the vector index. [Pre-filtering with an index](/docs/learn/data-models/vector-search/similarity-search.md#pre-filtering-with-an-index) describes the four tiers and which conditions qualify.

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/fetch

# FETCH

The `FETCH` clause is used to fetch records from a table.

The `FETCH` clause is used to retrieve related records or data from other tables in a single query. This is particularly useful when you want to gather data that is linked through relationships ([record links](/docs/reference/query-language/language-primitives/record-links.md) or [graph edges](/docs/reference/query-language/statements/relate.md)) without having to perform multiple separate queries.

The `FETCH` clause predates and is functionally identical to the [`ALL`](/docs/reference/query-language/language-primitives/idioms.md#all-elements) idiom which is used by appending a `.*` to a related record.

## Example usage

Suppose you have a person table and a post table, where each post is related to a person. You can use the FETCH clause to retrieve a person along with their posts in a single query:

```surql
-- Using FETCH syntax
SELECT * FROM person FETCH posts;

-- Using .*
SELECT *, posts.* FROM person;
```

In this example, `posts` would be a related field in the `person` table that links to the `post` table. The `FETCH` clause allows you to retrieve all posts associated with each person in the result set.

The following example shows querying using `FETCH` or `.*` compared to selecting individual fields of a related record.

```surql
-- Fetch all fields from author and category
SELECT
	title,
	category,
	author
FROM article
FETCH author, category;
-- Use .* syntax to do the same
SELECT
	title,
	category.*,
	author.*
FROM article;
```

### Fetching more than one level

To fetch a record link inside a fetched record, name its path with dots. Each part of the path is fetched, so `FETCH author.role` fetches both the author and the author's role, and a path can go as deep as the links do. A link inside an array, such as `permissions` below, is fetched for every element.

Separate several paths with commas to fetch links to different tables in the same query. Each path can have its own depth: the query below follows `author` three levels, to the role's permissions, and `category` one level further, to its parent category.

```surql
CREATE permission:read SET name = 'read';
CREATE permission:write SET name = 'write';
CREATE role:admin SET name = 'Admin', permissions = [permission:read, permission:write];
CREATE person:one SET name = 'Ann', role = role:admin;
CREATE category:science SET name = 'Science';
CREATE category:tech SET name = 'Tech', parent = category:science;
CREATE post:one SET title = 'Hi', author = person:one, category = category:tech;

SELECT * FROM post:one FETCH author.role.permissions, category.parent;
```

```surql title="Output"
[
	{
		author: {
			id: person:one,
			name: 'Ann',
			role: {
				id: role:admin,
				name: 'Admin',
				permissions: [
					{ id: permission:read, name: 'read' },
					{ id: permission:write, name: 'write' }
				]
			}
		},
		category: {
			id: category:tech,
			name: 'Tech',
			parent: { id: category:science, name: 'Science' }
		},
		id: post:one,
		title: 'Hi'
	}
]
```

The same works when the links sit inside objects in an array. Both queries below return the residents with each person fetched:

```surql
CREATE person:ann SET name = 'Ann';
CREATE person:bob SET name = 'Bob';
CREATE flat:one SET residents = [
	{ person: person:ann, since: 2020 },
	{ person: person:bob, since: 2023 }
];

SELECT *, residents.{ since, person: person.* } AS residents FROM flat:one;
SELECT * FROM flat:one FETCH residents.person;
```

```surql title="Output"
[
	{
		id: flat:one,
		residents: [
			{ person: { id: person:ann, name: 'Ann' }, since: 2020 },
			{ person: { id: person:bob, name: 'Bob' }, since: 2023 }
		]
	}
]
```

The destructuring in the first query returns only the fields it names, so the other fields of each object, here `since`, have to be listed. `FETCH` keeps them without naming them. To get only the linked records, `residents.person.*` is enough.

### Fetching only some linked records

`FETCH` replaces every link in the field it names, and has no `LIMIT` of its own. To fetch a few of a record's posts, such as the latest two, use a subquery in the field list. The subquery can sort and limit, and `.*` is not needed because it selects the records themselves:

```surql
CREATE user:one SET name = 'Ann';
CREATE post:1 SET title = 'First', at = d'2026-01-01';
CREATE post:2 SET title = 'Second', at = d'2026-01-02';
CREATE post:3 SET title = 'Third', at = d'2026-01-03';
RELATE user:one->wrote->[post:1, post:2, post:3];

SELECT name, (SELECT title, at FROM ->wrote->post ORDER BY at DESC LIMIT 2) AS latest FROM user:one;
```

```surql title="Output"
[
	{
		latest: [
			{ at: d'2026-01-03T00:00:00Z', title: 'Third' },
			{ at: d'2026-01-02T00:00:00Z', title: 'Second' }
		],
		name: 'Ann'
	}
]
```

For a field that holds an array of record links, the subquery reads from the field of the current record: `(SELECT title FROM $parent.posts ORDER BY title LIMIT 2)`. A slice such as `posts[0..2].*` also works, but it takes the first links in the order they are stored rather than the ones a sort would choose.

## Without the `FETCH` clause

```surql
-- Access single field from author link
SELECT
	title,
	category,
	author.full_name AS author_name
FROM article;
```

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/from

# FROM

The `FROM` clause is used to specify the table or view to query.

The `FROM` clause is used to specify the table or view to query. It can also be used to specify targets beyond just a single table or record name.

## Syntax

```syntax title="Clause Syntax"
STATEMENT
    [FROM [ONLY] @targets;]
```

## Data retrieval

One of the most common use cases for the `FROM` clause is to specify the table or view to query. You can use this clause to pull data from single or multiple tables.

```surql title="All the ways you can use the FROM clause"
-- Selects all records from both 'user' and 'admin' tables.
SELECT * FROM user, admin;

-- Selects all records from the table named in the variable '$table',
-- but only if the 'admin' field of those records is true.
-- Equivalent to 'SELECT * FROM user WHERE admin = true'.
LET $table = "user";
SELECT * FROM type::table($table) WHERE admin = true;

-- Selects a single record from:
-- * the table named in the variable '$table',
-- * and the identifier named in the variable '$id'.
-- This query is equivalent to 'SELECT * FROM user:admin'.
LET $table = "user";
LET $id = "admin";
SELECT * FROM type::record($table, $id);

-- Selects all records for specific users 'tobie' and 'jaime',
-- as well as all records for the company 'surrealdb'.
SELECT * FROM user:tobie, user:jaime, company:surrealdb;

-- Selects records from a list of identifiers. The identifiers can be numerical,
-- string, or specific records such as 'person:lrym5gur8hzws72ux5fa'.
SELECT * FROM [3648937, "test", person:lrym5gur8hzws72ux5fa, person:4luro9170uwcv1xrfvby];

-- Selects data from an object that includes a 'person' key,
-- which is associated with a specific person record, and an 'embedded' key set to true.
SELECT * FROM { person: person:lrym5gur8hzws72ux5fa, embedded: true };

-- This command first performs a subquery, which selects all 'user' records and adds a
-- computed 'adult' field that is true if the user's 'age' is 18 or older.
-- The main query then selects all records from this subquery where 'adult' is true.
SELECT * FROM (SELECT age >= 18 AS adult FROM user) WHERE adult = true;
```

### Using the `ONLY` keyword

The `ONLY` keyword can be used to specify that only a single target should be retrieved as a single value instead of inside of an array. This is useful when you want to retrieve data from a single table or view. The `ONLY` keyword can be used in conjunction with the `LIMIT 1` clause to specify that only the specified value should be retrieved.

This keyword is particularly useful with SDKs as returning a single item makes deserialisation easier.

As record IDs are unique, `ONLY` can be used with record IDs without needing to specify `LIMIT 1`. The same goes for single values.

The following examples show when the `ONLY` keyword can be used on its own and when a `LIMIT 1` clause is required to ensure that only a single value is returned.

```surql
-- Create ten random user records along with `user:one`
CREATE |user:10|, user:one;

SELECT * FROM user:one;
//- Returns [user:one] inside an array


SELECT * FROM ONLY user:one;
//- Returns { id: user:one }

SELECT * FROM ONLY user;
//- Error: more than one user record returned

-- Success: LIMIT 1 guarantees a single value
SELECT * FROM ONLY user LIMIT 1;

-- Success: single value returned
SELECT * FROM ONLY 9;

SELECT * FROM ONLY 8, 9;
//- Error: more than one value returned

-- Success: LIMIT 1 guarantees a single value
SELECT * FROM ONLY 8, 9 LIMIT 1;
```

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/group

# GROUP

The `GROUP` clause is used to group records by one or more fields.

The `GROUP` clause is used to aggregate data based on one or more fields. It is particularly useful when you want to perform calculations on groups of data, such as counting the number of records, calculating averages, or finding sums for each group.

This is often used in reporting and data analysis to summarize data in a meaningful way. More specifically, it is used to:

- Aggregating data: When you need to calculate aggregate values like SUM, COUNT, AVG, MIN, or MAX for each group of data.
- Data summarisation: When you want to summarise data into categories or groups.
- Reporting: When generating reports that require grouped data, such as sales reports by region or department.

This clause is followed with either:

* `BY` to specify certain fields to group by, or
* `ALL` to group every selected record into a single aggregate.

## Syntax

```syntax title="Clause Syntax"
GROUP [ BY @fields | ALL ]
```

## Aggregate functions

A [number of functions](/docs/reference/query-language/functions/database-functions/#aggregate-functions) can be used inside a `GROUP BY` query to perform an operation on the data as a whole as opposed to per record.

For example, the [`math::sum()`](/docs/reference/query-language/functions/database-functions/math.md#mathsum) function can be used on an array of numbers to calculate their final sum.

```surql
math::sum([
    {
        name: "Billy",
        money: 10
    },
    { 
        name: "Tommy",
        money: 20
    }
].money);
//- 30
```

Attempting to use the same function inside a `SELECT` query will not work as `math::sum()` expects an array of numbers but only receives a single integer each time it is called.

```surql
SELECT 
    name AS names, 
    math::sum(money) AS money 
FROM [
    {
        name: "Billy",
        money: 10
    },
    { 
        name: "Tommy",
        money: 20
    }
];
```

If the data is aggregated with a `GROUP` clause, the query will no longer fail.

```surql
SELECT 
    name AS names,
    math::sum(money) AS money
FROM [
    {
        name: "Billy",
        money: 10
    },
    { 
        name: "Tommy",
        money: 20
    }
] GROUP ALL;
```

```surql title="Output"
[
	{
		money: 30,
		names: [
			'Billy',
			'Tommy'
		]
	}
]
```

### Joining strings in a group

[`array::join()`](/docs/reference/query-language/functions/database-functions/array.md#arrayjoin) is also an aggregate function, and joins the values of a field across the group into one string, like `GROUP_CONCAT` or `STRING_AGG` in SQL:

```surql
INSERT INTO person [
	{ id: person:alice, team: 'red', name: 'Alice' },
	{ id: person:bob, team: 'red', name: 'Bob' },
	{ id: person:carol, team: 'blue', name: 'Carol' }
];

SELECT team, array::join(name, ', ') AS names FROM person GROUP BY team;
```

```surql title="Output"
[
	{ names: 'Carol', team: 'blue' },
	{ names: 'Alice, Bob', team: 'red' }
]
```

`string::join()` is not an aggregate function, so `string::join(', ', name)` runs once for each record and returns an array. The method `name.join(', ')` also runs once for each record, and on a string it calls `string::join()` with that string as the separator, which returns `', '`.

The full list of aggregate functions is on the [functions overview](/docs/reference/query-language/functions/database-functions/#aggregate-functions).

## Longer example

```surql
SELECT
    product_id,
    region,
    math::sum(amount) AS total_sales
FROM
    sales
GROUP BY
    product_id, region;
```

Explanation:
- `SELECT product_id, region, math::sum(amount) AS total_sales`: This selects the `product_id` and `region` fields and calculates the total sales amount for each group. The `AS` clause is used to rename the calculated field to `total_sales`.

- `FROM sales`: This specifies the table from which to retrieve the data. Using the `FROM` clause, we specify the table `sales` to retrieve the data from.

- `GROUP BY product_id, region`: This groups the results by product_id and region, so the `math::sum()` function calculates the total sales for each unique combination of product_id and region.

This query will return a result where each record represents a unique combination of `product_id` and `region`, along with the total sales amount for that combination. This is useful for understanding how different products are performing in different regions.

```surql
		SELECT
	count() AS total,
	math::mean(age) AS average_age,
	gender,
	country
FROM rams
GROUP BY gender, country;
```

## What a projection returns

After `GROUP`, each result stands for many records rather than one. A projection therefore has a single value to return only when that value is the same for every record in the group. Where it differs from record to record, SurrealDB returns all of them in an array instead of picking one.

Three rules decide which happens:

1. A **group key** is one value, because grouping is what made every record in the group share it.
2. An **aggregate name** always aggregates, including over a constant.
3. **Everything else** depends on whether the expression reads the record. An expression that does not is evaluated once and gives one value; an expression that does gives an array holding one element per record.

The group key is the field named in `GROUP BY`. A record id is not a group key, so projecting `id` collects it like any other expression that reads the record:

```surql
CREATE person:alice SET age = 30, name = 'Alice';
CREATE person:bob SET age = 30, name = 'Bob';

SELECT
    -- Rule 1, a group key: one value, shared by every record in the group
    age,
    -- Rule 2, an aggregate name: aggregates even over a constant, so this counts the records
    math::sum(1) AS total,
    -- Rule 3, does not read the record: evaluated once
    4 + 3 AS constant,
    -- Rule 3, reads the record: one element per record
    name,
    -- Rule 3, and the reason a record id is not a group key
    id
FROM person GROUP BY age;
```

```surql title="Output"
[
	{
		age: 30,
		constant: 7,
		id: [
			person:alice,
			person:bob
		],
		name: [
			'Alice',
			'Bob'
		],
		total: 2
	}
]
```

A collected value is an array, not a [set](/docs/reference/query-language/language-primitives/data-types/sets.md): it keeps duplicates, so two records named `'Alice'` in one group give `['Alice', 'Alice']`. Its order follows the record ids, which is why the example above gives its own ids rather than letting SurrealDB generate them. Use [`array::group()`](/docs/reference/query-language/functions/database-functions/array.md#arraygroup) where the unique values are what you want.

Some expressions count as reading the record because the engine cannot see through them: a call to a user-defined function, whose body may reach the record through `$parent`; `type::field()` and the index functions behind `@@` and `<|...|>`, which are bound to the record; and any function that writes, whose number of effects depends on how often it runs.

> [!NOTE]
> Standard SQL raises an error for the record-reading branch of rule 3, on the grounds that a projection would otherwise have more than one value to return. SurrealDB collects the values into an array instead, which keeps every value and stays deterministic.

## Latest record per group

When you need the most recently modified record for each value of a field, group by that field and use [`.map()`](/docs/reference/query-language/functions/database-functions/array.md#arraymap) to run a nested `SELECT` per group:

```surql
(SELECT id, role FROM person GROUP BY role).map(|$o| {
    SELECT * FROM ONLY $o.id ORDER BY modified_at DESC LIMIT 1
});
```

`GROUP BY` collects record ids into an array, after which the inner query orders those records and returns the latest. For a worked example with sample data, see [Latest record per group](/docs/learn/querying/concepts-and-guides/subqueries-and-advanced-patterns.md#latest-record-per-group).

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/limit

# LIMIT

The `LIMIT` clause is used to limit the number of records returned by a query.

The `LIMIT` clause is used to limit the number of records returned by a query. It is particularly useful when you want to retrieve a specific number of records from a table.

## Syntax

```syntax title="Clause Syntax"
LIMIT @number
```

## Examples

```surql
-- Select the first 10 records
SELECT * FROM person LIMIT 10;

-- Start at record 50 and select the following 10 records
SELECT * FROM person LIMIT 10 START 50;
```

```surql
-- Select the first 5 records from the array
SELECT * FROM [1,2,3,4,5,6,7,8,9,10] LIMIT 5 START 4;
```

```surql title="Output"
[
	5,
	6,
	7,
	8,
	9
]
```

The `LIMIT` clause followed by `1` is often used along with the `ONLY` clause to satisfy the requirement that only up to a single record can be returned.

```surql
-- Record IDs are unique so guaranteed to be no more than 1
SELECT * FROM ONLY person:jamie;

-- Error because no guarantee that this will return a single record
SELECT * FROM ONLY person WHERE name = "Jaime";

-- Add `LIMIT 1` to ensure that only up to one record will be returned
SELECT * FROM ONLY person WHERE name = "Jaime" LIMIT 1;
```

## Use in pagination

When using the `LIMIT` clause, it is possible to paginate results by using the `START` clause to start from a specific record from the result set. The `START` count starts from 0.

This pattern is most often used through SDKs to avoid sending messages that exceed a certain size to other pieces of software, and so on.

The following pseudocode demonstrates the most common pattern seen when `START` and `LIMIT` are used together.

```rust
let current = 0;
loop {
    let query = db.query(SELECT * FROM person START {current} LIMIT 100);
    if query.is_empty() {
        break;
    } else {
        query.send_to_app();
        current += 100;
    }
}
```

`START` reads the records it skips and then discards them, so a page far into a large result costs more than the first page.

### Total count next to a page

A paginated API often returns the total number of matching records together with one page of them. `LIMIT` and `START` do not change what `count()` sees, so count the same `WHERE` condition in a second statement. An object can hold both results in one response:

```surql
LET $min_age = 18;

{
	total: (SELECT count() FROM person WHERE age >= $min_age GROUP ALL)[0].count,
	page: (SELECT name, age FROM person WHERE age >= $min_age ORDER BY name LIMIT 25 START 50)
};
```

When no record matches, `total` is `0` and `page` is an empty array. Counting a whole table with no `WHERE` is faster with a [`COUNT` index](/docs/reference/query-language/functions/database-functions/count.md#using-a-count-index-with-count).

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/omit

# OMIT

The `OMIT` clause is used to omit fields from the result set 

The `OMIT` clause is used to omit fields from the result set which can be particularly useful when querying large datasets.

## Syntax

```syntax title="Clause Syntax"
OMIT @fields FROM @table
```

## Examples

```surql
CREATE person:tobie SET
	name = 'Tobie',
	password = '123456',
	opts.security = 'secure',
	opts.enabled = true;
CREATE person:jaime SET
	name = 'Jaime',
	password = 'asdfgh',
	opts.security = 'secure',
	opts.enabled = false;

SELECT * FROM person;
-- Omit the password field and security field in the options object
SELECT * OMIT password, opts.security FROM person;

-- Using destructuring syntax
SELECT * OMIT password, opts.{ security, enabled } FROM person;
```

```surql title= "Return fields"
-------- Query 3 (132.138µs) --------

[
	{
		id: person:jaime,
		name: 'Jaime',
		opts: {
			enabled: false,
			security: 'secure'
		},
		password: 'asdfgh'
	},
	{
		id: person:tobie,
		name: 'Tobie',
		opts: {
			enabled: true,
			security: 'secure'
		},
		password: '123456'
	}
]

-------- Query 4 (61.876µs) --------

[
	{
		id: person:jaime,
		name: 'Jaime',
		opts: {
			enabled: false
		}
	},
	{
		id: person:tobie,
		name: 'Tobie',
		opts: {
			enabled: true
		}
	}
]

-------- Query 5 (52.152µs) --------

[
	{
		id: person:jaime,
		name: 'Jaime',
		opts: {}
	},
	{
		id: person:tobie,
		name: 'Tobie',
		opts: {}
	}
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/order

# ORDER

The `ORDER` clause specifies the sort order of the records in a table.

To sort records, SurrealDB allows ordering on multiple fields and nested fields. Use the `ORDER` clause to specify a comma-separated list of field names that should be used to order the resulting records.

While not necessary to parse, the `ORDER` clause can be followed with `BY` to make queries more readable.

The `ASC` and `DESC` keywords can be used to specify whether results should be sorted in an ascending or descending manner. The `COLLATE` keyword can be used to use Unicode collation when ordering text in string values, ensuring that different cases, and different languages are sorted in a consistent manner. Finally, the `NUMERIC` can be used to correctly sort text which contains numeric values.

`COLLATE` can also be used to order by lexical instead of unicode order. For example, 'á' comes after 'z' by default (Unicode sorting) but with `COLLATE` 'á' comes before 'z'.

From SurrealDB 3.3, sort fields do not need to appear in the `SELECT` list. Sorting runs before projection, so you can order by a field that is omitted from the result:

```surql
-- Sort by `at` without returning it
SELECT event, subject FROM audit_log ORDER BY at;
```

The clause takes field paths rather than expressions, so `ORDER BY string::len(name)` does not parse. To sort on a computed value, alias it in the `SELECT` list and order by that alias:

```surql
SELECT name, string::len(name) AS length FROM person ORDER BY length DESC;
```

The same applies to a field whose name is held in a parameter. `ORDER BY $field` does not parse, because the clause takes a field path and not a value. Select the field with [`type::field()`](/docs/reference/query-language/functions/database-functions/type.md#typefield) under an alias and order by the alias. A dotted path such as `'address.city'` works as well:

```surql
CREATE person:1 SET name = 'Boromir', age = 30;
CREATE person:2 SET name = 'Allanon', age = 40;
CREATE person:3 SET name = 'Cyndane', age = 20;

LET $field = 'age';
SELECT name, type::field($field) AS sort_key FROM person ORDER BY sort_key DESC;
```

```surql title="Output"
[
	{ name: 'Allanon', sort_key: 40 },
	{ name: 'Boromir', sort_key: 30 },
	{ name: 'Cyndane', sort_key: 20 }
]
```

The sort direction cannot come from a parameter either, so `ASC` or `DESC` is written in the query. To leave the alias out of the result, select from this query in an outer `SELECT` that names the fields to return.

`SPLIT` and `GROUP` still require their fields to appear in the selection.

## Syntax

```syntax title="Clause Syntax"
[ ORDER [ BY ] 
	@field [ COLLATE ] [ NUMERIC ] [ ASC | DESC ], ...
	| RAND()
]
```

## Examples

```surql
SELECT * FROM <table> ORDER BY <field> ASC;
```

```surql 
-- Order records randomly
SELECT * FROM <table> ORDER BY rand();

-- Order records descending by a single field
SELECT * FROM <table> ORDER BY <field> DESC;

-- Order records by multiple fields independently
SELECT * FROM <table> ORDER BY <field> ASC, <field2> DESC;

-- Order text fields with lexical collation instead of Unicode order
SELECT * FROM <table> ORDER BY <field> COLLATE ASC;

-- Order text fields with which include numeric values
SELECT * FROM <table> ORDER BY <field> NUMERIC ASC;

-- COLLATE and NUMERIC can be used together
SELECT * FROM <table> ORDER BY <field> COLLATE NUMERIC ASC;
```

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/overview

# Clauses

Index of SurrealQL clauses including FROM, WHERE, FETCH, ORDER, LIMIT, GROUP and EXPLAIN for query shaping.

In SurrealQL, clauses can be used to alter the way a query is executed. They are used in the following ways:

- [`EXPLAIN`](/docs/reference/query-language/clauses/explain.md): Explain the query plan.
- [`FETCH`](/docs/reference/query-language/clauses/fetch.md): Fetch all the fields of related records.
- [`FROM`](/docs/reference/query-language/clauses/from.md): Specify the table(s) or other target(s) to query from.
- [`GROUP`](/docs/reference/query-language/clauses/group.md): Group the results by a set of fields.
- [`LIMIT`](/docs/reference/query-language/clauses/limit.md): Limit the number of results.
- [`OMIT`](/docs/reference/query-language/clauses/omit.md): Omit related records.
- [`ORDER`](/docs/reference/query-language/clauses/order.md): Specify the sort order of the results.
- [`SPLIT`](/docs/reference/query-language/clauses/split.md): Split an array field so that each element becomes its own record in the result.
- [`START`](/docs/reference/query-language/clauses/start.md): Return the results of a query starting from a certain point.
- [`WHERE`](/docs/reference/query-language/clauses/where.md): Specify a condition that acts as a filter.
- [`WITH`](/docs/reference/query-language/clauses/with.md): Replace the default table iterator with an index iterator.

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/split

# SPLIT

The SPLIT clause in SurrealQL is used to split the results of a query based on a specific field, particularly when dealing with arrays.

The `SPLIT` clause in SurrealQL is used to split the results of a query based on a specific field, particularly when dealing with arrays. This is useful in scenarios where you want to treat each element of an array as a separate record in the result. It can be particularly helpful in data analysis contexts where you need to work with individual elements of an array separately.

## Syntax

```syntax title="Clause Syntax"
SPLIT [ON] @field
```

Suppose you have a user table with a field emails that contains an array of email addresses for each user. You want to list each email address as a separate record.

Here's how you can use the SPLIT clause in SurrealQL:

```surql
CREATE user SET
    name = "John Doe",
    emails = ["john@example.com", "doe@example.com"];

-- Split the results by each value in the emails array
SELECT * FROM user SPLIT emails;
```

Explanation:
- `CREATE user SET ...`: This creates a user record with a name and an array of email addresses.
- `SELECT * FROM user SPLIT emails`: This query selects all fields from the user table and splits the results based on the emails field. Each email address in the `emails` array will now be in a field of the same name that only contains a single value.

Output:
The output of the query will be:

```surql
[
	{
		emails: 'john@example.com',
		id: user:unjgil312jvvxfbdj706,
		name: 'John Doe'
	},
	{
		emails: 'doe@example.com',
		id: user:unjgil312jvvxfbdj706,
		name: 'John Doe'
	}
]
```

## Using `SPLIT` to restructure collected paths

One practical use case with `SPLIT` is returning every possible combination of the relations inside multiple graph paths. For instance, take the following data below that represents the relations between Canada the country, its provinces, and their cities.

```surql
CREATE country:canada;
CREATE province:bc, province:alberta;
CREATE city:vancouver, city:victoria, city:edmonton, city:calgary;

RELATE [city:vancouver, city:victoria]->in->province:bc;
RELATE [city:edmonton, city:calgary]->in->province:alberta;
RELATE [province:bc, province:alberta]->in->country:canada;
```

A graph query on both of these paths shows all of the provinces and cities.

```surql
SELECT 
    id AS country,
    <-in<-province AS provinces,
    <-in<-province<-in<-city AS cities FROM ONLY country:canada;
```

```surql title="Output"
{
	cities: [
		city:calgary,
		city:edmonton,
		city:vancouver,
		city:victoria
	],
	country: country:canada,
	provinces: [
		province:alberta,
		province:bc
	]
}
```

Using `SPLIT` in this case transforms the output from a collection of paths centred on the `country:canada` record into an array of objects, each representing every possible combination of cities and provinces inside the country.

```surql
SELECT
    id AS country,
    <-in<-province AS province,
    <-in<-province<-in<-city AS city FROM country:canada
    SPLIT city, province;
```

```surql title="Output"
[
	{
		city: city:calgary,
		country: country:canada,
		province: province:alberta
	},
	{
		city: city:calgary,
		country: country:canada,
		province: province:bc
	},
	{
		city: city:edmonton,
		country: country:canada,
		province: province:alberta
	},
	{
		city: city:edmonton,
		country: country:canada,
		province: province:bc
	},
	{
		city: city:vancouver,
		country: country:canada,
		province: province:alberta
	},
	{
		city: city:vancouver,
		country: country:canada,
		province: province:bc
	},
	{
		city: city:victoria,
		country: country:canada,
		province: province:alberta
	},
	{
		city: city:victoria,
		country: country:canada,
		province: province:bc
	}
]
```

An example of the same query then mapped into an array of unique keys for serialisation:

```surql
(SELECT
    id,
    <-in<-province AS province,
    <-in<-province<-in<-city AS city FROM country:canada
    SPLIT city, province)
.map(|$obj| 
    <string>$obj.id.id() 
    + '|' 
    + <string>$obj.province.id() 
    + '|' 
    + <string>$obj.city.id()
);
```

```surql title="Output"
[
	'canada|alberta|calgary',
	'canada|bc|calgary',
	'canada|alberta|edmonton',
	'canada|bc|edmonton',
	'canada|alberta|vancouver',
	'canada|bc|vancouver',
	'canada|alberta|victoria',
	'canada|bc|victoria'
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/start

# START

The `START` clause is used to set the point at which to return results of a query

The `START` clause is used to set the index at which to start returning results of a query, with 0 as the starting point.

This simple query demonstrates how adding `START 2` will skip over the numbers 1 and 2, returning the output `[3, 4, 5]`.

```surql
SELECT * FROM 1, 2, 3, 4, 5 START 2;
```

This clause is most often paired with `LIMIT` to perform pagination, to avoid sending messages that exceed a certain size to other pieces of software, and so on.

The following pseudocode demonstrates the most common pattern seen when `START` and `LIMIT` are used together.

```rust
let current = 0;
loop {
    let query = db.query(SELECT * FROM person START {current} LIMIT 100);
    if query.is_empty() {
        break;
    } else {
        query.send_to_app();
        current += 100;
    }
}
```

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/where

# WHERE

The `WHERE` clause can be used to specify a condition that acts as a filter.

The `WHERE` clause can be used to specify a condition that acts as a filter. You can use the `WHERE` clause to either filter the result of the FROM clause in a `SELECT` statement or specify which records to operate on in an `UPDATE`, `MERGE`, or `DELETE` statement.

It can also be used in special cases when working with conditions in [`DEFINE FUNCTION`](/docs/reference/query-language/statements/define/function.md) statements or when asserting access control in [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) & [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statements.

## Syntax

```syntax title="Clause Syntax"
STATEMENT
    [WHERE condition;]

```

## Conditional record selection

The most common use case for the `WHERE` clause is to filter the result of the `SELECT` statement. It is particularly useful when you want to select a subset of records from a table based on a condition.

```surql
SELECT @fields FROM <TABLE_NAME> WHERE <CONDITION> = <VALUE>;
```

When fetching records from a table, the `WHERE` clause is used to filter the records that are returned.

### Optional filters

A search form often has filters that the user may or may not fill in. One query can handle every combination: pair each condition with a check that its parameter is `NONE`, so that an unset parameter makes its condition true and filters nothing.

```surql
CREATE product:1 SET name = 'mug', category = 'kitchen', price = 8;
CREATE product:2 SET name = 'pan', category = 'kitchen', price = 30;
CREATE product:3 SET name = 'lamp', category = 'home', price = 25;

LET $category = 'kitchen';

SELECT VALUE name FROM product
WHERE ($category IS NONE OR category = $category)
  AND ($max_price IS NONE OR price <= $max_price)
ORDER BY name;
```

```surql title="Output"
['mug', 'pan']
```

With neither parameter set, the same query returns all three products. With both set (`$max_price = 10` as well), it returns only `mug`. A parameter that was never defined is `NONE`, so the query works whether a client omits the variable or sends `NONE` for it.

## Conditional record alteration

The `WHERE` clause can also be used to specify which records to operate on in an `UPDATE`, `MERGE`, or `DELETE` statement.

```surql
UPDATE [TABLE_NAME] SET [FIELDS] WHERE [CONDITION] = [VALUE];
```

## Setting conditions in `DEFINE FUNCTION` statements

```surql
-- Define a function that checks if a relation exists between two nodes
DEFINE FUNCTION fn::relation_exists(
	$in: record,
	$tb: string,
	$out: record
) -> bool {
	-- Check if a relation exists between the two nodes.
	LET $results = SELECT VALUE id FROM type::table($tb) WHERE in = $in
	  AND out = $out;
	-- Return true if a relation exists, false otherwise
    RETURN array::len($results) > 0;
};
```

## Setting permissions conditions in `DEFINE TABLE` statements

The `WHERE` clause can be used to specify the conditions for the permissions of a table and based on the conditions, the permissions are applied to the table CRUD operations.

```surql
-- Specify access permissions for the 'post' table
DEFINE TABLE post SCHEMALESS
	PERMISSIONS
		FOR select
			-- Published posts can be selected
			WHERE published = true
			-- A user can select all their own posts
			OR user = $auth.id
		FOR create, update
			-- A user can create or update their own posts
			WHERE user = $auth.id
		FOR delete
			-- A user can delete their own posts
			WHERE user = $auth.id
			-- Or an admin can delete any posts
			OR $auth.admin = true
;
```

```surql
-- Define a relation table, and constrain the type of relation which can be stored
DEFINE TABLE assigned_to SCHEMAFULL TYPE RELATION IN tag OUT sticky
    PERMISSIONS
        FOR create, select, update, delete
            WHERE in.owner == $auth.id AND out.author == $auth.id;
```

---

Source: https://surrealdb.com/docs/reference/query-language/clauses/with

# WITH

The `WITH` clause is used to select records from a table with an index, which is a pre-computed lookup table for faster queries.

When retrieving data from a table, the query planner can replace the standard table iterator with one or several index iterators based on the structure and requirements of the query. This is particularly useful when querying large datasets, as it can significantly reduce the time it takes to retrieve the data.

However, there may be situations where manual control over these potential optimisations is desired or required.

The `WITH` clause is used to replace the default table iterator with an index iterator. In cases where the cardinality of an index can be high, potentially even equal to the number of records in the table, the sum of the records iterated by several indexes may end up being larger than the number of records obtained by iterating over the table.

In such cases, if there are different index possibilities, the most probable optimal choice would be to use the index known with the lowest cardinality.

The query planner can replace the standard table iterator with one or several index iterators based on the structure and requirements of the query.

> [!NOTE]
> If you are using a `SELECT` statement, the `WITH` clause is used to specify the index to use for the query. You can define an index using the [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md) statement. Also see the [`DEFINE ANALYZER`](/docs/reference/query-language/statements/define/analyzer.md) statement for more information on optimising query performance with full-text search.

## Syntax

```syntax title="Clause Syntax"
[ WITH [ NOINDEX | INDEX @indexes ... ]]
```

This clause can be used in the following ways:

- `WITH NOINDEX`: forces the query planner to use the table iterator. (Default)
- `WITH INDEX @indexes`: restricts the query planner to using only the specified index(es)

```surql
-- forces the query planner to use the specified index(es):
SELECT * FROM person
WITH INDEX ft_email
WHERE
	email = 'tobie@surrealdb.com' AND
	company = 'SurrealDB';

-- forces the usage of the table iterator
SELECT name FROM person WITH NOINDEX WHERE job = 'engineer'
  AND gender = 'm';
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions

# Database Functions

Built-in SurrealQL database functions and constants, along with JavaScript and SurrealML functions.

Database functions are SurrealQL's built-in, namespaced helpers (`string::split()`, `math::mean()`, `time::now()`, and so on). They run inside the database engine and are the usual choice for everyday querying and data shaping.

SurrealQL also supports other kinds of callable logic:

- [JavaScript functions](/docs/reference/query-language/scripting/overview.md) - embedded scripts when the JavaScript runtime is enabled; see the scripting docs for definitions, context, and limits.
- [SurrealML functions](/docs/explore/ml-models.md) - helpers used with SurrealML.

Several function families transform values across representation boundaries (JSON, CBOR, tokens, runtime query strings). See [Representations and codecs](/docs/learn/querying/concepts-and-guides/representations-and-codecs.md) for when to use each.

The table below lists all of SurrealDB's function modules, grouped by purpose and data type, with short examples and links to detailed documentation.

<table>
	<thead>
		<tr>
			<th scope="col">
				Function
			</th>
			<th scope="col">Description and Example</th>
		</tr>
	</thead>
	<tbody>
	<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/api.md">
					<code>API</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used to add middleware to a defined API endpoint.

				Example: <code>api::timeout(1s)</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/array.md">
					<code>Array</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when working with, and
				manipulating arrays of data.

				Example: <code>array::len([1,2,3])</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/bytes.md">
					<code>Bytes</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when working with bytes in
				SurrealQL.

				Example: <code>bytes::len("SurrealDB".to_bytes());</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/count.md">
					<code>Count</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				This function can be used when counting field values and
				expressions.

				Example: <code>count([1,2,3])</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/crypto.md">
					<code>Crypto</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when hashing data, encrypting
				data, and for securely authenticating users into the
				database.

				Example: <code>crypto::argon2::generate("MyPaSSw0RD")</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/duration.md">
					<code>Duration</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				Functions and constants for converting between numeric values
				and duration data.

				Example: <code>duration::days(90h30m)</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/encoding.md">
					<code>Encoding</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				Encode and decode values as JSON, CBOR, or Base64.
				Example: <code>encoding::cbor::encode({'foo': 'bar'})</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/eval.md">
					<code>Eval</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				Evaluate a SurrealQL or ISO GQL (Cypher Query Language) query string at runtime inside the
				caller's transaction.
				Example: <code>eval::surql("RETURN 1 + 1")</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/file.md">
					<code>Files</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used to work with files.
				Example: <code>f"my_bucket:/my_book.txt".get()</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/geo.md">
					<code>Geo</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when working with and analysing
				geospatial data.

				Example:{' '}
				<code>geo::distance((-0.04, 51.55), (30.46, -17.86))</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/http.md">
					<code>HTTP</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when opening and submitting
				remote web requests, and webhooks.

				Example: `http::get('https://surrealdb.com')`
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/math.md">
					<code>Math</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				Functions and constants for
				analysing numeric data and numeric collections.

				Example:{' '}
				<code>
					math::max([ 26.164, 13.746189, 23, 16.4, 41.42 ])
				</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/not.md">
					<code>Not</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				This function reverses the truthiness of a value.

				Example: <code>not(true)</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/object.md">
					<code>Object</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when working with, and
				manipulating data objects.

				Example:{' '}
				<code>
					object::from_entries([[ "a", 1 ],[ "b", true ]])
				</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/parse.md">
					<code>Parse</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when parsing email addresses and
				URL web addresses.

				Example:{' '}
				<code>
					parse::url::domain("http://127.0.0.1/index.html")
				</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/rand.md">
					<code>Rand</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when generating random data
				values.

				Example:{' '}
				<code>
					rand::enum('one', 'two', 3, 4.15385, 'five', true)
				</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/record.md">
					<code>Record</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used to retrieve specific metadata
				from a SurrealDB Record ID.

				Example: <code>record::id(person:tobie)</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/search.md">
					<code>Search</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions are used in conjunction with the{' '}
				<code>@@</code> operator (the 'matches' operator) to either
				collect the relevance score or highlight the searched
				keywords within the content.

				Example:{' '}
				<code>
					SELECT search::score(1) AS score FROM book WHERE title
					@1@ 'rust web'
				</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/sequence.md">
					<code>Sequence</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used to work with a defined sequence.

				Example: <code>sequence::nextval('mySeq2')</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/session.md">
					<code>Session</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions return information about the current
				SurrealDB session.

				Example: <code>session::db()</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/set.md">
					<code>Set</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when working with, and
				manipulating sets of data.

				Example: <code>`set::len({1,2,3})`</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/sleep.md">
					<code>Sleep</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				This function can be used to introduce a delay or pause in
				the execution of a query or a batch of queries for a
				specific amount of time.

				Example: <code>sleep(900ms)</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/string.md">
					<code>String</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used when working with and
				manipulating text and string values.

				Example:{' '}
				<code>string::reverse('emosewa si 0.2 BDlaerruS')</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/time.md">
					<code>Time</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				Functions and constants for
				working with and manipulating datetime values.

				Example: <code>time::timezone()</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/type.md">
					<code>Type</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				These functions can be used for generating and coercing data
				to specific data types.

				Example: <code>type::is_number(500)</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/value.md">
					<code>Value</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				This module contains several miscellaneous functions that
				can be used with values of any type.

				Example:{' '}
				<code>value::diff([true, false], [true, true])</code>
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function">
				<a href="/docs/reference/query-language/functions/database-functions/vector.md">
					<code>Vector</code>
				</a>
			</td>
			<td scope="row" data-label="Description and Example">
				A collection of essential vector operations that provide
				foundational functionality for numerical computation,
				machine learning, and data analysis.

				Example: <code>vector::add([1, 2, 3], [1, 2, 3])</code>
			</td>
		</tr>
	</tbody>
</table>

## How to use database functions

### Classic syntax

Functions in SurrealDB can always be called using their full path names beginning with the package names indicated above, followed by the function arguments.

```surql
string::split("SurrealDB 3.0 is now here!", " ");
array::len([1,2,3]);
type::is_number(10);
type::record("cat", "mr_meow");
```

```surql title="Output"
-------- Query --------

[
	'SurrealDB',
	'3.0',
	'is',
	'now',
	'here!'
]

-------- Query --------

3

-------- Query --------

true

-------- Query --------

cat:mr_meow
```

### Method syntax

Functions that are called on an existing value can be called using method syntax, using the `.` (dot) operator.

The following functions will produce the same output as the classic syntax above. `type::record()` cannot be called with method syntax because it is used to outright create a record ID from nothing, rather than being called on an existing value.

```surql
"SurrealDB 3.1 is now here!".split(" ");
[1,2,3].len();
10.is_number();
```

The method syntax is particularly useful when calling a number of functions inside a single query.

```surql
array::len(array::windows(array::distinct(array::flatten([[1,2,3],[1,4,6],[4,2,4]])), 2));
```

Without method chaining, a query of this type is often written across multiple nested lines:

```surql
array::len(
    array::clump(
        array::distinct(
            array::flatten([[1,2,3],[1,4,6],[4,2,4]])
        )
    , 2)
);
```

However, method chaining syntax allows queries of this type to be read from left to right in a functional manner. This is known as method chaining. As each of the methods below except the last return an array, further array methods can thus be called by using the `.` operator. The final method then returns an integer.

```surql
[[1,2,3],[1,4,6],[4,2,4],2].flatten().distinct().windows(2).len();
```

This can be made even more readable by splitting over multiple lines.

```surql
[[1,2,3],[1,4,6],[4,2,4]]
    .flatten()
    .distinct()
    .windows(2)
    .len();
```

### Conversion from `::` (double colon) to `_` (underscore) syntax

_(since v3.0.0)_

Full function paths in SurrealDB were converted to match the method syntax detailed above.

```surql
-- Old syntax
type::is::record(person:one);
-- Method syntax
person:one.is_record();
-- New syntax now matches method syntax
type::is_record(person:one);
```

### Built-in constants

Some modules expose constants (fixed values) as well as functions. Consts use the same `module::name` path syntax as for functions, but omit parentheses because they access direct values instead of a function to be called.

- **[Math](/docs/reference/query-language/functions/database-functions/math.md#math-constants)** - numeric constants (π, e, τ, infinities, and related values).
- **[Time](/docs/reference/query-language/functions/database-functions/time.md#time-constants)** - `time::epoch`, `time::minimum`, and `time::maximum`.
- **[Duration](/docs/reference/query-language/functions/database-functions/duration.md#duration-constants)** - `duration::max`.

```surql
[math::pi, math::tau, math::e];
```

```surql title="Output"
[
	3.141592653589793f,
	6.283185307179586f,
	2.718281828459045f
]
```

## Aggregate functions

A few functions can be used not just on their own but with a [`GROUP BY`](/docs/reference/query-language/clauses/group.md) clause including as part of a [pre-computed table view](/docs/reference/query-language/statements/define/table.md#pre-computed-table-views).

These functions are:

* [`count()`](/docs/reference/query-language/functions/database-functions/count.md)
* [`math::max()`](/docs/reference/query-language/functions/database-functions/math.md#mathmax)
* [`math::min()`](/docs/reference/query-language/functions/database-functions/math.md#mathmin)
* [`math::sum()`](/docs/reference/query-language/functions/database-functions/math.md#mathsum)
* [`math::mean()`](/docs/reference/query-language/functions/database-functions/math.md#mathmean)
* [`math::stddev()`](/docs/reference/query-language/functions/database-functions/math.md#mathstddev)
* [`math::variance()`](/docs/reference/query-language/functions/database-functions/math.md#mathvariance)
* [`time::max()`](/docs/reference/query-language/functions/database-functions/time.md#timemax)
* [`time::min()`](/docs/reference/query-language/functions/database-functions/time.md#timemin)

The following functions also aggregate in a `GROUP BY` query, but cannot be used in a pre-computed table view:

* [`array::distinct()`](/docs/reference/query-language/functions/database-functions/array.md#arraydistinct)
* [`array::group()`](/docs/reference/query-language/functions/database-functions/array.md#arraygroup)
* [`array::join()`](/docs/reference/query-language/functions/database-functions/array.md#arrayjoin)
* [`math::median()`](/docs/reference/query-language/functions/database-functions/math.md#mathmedian)
* [`vector::sum()`](/docs/reference/query-language/functions/database-functions/vector.md#vectorsum)

## Anonymous functions

SurrealDB also allows for the creation of anonymous functions (also known as closures) that do not need to be defined on the database. See [the page on closures](/docs/reference/query-language/language-primitives/data-types/closures.md) for more details.

## Extensions

You can also write your own functions in Rust that can be compiled to WASM modules, linked to, and called from the database. For more on how extensions are built and run, see [Extensions](/docs/learn/extensions.md).

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/api

# API

These functions can be used with the DEFINE API or DEFINE CONFIG statements.

API functions run as middleware on a custom endpoint, altering a request before it is handled or a response before it is returned. They are passed in inside a `DEFINE API` or `DEFINE CONFIG API` statement.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#apiinvoke"><code>api::invoke()</code></a></td>
      <td scope="row" data-label="Description">Invokes an `/api` endpoint and returns the result</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#apitimeout"><code>api::timeout()</code></a></td>
      <td scope="row" data-label="Description">Middleware to set a timeout for requests made to a defined API endpoint</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#apireqbody"><code>api::req::body()</code></a></td>
      <td scope="row" data-label="Description">Middleware to set the body type for an API endpoint request</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#apireqmax_body"><code>api::req::max_body()</code></a></td>
      <td scope="row" data-label="Description">Middleware to cap the size of an API endpoint request body</td>
    </tr>
        <tr>
    <td scope="row" data-label="Function"><a href="#apiresbody"><code>api::res::body()</code></a></td>
      <td scope="row" data-label="Description">Middleware to set the body type for an API endpoint response</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#apiresheader"><code>api::res::header()</code></a></td>
      <td scope="row" data-label="Description">Middleware to add a single header to an API endpoint response</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#apiresheaders"><code>api::res::headers()</code></a></td>
      <td scope="row" data-label="Description">Middleware to adds multiple headers to an API endpoint response</td>
    </tr>
        <tr>
      <td scope="row" data-label="Function"><a href="#apiresstatus"><code>api::res::status()</code></a></td>
      <td scope="row" data-label="Description">Middleware to set the status for an API endpoint response</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#custom-middleware"><code>Custom middleware</code></a></td>
      <td scope="row" data-label="Description">Middleware to set the status for an API endpoint response</td>
    </tr>
  </tbody>
</table>

## Overview

API functions are passed in as middleware inside a [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) or [`DEFINE CONFIG API`](/docs/reference/query-language/statements/define/config.md) statement and called when a request is received.

The only API function intended for use in regular queries is the [`api::invoke`](#apiinvoke) function, which is used to test API endpoints instead of as middleware.

The signatures for all other functions are presented here from the point of view of the user. For example, the `api::timeout` function takes a single duration.

```surql
api::timeout(duration)
```

In practice, two extra arguments are passed in to these functions unseen to the user, making this the true signature.

```surql
api::timeout($req: object, $next: function, $duration: duration)
```

For more details on how this works, see [this section](#structure-of-api-functions) on this page.

## `api::invoke`

```surql title="API DEFINITION"
api::invoke($path: string, $options: option<object>) -> object
```

The `api::invoke` function invokes a custom `/api` endpoint defined using a `DEFINE API` statement. While a `DEFINE API` statement creates an API endpoint at the `/api/:namespace/:database/:endpoint` path, this function is called when a namespace and database have already been decided, necessitating only the final path (such as `"/test"`) for it to be invoked.

The following two examples of the function assume that this `DEFINE API` statement has been used to set up the `"/test"` endpoint.

```surql title="Define API endpoint"
DEFINE API "/test"
    FOR get 
        MIDDLEWARE
            api::timeout(1s)
        THEN {
            {
                status: 404,
                body: $request.body,
                headers: { the_time_is_now: <string>time::now() }
            };
        };
```

Calling the `api::invoke` function with just a path:

```surql title="Use defined endpoint"
api::invoke("/test");
```

```surql title="Sample output"
{
	body: NONE,
	headers: {
		the_time_is_now: '2026-10-14T04:52:28.809466Z',
		"x-surreal-request-id": 'aca83c7f-54da-49d1-9b98-42021ce97583'
	},
	request_id: 'aca83c7f-54da-49d1-9b98-42021ce97583',
	status: 404
}
```

Calling the `api::invoke` function with a path and an object containing a body and headers:

```surql
api::invoke("/test", {
    body: <bytes> '{ "a": true }',
    headers: {
        "Content-Type": "application/json",
        Accept: "application/cbor",
    }
});
```

```surql title="Sample output"
{
	body: b"7B202261223A2074727565207D",
	headers: {
		the_time_is_now: '2026-10-14T04:52:28.810780Z',
		"x-surreal-request-id": 'cf618cec-4e93-4981-b435-281d17b01c1e'
	},
	request_id: 'cf618cec-4e93-4981-b435-281d17b01c1e',
	status: 404
}
```

For more information and examples, see the page for the `DEFINE API` statement.

## `api::timeout`

The `api::timeout` function sets the maximum timeout for a request.

```surql title="API DEFINITION"
api::timeout($timeout: duration)
```

The following example will always return an error because the

```surql title="Example"
DEFINE API "/exceeds_timeout"
    FOR get 
        MIDDLEWARE
            api::timeout(1ns)
        THEN {
            sleep 1ns;
            {}
        };
```

## `api::req::body`

```surql title="API DEFINITION"
api::req::body($path: string, $strategy: option<string>)
```

This function sets the strategy (the input format) for the endpoint. It can take one of the following strings:

* 'auto'
* 'json'
* 'cbor'
* 'flatbuffers'
* 'plain'
* 'bytes'
* 'native'

The following example shows an endpoint for each of these strategies, followed by an invocation for each that matches it.

```surql
DEFINE API "/body/json" FOR post
    MIDDLEWARE api::req::body("json")
    THEN {{ body: { parsed: $request.body } }};

DEFINE API "/body/cbor" FOR post
    MIDDLEWARE api::req::body("cbor")
    THEN {{ body: { parsed: $request.body } }};

DEFINE API "/body/plain" FOR post
    MIDDLEWARE api::req::body("plain")
    THEN {{ body: { parsed: $request.body } }};

DEFINE API "/body/bytes" FOR post
    MIDDLEWARE api::req::body("bytes")
    THEN {{ body: { parsed: $request.body } }};

DEFINE API "/body/native" FOR post
    MIDDLEWARE api::req::body("native")
    THEN {{ body: { parsed: $request.body } }};

DEFINE API "/body/auto" FOR post
    MIDDLEWARE api::req::body("auto")
    THEN {{ body: { parsed: $request.body } }};

api::invoke("/body/json", {
    method: "post",
    headers: { "content-type": "application/json" },
    body: <bytes>'{"name":"billy","billys_number":753}'
});

api::invoke("/body/cbor", {
    method: "post",
    headers: { "content-type": "application/cbor" },
    body: encoding::cbor::encode('CBOR!!')
});

api::invoke("/body/plain", {
    method: "post",
    headers: { "content-type": "text/plain" },
    body: <bytes>'plain text content'
});

api::invoke("/body/bytes", {
    method: "post",
    headers: { "content-type": "application/octet-stream" },
    body: <bytes>'raw bytes'
});

api::invoke("/body/native", {
    method: "post",
    headers: { "content-type": "application/vnd.surrealdb.native" },
    body: { native: "format" }
});

api::invoke("/body/auto", {
    method: "post",
    headers: { "content-type": "application/json" },
    body: <bytes>'{"auto":"json"}'
});

api::invoke("/body/auto", {
    method: "post",
    body: <bytes>'some data'
});
```

## `api::req::max_body`

_(since v3.3.0)_

```surql title="API DEFINITION"
api::req::max_body($limit: int | string)
```

This function caps the size of the raw request body. The limit is a non-negative byte count, or a byte-size string such as `"512kb"` or `"1mb"`.

The cap is applied to the body as received, before any parsing, so place it ahead of [`api::req::body`](#apireqbody) to have an oversized payload rejected without being decoded first.

```surql
DEFINE API "/upload"
    FOR post
        MIDDLEWARE
            api::req::max_body("1mb"),
            api::req::body("json")
        THEN {
            RETURN { status: 201, body: { received: $request.body } };
        };
```

A request over the limit is answered with status `413` and never reaches the handler.

```surql
DEFINE API "/limited"
    FOR post
        MIDDLEWARE
            api::req::max_body(10),
            api::req::body("json")
        THEN {
            { status: 200, body: { parsed: $request.body } };
        };

api::invoke("/limited", {
    method: "post",
    headers: { "content-type": "application/json" },
    body: <bytes>'{"data":"aaaaaaaaaaaaaaa"}'
});
```

```surql title="Output"
{
	body: 'Invalid request body: The body exceeded the max payload size of 10b',
	headers: {  },
	status: 413
}
```

Only a raw payload is measured: a `bytes` body by its byte length, and a `string` body by its UTF-8 byte length. Any other body value passes through unchecked, as does a request with no body at all.

## `api::res::body`

```surql title="API DEFINITION"
api::res::body($path: string, $strategy: option<string>)
```

```surql
DEFINE API "/serialize_json"
    FOR get
        MIDDLEWARE
            api::res::body("json")
        THEN {
            {
                status: 200,
                body: {
                    message: "Hello"
                }
            };
        };

api::invoke("/serialize_json").{
    body: <string>body, -- Cast response bytes into string
    headers,
    status
};
```

```surql title="Output"
{ 
    body: '{"message":"Hello"}', 
    headers: { "access-control-allow-origin": '*',
      "content-type": 'application/json' }
    status: 200 
}
```

## `api::res::header`

The `api::res::header` function sets a single header for a response.

```surql title="API DEFINITION"
api::res::header($header_name: string, $val: value)
```

```surql title="Example"
DEFINE API "/test"
  FOR get 
    MIDDLEWARE
      api::res::header("country-origin", "CA")
    THEN {
      {
        status: 200,
        headers: {
          "requested-at": <string>time::now()
        },
        body: SELECT * FROM person
      };
    };
```

## `api::res::headers`

The `api::res::headers` function takes an object to set the headers for a response.

```surql title="API DEFINITION"
api::res::headers($headers: object)
```

```surql title="Example"
DEFINE API "/test"
    FOR get 
        MIDDLEWARE
            api::res::headers({
                "country-origin": "CA",
                "language": "FR"
            })
        THEN {
            {
                status: 200,
                headers: {
                    "requested-at": <string>time::now()
                },
                body: SELECT * FROM person
            };
        };
```

## `api::res::status`

```surql title="API DEFINITION"
api::res::status($http_code: int)
```

The `api::res::status` function adds a status to the response of an API endpoint.

```surql
DEFINE API "/always_ok"
    FOR get
        MIDDLEWARE
            api::res::status(200)
        THEN {
            {
                status: 404,
                body: {
                    some: "data"
                }
            };
        };

api::invoke("/always_ok");
```

```surql title="Sample output"
{
	body: {
		some: 'data'
	},
	headers: {
		"x-surreal-request-id": '27d6f78e-2ca9-4ed8-81c7-d66523e37c15'
	},
	request_id: '27d6f78e-2ca9-4ed8-81c7-d66523e37c15',
	status: 200
}
```

Setting an invalid HTTP status returns a response with the status `400` and the reason as its body.

```surql
DEFINE API "/status/invalid-low"
    FOR get
        MIDDLEWARE
            api::res::status(99)
        THEN {
            RETURN {
                status: 200,
                body: {}
            };
        };

api::invoke("/status/invalid-low");
```

```surql title="Sample output"
{
	body: 'Invalid HTTP status code: 99. Must be between 100 and 599',
	headers: {
		"x-surreal-request-id": '5421b446-0522-4a1f-80a4-771e76cd3bae'
	},
	request_id: '5421b446-0522-4a1f-80a4-771e76cd3bae',
	status: 400
}
```

## Custom middleware

A `DEFINE FUNCTION` statement can be used to define a function for use as custom middleware. For more details on defining a custom function in this manner, see the [`DEFINE API`](/docs/reference/query-language/statements/define/api.md#custom-middleware) page.

## Structure of API functions

An API function can technically be called in the same way as any other function, as long as the first argument is an object and the second argument is a closure that returns an object. After a `MIDDLEWARE` clause these arguments will be automatically filled, but dummy arguments can be passed in for practice or testing.

```surql
api::res::body({}, || {}, "json").{
    body: <string>body,
    context,
    headers,
    status
};

-- Returns:
{
	body: 'null',
	context: {},
	headers: {
		"content-type": 'application/json'
	},
	status: 200
};

api::res::body({}, || {}, "jsonnn").{
    body: <string>body,
    context,
    headers,
    status
};

-- Returns:
'Failed to decode BodyStrategy, no variants matched'
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/array

# Array

These functions can be used when working with, and manipulating arrays of data.

These functions can be used when working with, and manipulating arrays of data.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayadd"><code>array::add()</code></a></td>
      <td scope="row" data-label="Description">Adds an item to an array if it doesn't exist</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayall"><code>array::all()</code></a></td>
      <td scope="row" data-label="Description">Checks whether all array values are truthy, or equal to a condition</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayany"><code>array::any()</code></a></td>
      <td scope="row" data-label="Description">Checks whether any array value is truthy, or equal to a condition</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayat"><code>array::at()</code></a></td>
      <td scope="row" data-label="Description">Returns value for X index, or in reverse for a negative index</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayappend"><code>array::append()</code></a></td>
      <td scope="row" data-label="Description">Appends an item to the end of an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayboolean_and"><code>array::boolean_and()</code></a></td>
      <td scope="row" data-label="Description">Perform the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND">AND</a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND"> </a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND">bitwise operations</a> on two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayboolean_or"><code>array::boolean_or()</code></a></td>
      <td scope="row" data-label="Description">Perform the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR">OR</a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR"> </a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR">bitwise operations</a> on two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayboolean_xor"><code>array::boolean_xor()</code></a></td>
      <td scope="row" data-label="Description">Perform the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR">XOR</a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR"> </a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR">bitwise operations</a> on two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayboolean_not"><code>array::boolean_not()</code></a></td>
      <td scope="row" data-label="Description">Perform the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT">NOT</a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT"> </a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT">bitwise operations</a> on an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraycombine"><code>array::combine()</code></a></td>
      <td scope="row" data-label="Description">Combines all values from two arrays together</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraycomplement"><code>array::complement()</code></a></td>
      <td scope="row" data-label="Description">Returns the complement of two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayclump"><code>array::clump()</code></a></td>
      <td scope="row" data-label="Description">Returns the original array split into multiple arrays of X size</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayconcat"><code>array::concat()</code></a></td>
      <td scope="row" data-label="Description">Returns the merged values from two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraydifference"><code>array::difference()</code></a></td>
      <td scope="row" data-label="Description">Returns the difference between two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraydistinct"><code>array::distinct()</code></a></td>
      <td scope="row" data-label="Description">Returns the unique items in an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayfill"><code>array::fill()</code></a></td>
      <td scope="row" data-label="Description">Fills an existing array of the same value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayfilter"><code>array::filter()</code></a></td>
      <td scope="row" data-label="Description">Filters out values that do not match a pattern</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayfilter_index"><code>array::filter_index()</code></a></td>
      <td scope="row" data-label="Description">Returns the indexes of all occurrences of all matching X value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayfind"><code>array::find()</code></a></td>
      <td scope="row" data-label="Description">Returns the first matching value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayfind_index"><code>array::find_index()</code></a></td>
      <td>Returns the index of the first occurrence of X value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayfirst"><code>array::first()</code></a></td>
      <td scope="row" data-label="Description">Returns the first item in an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayflatten"><code>array::flatten()</code></a></td>
      <td scope="row" data-label="Description">Flattens multiple arrays into a single array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayfold"><code>array::fold()</code></a></td>
      <td scope="row" data-label="Description">Applies an operation on an initial value plus every element in the array, returning the final result.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraygroup"><code>array::group()</code></a></td>
      <td scope="row" data-label="Description">Flattens and returns the unique items in an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayinsert"><code>array::insert()</code></a></td>
      <td scope="row" data-label="Description">Inserts an item at the end of an array, or in a specific position</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayintersect"><code>array::intersect()</code></a></td>
      <td scope="row" data-label="Description">Returns the values which intersect two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayis_empty"><code>array::is_empty()</code></a></td>
      <td scope="row" data-label="Description">Checks if an array is empty</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayjoin"><code>array::join()</code></a></td>
      <td scope="row" data-label="Description">Returns concatenated value of an array with a string in between.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraylast"><code>array::last()</code></a></td>
      <td scope="row" data-label="Description">Returns the last item in an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraylen"><code>array::len()</code></a></td>
      <td scope="row" data-label="Description">Returns the length of an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraylogical_and"><code>array::logical_and()</code></a></td>
      <td scope="row" data-label="Description">Performs the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND">AND</a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND"> </a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND">logical operations</a> on two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraylogical_or"><code>array::logical_or()</code></a></td>
      <td scope="row" data-label="Description">Performs the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR">OR</a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR"> </a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR">logical operations</a> on two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraylogical_xor"><code>array::logical_xor()</code></a></td>
      <td scope="row" data-label="Description">Performs the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR">XOR</a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR"> </a><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR">logical operations</a> on two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraymap"><code>array::map()</code></a></td>
      <td scope="row" data-label="Description">Applies an operation to every item in an array and passes it on</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraymax"><code>array::max()</code></a></td>
      <td scope="row" data-label="Description">Returns the greatest item from an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraymatches"><code>array::matches()</code></a></td>
      <td scope="row" data-label="Description">Returns an array of booleans indicating which elements of the input array contain a specified value.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraymin"><code>array::min()</code></a></td>
      <td scope="row" data-label="Description">Returns the least item from an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraypop"><code>array::pop()</code></a></td>
      <td scope="row" data-label="Description">Returns the last item from an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayprepend"><code>array::prepend()</code></a></td>
      <td scope="row" data-label="Description">Prepends an item to the beginning of an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraypush"><code>array::push()</code></a></td>
      <td scope="row" data-label="Description">Appends an item to the end of an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayrange"><code>array::range()</code></a></td>
      <td scope="row" data-label="Description">Creates a number array from a range (start to end)</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayreduce"><code>array::reduce()</code></a></td>
      <td scope="row" data-label="Description">Applies an operation on every element in the array, returning the final result.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayremove"><code>array::remove()</code></a></td>
      <td scope="row" data-label="Description">Removes an item at a specific position from an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayrepeat"><code>array::repeat()</code></a></td>
      <td scope="row" data-label="Description">Creates an array a given size with a specified value used for each element.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayreverse"><code>array::reverse()</code></a></td>
      <td scope="row" data-label="Description">Reverses the sorting order of an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayshuffle"><code>array::sequence()</code></a></td>
      <td scope="row" data-label="Description">Creates an array of sequential integers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayshuffle"><code>array::shuffle()</code></a></td>
      <td scope="row" data-label="Description">Randomly shuffles the contents of an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayslice"><code>array::slice()</code></a></td>
      <td scope="row" data-label="Description">Returns a slice of an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraysort"><code>array::sort()</code></a></td>
      <td scope="row" data-label="Description">Sorts the values in an array in ascending or descending order</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraysort_lexical"><code>array::sort_lexical()</code></a></td>
      <td scope="row" data-label="Description">Sorts the values in an array, with strings sorted lexically</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraysort_natural"><code>array::sort_natural()</code></a></td>
      <td scope="row" data-label="Description">Sorts the values in an array, with numeric strings sorted numerically</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraysort_natural_lexical"><code>array::sort_natural_lexical()</code></a></td>
      <td scope="row" data-label="Description">Sorts the values in an array, applying both natural numeric and lexical ordering to strings</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraysortasc"><code>array::sort::asc()</code></a></td>
      <td scope="row" data-label="Description">Sorts the values in an array in ascending order</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraysortdesc"><code>array::sort::desc()</code></a></td>
      <td scope="row" data-label="Description">Sorts the values in an array in descending order</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayswap"><code>array::swap()</code></a></td>
      <td scope="row" data-label="Description">Swaps two items in an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraytranspose"><code>array::transpose()</code></a></td>
      <td scope="row" data-label="Description">Performs 2d array transposition on arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arrayunion"><code>array::union()</code></a></td>
      <td scope="row" data-label="Description">Returns the unique merged values from two arrays</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#arraywindows"><code>array::windows()</code></a></td>
      <td scope="row" data-label="Description">Returns a number of arrays of length `size` created by moving one index at a time down the original array</td>
    </tr>
  </tbody>
</table>

## `array::add`

The `array::add` function adds an item to an array only if it doesn't exist.

```surql title="API DEFINITION"
array::add(array, $new_val: value) -> array
```

The following example shows this function, and its output:

```surql
array::add(["one", "two"], "three");
```

```surql title="Output"
['one', 'two', 'three']
```

If the item to add is an array, it will add each item of the array instead of the array itself as a separate value.

```surql
[1,2,3].add([2,3,4]);
```

```surql title="Output"
[1, 2, 3, 4]
```

<br />

## `array::all`

When called on an array without any extra arguments, the `array::all` function checks whether all array values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

```surql title="API DEFINITION"
array::all(array) -> bool
array::all(array, $predicate: value) -> bool
array::all(array, $predicate: closure) -> bool
```

The following example shows this function, and its output:

```surql
array::all([ 1, 2, 3, NONE, 'SurrealDB', 5 ]);
//- false

["all", "clear"].all();
//- true
```

The `array::all` function can also be followed with a value or a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) to check if all elements conform to a condition.

```surql
["same", "same", "same"].all("same");
//- true

[
  "What's",
  "it",
  "got",
  "in",
  "its",
  "pocketses??"
].all(|$s| $s.len() > 1);
//- true

[1, 2, "SurrealDB"].all(|$var| $var.is_string());
//- false
```

The `array::all` function can also be called using its alias `array::every`.

```surql
[1, 2, 3].every(|$num| $num > 0);
```

```surql title="Output"
true
```

<br />

## `array::any`

The `array::any` function checks whether any array values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

```surql title="API DEFINITION"
array::any(array) -> bool
array::any(array, $predicate: value) -> bool
array::any(array, $predicate: closure) -> bool
```

When called on an array without any extra arguments, the `array::any` function checks whether any array values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

```surql
array::any([ 1, 2, 3, NONE, 'SurrealDB', 5 ]);
//- true

["", 0, NONE, NULL, [], {}].any();
//- false
```

The `array::any` function can also be followed with a value or a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) to check if any elements conform to a condition.

```surql
["same", "same?", "Dude, same!"].any("same");
//- true

[
  "What's",
  "it",
  "got",
  "in",
  "its",
  "pocketses??"
].any(|$s| $s.len() > 15);
//- false

[1, 2, "SurrealDB"].any(|$var| $var.is_string());
//- true
```

The `array::any` function can also be called using the aliases `array::some` and `array::includes`.

```surql
[1, 2, 3].some(|$num| $num > 2);
//- true

[1999, 2001, 2002].includes(2000);
//- false
```

<br />

## `array::at`

The `array::at` function returns the value at the specified index, or in reverse for a negative index.

```surql title="API DEFINITION"
array::at(array, $index: int) -> any
```

The following example shows this function, and its output:

```surql
array::at(['s', 'u', 'r', 'r', 'e', 'a', 'l'], 2);
```

```surql title="Output"
'r'
```

You can also pass a negative index. This will perform the lookup in reverse:

```surql
array::at(['s', 'u', 'r', 'r', 'e', 'a', 'l'], -3);
```

```surql title="Output"
'e'
```

<br />

## `array::append`

The `array::append` function appends a value to the end of an array.

```surql title="API DEFINITION"
array::append(array, $new_val: value) -> array
```

The following example shows this function, and its output:

```surql
array::append([1, 2, 3, 4], 5);
```

```surql title="Output"
[1, 2, 3, 4, 5]
```

<br />

## `array::boolean_and`

The `array::boolean_and` function performs the [`AND` `bitwise operations`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND) on the input arrays per-element based on the element's truthiness.
If one array is shorter than the other it is considered null and thus false.

```surql title="API DEFINITION"
array::boolean_and($lh: array, $rh: array)
```

The following example shows this function, and its output:

```surql
array::boolean_and(["true",
  "false",
  1,
  1],
  ["true",
  "true",
  0,
  "true"]);
```

```surql title="Output"
[true, true, false, true]
```

For those that take two arrays, missing elements (if one array is shorter than the other) are considered `null` and thus false.

```surql
array::boolean_and([true, true], [false]);
```

```surql title="Output"
[ false, false ]
```

<br />

## `array::boolean_or`

The `array::boolean_or` function performs the [OR bitwise operations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR) on the input arrays per-element based on the element's truthiness.
It takes two arrays and if one array is shorter than the other or missing, the output is considered null and thus false.

```surql title="API DEFINITION"
array::boolean_or($lh: array, $rh: array)
```

The following example shows this function, and its output:

```surql
array::boolean_or([false,
  true,
  false,
  true],
  [false,
  false,
  true,
  true]);
```

```surql title="Output"
[false, true, true, true]
```

<br />

## `array::boolean_xor`

The `array::boolean_xor` function performs the [XOR bitwise operations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR).

```surql title="API DEFINITION"
array::boolean_xor($lh: array, $rh: array)
```

The following example shows this function, and its output:

```surql
array::boolean_xor([false,
  true,
  false,
  true],
  [false,
  false,
  true,
  true]);
```

```surql title="Output"
[false, true, true, false]
```

<br />

## `array::boolean_not`

The `array::boolean_not` function performs the [`NOT bitwise operations`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT) on the input array(s) per-element based on the element's truthiness.
It takes in one array and it returns false if its single operand can be converted to true.

```surql title="API DEFINITION"
array::boolean_not(array)
```

The following example shows this function, and its output:

```surql
array::boolean_not([ false, true, 0, 1 ]);
```

```surql title="Output"
[true, false, true, false]
```

<br />

## `array::combine`

The `array::combine` function combines all values from two arrays together, returning an array of arrays.

```surql title="API DEFINITION"
array::combine(array, $other: array) -> array
```

The following example shows this function, and its output:

```surql
array::combine([1, 2], [2, 3]);
```

```surql title="Output"
[ [1, 2], [1, 3], [2, 2], [2, 3] ]
```

<br />

## `array::complement`

The `array::complement` function returns the complement of two arrays, returning a single array containing items which are not in the second array.

```surql title="API DEFINITION"
array::complement(array, $other: array) -> array
```

The following example shows this function, and its output:

```surql
array::complement([1, 2, 3, 4], [3, 4, 5, 6]);
```

```surql title="Output"
[1, 2]
```

<br />

## `array::concat`

The `array::concat` function merges an array with one or more arrays, returning an array which may contain duplicate values. If you want to remove duplicate values from two merged arrays, then use the [`array::union()`](#arrayunion) function.

```surql title="API DEFINITION"
array::concat(array, $other: array, ..) -> array
```

The following example shows this function, and its output:

```surql
array::concat([1, 2, 3, 4], [3, 4, 5, 6]);
//- [1, 2, 3, 4, 3, 4, 5, 6]

[1,2].concat([3,4], [4,3])
//- [1, 2, 3, 4, 4, 3]
```

As of SurrealDB 3.0.0, the behaviour of this function can also be achieved using the `+` operator.

```surql
[1, 2, 3, 4] + [3, 4, 5, 6];
```

```surql title="Output"
[ 1, 2, 3, 4, 3, 4, 5, 6 ]
```

<br />

## `array::clump`

The `array::clump` function returns the original array split into sub-arrays of `size`. The last sub-array may have a length less than the length of `size` if `size` does not divide equally into the original array.

```surql title="API DEFINITION"
array::clump(array, $size: int) -> array
```

The following examples show this function, and its output:

```surql
LET $array = [1, 2, 3, 4];
RETURN array::clump($array, 2);
RETURN array::clump($array, 3);
```

```surql title="Output"
-- [ [ 1, 2], [3, 4] ]
-- [ [1, 2, 3], [4] ]
```

<br />

## `array::difference`

The `array::difference` function determines the difference between two arrays, returning a single array containing items which are not in both arrays.

```surql title="API DEFINITION"
array::difference(array, $other: array) -> array
```

The following example shows this function, and its output:

```surql
array::difference([1, 2, 3, 4], [3, 4, 5, 6]);
```

```surql title="Output"
[ 1, 2, 5, 6 ]
```

<br />

## `array::distinct`

The `array::distinct` function calculates the unique values in an array, returning a single array.

```surql title="API DEFINITION"
array::distinct(array) -> array
```

The following example shows this function, and its output:

```surql
array::distinct([ 1, 2, 1, 3, 3, 4 ]);
```

```surql title="Output"
[ 1, 2, 3, 4 ]
```

<br />

## `array::fill`

The `array::fill` function replaces all values of an array with a new value.

```surql title="API DEFINITION"
array::fill(array, $with: any) -> array
```

The function also accepts a third and a fourth parameter which allows you to replace only a portion of the source array.

```surql title="API DEFINITION"
array::fill(array, $with: any, $start: int, $end: int) -> array
```

The following example shows this function, and its output:

```surql
array::fill([ 1, 2, 3, 4, 5 ], 10);
```

```surql title="Output"
[ 10, 10, 10, 10, 10 ]
```

The following example shows how you can use this function with a starting position, and an ending position, which in this example will replace one item from the array:

```surql
array::fill([ 1, NONE, 3, 4, 5 ], 10, 1, 2);
```

```surql title="Output"
[ 1, 10, 3, 4, 5 ]
```

The following example shows how you can use this function with starting and ending negative positions, which in this example will replace one item from the array:

```surql
array::fill([ 1, 2, NONE, 4, 5 ], 10, -3, -2);
```

```surql title="Output"
[ 1, 2, 10, 4, 5 ]
```

<br />

## `array::filter`

The `array::filter` function filters out values in an array that do not match a pattern, returning only the ones that do match.

```surql title="API DEFINITION"
array::filter(array, $predicate: value) -> array
array::filter(array, $predicate: closure) -> array
```

The following example shows this function, and its output:

```surql
array::filter([ 1, 2, 1, 3, 3, 4 ], 1);
//- [ 1, 1 ]

[true, false, false, false, true, true].filter(true);
//- [ true, true, true ]
```

The `array::filter` function can also take a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) for more customised filtering.

```surql
 [
    { importance: 10,
      message: "I need some help with this query..." },
    { importance: 0, message: "TEST Is this thing on?" },
    { importance: 5, message: "I have an idea. What if we..."},
    { importance: 100,
      message: "Stuck on an island with two hours of battery life left. Can you..."}
].filter(|$v| $v.importance > 5);
```

```surql title="Output"
[
	{
		importance: 10,
		message: 'I need some help with this query...'
	},
	{
		importance: 100,
		message:
		  'Stuck on an island with two hours of battery life left. Can you...'
	}
]
```

Note that the function checks whether the output of the inner closure [is truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness), as opposed to only expecting a `bool`. As any and all values can be checked for truthiness, simply passing the closure argument as its output is enough to filter out values that are not truthy, such as `NONE` values and empty arrays.

```surql
[1,2,3,NONE,0,"",{},[]].filter(|$v| $v);
```

```surql title="Output"
[1, 2, 3]
```

A more real-life example of this pattern in which only the `person` records that have been seen by another are returned:

```surql
CREATE person:one, person:two;
RELATE person:one->sees->person:two;

(SELECT 
  id, 
  <-sees<-person AS is_seen_by
FROM person)
    .filter(|$person| $person.is_seen_by);
```

```surql title="Output"
[
	{
		id: person:two,
		is_seen_by: [
			person:one
		]
	}
]
```

<br />

## `array::filter_index`

The `array::filter_index` function returns the indexes of all occurrences of all matching values.

```surql title="API DEFINITION"
array::filter_index(array, $predicate: value) -> array
array::filter_index(array, $predicate: closure) -> array
```

The following examples show this function, and its output:

```surql
array::filter_index(['a', 'b', 'c', 'b', 'a'], 'b');
//- [ 1, 3 ]

[0, 0, 1, 0, 0, 5, 1].filter_index(0);
//- [ 0, 1, 3, 4 ]
```

The `array::filter_index` function can also take a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) for more customised filtering.

```surql
 [
    { importance: 10,
      message: "I need some help with this query..." },
    { importance: 0, message: "TEST Is this thing on?" },
    { importance: 5, message: "I have an idea. What if we..."},
    { importance: 100,
      message: "Stuck on an island with two hours of battery life left. Can you..."}
].filter_index(|$v| $v.importance > 5);
```

```surql title="Output"
[0, 3]
```

<br />

## `array::find`

The `array::find` function returns the first occurrence of `value` in the array or `NONE` if `array` does not contain `value`.

```surql title="API DEFINITION"
array::find(array, $predicate: value)   -> value | NONE
array::find(array, $predicate: closure) -> value | NONE
```

The following example shows this function, and its output:

```surql
array::find(['a', 'b', 'c', 'b', 'a'], 'b');
//- 'b'

[1, 2, 3].find(4);
//- NONE
```

The `array::find` function is most useful when a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) is passed in which allows for customised searching.

```surql
-- Find one number 3 or greater
[1, 2, 5].find(|$num| $num >= 3);

-- Find the first adventurer good enough for the task
[
    { strength: 15, intelligence: 6,  name: "Dom the Magnificent" },
    { strength: 10, intelligence: 15, name: "Mardine"             },
    { strength: 20, intelligence: 3,  name: "Gub gub"             },
    { strength: 10, intelligence: 18, name: "Lumin695"            }
].find(|$c| $c.strength > 9 AND $c.intelligence > 9);
```

```surql title="Output"
-------- Query --------

5

-------- Query --------

{
	intelligence: 15,
	name: 'Mardine',
	strength: 10
}
```

<br />

## `array::find_index`

The `array::find_index` function returns the index of the first occurrence of `value` in the array or `NONE` if `array` does not contain `value`.

```surql title="API DEFINITION"
array::find_index(array, $predicate: value)   -> number | NONE
array::find_index(array, $predicate: closure) -> number | NONE
```

The following example shows this function, and its output:

```surql
array::find_index(['a', 'b', 'c', 'b', 'a'], 'b');
//- 1

[1, 2, 3].find_index(4);
//- NONE
```

The `array::find_index` function can also take a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) for more customised searching.

```surql
[1, 2, 3].find_index(|$num| $num > 2);
```

```surql title="Output"
2
```

The `array::find_index` function can also be called using the alias `array::index_of`.

```surql
["cat", "badger", "dog", "octopus"].index_of("octopus");
```

```surql title="Output"
3
```

<br />

## `array::first`

The `array::first` function returns the first value from an array.

```surql title="API DEFINITION"
array::first(array) -> any
```

The following example shows this function, and its output:

```surql
array::first([ 's', 'u', 'r', 'r', 'e', 'a', 'l' ]);
```

```surql title="Output"
's'
```

<br />

## `array::flatten`

The `array::flatten` function flattens an array of arrays, returning a new array with all sub-array elements concatenated into it.

```surql title="API DEFINITION"
array::flatten(array) -> array
```

The following example shows this function, and its output:

```surql
array::flatten([ [1,
  2],
  [3,
  4],
  'SurrealDB',
  [5,
  6,
  [7,
  8]] ]);
```

```surql title="Output"
[ 1, 2, 3, 4, 'SurrealDB', 5, 6, [7, 8] ]
```

<br />

## `array::fold`

The `array::fold` function returns a final value from the elements of an array by allowing an operation to be performed at each step of the way as each subsequent item in the array is encountered. To use `array::fold`, pass in an initial value, followed by parameter names for the current value and the next value and an operation to perform on them. If you only want to perform an operation on each item and do not need an initial value, use the [`array::reduce`](#arrayreduce) function instead.

```surql title="API DEFINITION"
array::fold(array, $initial_value: value, $operator: closure) -> value
```

This function is commonly used to sum or subtract the items in an array from an initial value.

```surql
[10,12,10,15].fold(100, |$a, $b| $a - $b);
//- 53
```

The function will then perform the following operation for each step of the way.

* `$a` = 100 (initial value), `$b` = 10 (first item in the array). Operation `$a - $b` = 90. 90 is passed on.
* `$a` = 90, `$b` = 12. Operation `$a - $b` = 78. 78 is passed on.
* `$a` = 84, `$b` = 10. Operation `$a - $b` = 74. 68 is passed on.
* `$a` = 74, `$b` = 15. Operation `$a - $b` = 53. No more items to operate on in the array, 53 is returned.

Another example showing `array::fold()` used to reverse a `string`:

```surql
"I am a forwards string"
  .split('')
  .fold("", |$one, $two| $two + $one);
```

```surql title="Output"
'gnirts sdrawrof a ma I'
```

Or to modify a string in some other way.

```surql
"I don't like whitespace"
  .split(" ")
  .fold("", |$one, $two| $one + "_" + $two);
```

```surql title="Output"
"_I_don't_like_whitespace"
```

As the output above shows, it is often nice to know which item of the array one is working with. This function allows a third parameter to be passed in that keeps track of the index of the current item.

```surql
"I don't like whitespace"
  .split(" ")
  .fold("",
    |$one,
    $two,
    $index| IF $index = 0 { $one + $two } ELSE { $one + "_" + $two });
```

```surql title="Output"
"I_don't_like_whitespace"
```

The `array::fold()` function can be used to generate an array of values that can then be passed on to statements like [`INSERT`](/docs/reference/query-language/statements/insert.md) for bulk insertion.

```surql
INSERT INTO person (
  -- Create 1000 objects with a random ULID and incrementing number
    (<array>0..1000).fold([], |$v, $_, $i| {
    $v.append( { 
      id: rand::ulid(),
      person_num: $i
      });
    })
) RETURN NONE;
```

This function is also useful for aggregating the results of graph queries. The following shows a graph table called `to` that holds the distance from one city to another. The `array::fold()` function can then be used to pass an object along that tracks the first and last city, while accumulating the distance and number of trips along the way.

```surql
CREATE city:one, city:two, city:three;
RELATE city:one -> to -> city:two SET distance = 25.5;
RELATE city:two -> to -> city:three SET distance = 4.1;
[
	city:one,
	city:two,
	city:three
].map(|$v| { {
	city: $v,
	distance: 0,
	from: NONE,
	to: NONE,
	trips: 0
} }).fold({
	city: NONE,
	distance: 0,
	from: NONE,
	to: NONE,
	trips: 0
}, |$acc, $val, $i| {
	RETURN IF $i = 0 {
		{
			city: $val.city,
			distance: 0,
			from: $val.city,
			to: NONE,
			trips: $acc.trips + 1
		}
  }
	ELSE {
		{
			city: $val.city,
			distance: (SELECT VALUE distance FROM ONLY to WHERE in = $acc.city
			  AND out = $val.city LIMIT 1) + $acc.distance,
			from: $acc.from,
			to: $val.city,
			trips: $acc.trips + 1
		}
  };
}).chain(|$v| { {
	distance: $v.distance,
	from: $v.from,
	to: $v.to,
	trips: $v.trips
} });
```

Final result:

```surql
{
	distance: 29.6f,
	from: city:one,
	to: city:three,
	trips: 3
}
```

## `array::group`

The `array::group` function flattens and returns the unique items in an array.

```surql title="API DEFINITION"
array::group(array) -> array
```

The following example shows this function, and its output:

```surql
array::group([1,
  2,
  3,
  4,
  [3,
  5,
  6],
  [2,
  4,
  5,
  6],
  7,
  8,
  8,
  9]);
```

```surql title="Output"
[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
```

<br />

## `array::insert`

The `array::insert` function inserts a value into an array.

```surql title="API DEFINITION"
array::insert(array, $insert: value) -> array
array::insert(array, $insert: value, $position: int) -> array
```

When only a value is used as the second argument, it will be appended to the end of the array.

```surql
array::insert([1, 2, 3, 4], 'and me');
```

```surql title="Output"
[1, 2, 3, 4, 'and me']
```

If the value to append is followed by an index, this will be used as the location for the new value. A negative index can also be used to index from the end instead of from the beginning of an array.

```surql
array::insert([1, 2, 3, 4], 'and me', 0);
//- ['and me', 1, 2, 3, 4]

array::insert([1, 2, 3, 4], 'and me', -1);
//- [1, 2, 3, 'and me', 4]
```

A negative index can be provided to specify a position relative to the end of the array.

<br />

## `array::intersect`

The `array::intersect` function calculates the values which intersect two arrays, returning a single array containing the values which are in both arrays.

```surql title="API DEFINITION"
array::intersect(array, $other: array) -> array
```

The following example shows this function, and its output:

```surql
array::intersect([1, 2, 3, 4], [3, 4, 5, 6]);
```

```surql title="Output"
[ 3, 4 ]
```

<br />

## `array::is_empty`

The `array::is_empty` function checks whether the array contains values.

```surql title="API DEFINITION"
array::is_empty(array) -> bool
```

The following example shows this function, and its output:

```surql title="An array that contain values"
array::is_empty([1, 2, 3, 4]);

-- false
```

```surql title="An empty array"
array::is_empty([]);

-- true
```

<br />

## `array::join`

The `array::join` function takes an array and a string as parameters and returns a concatenated string.

```surql title="API DEFINITION"
array::join(array, $concat_with: string) -> string
```

The following example shows this function, and its output:

```surql
array::join(["again", "again", "again"], " and ");
```

```surql title="Output"
"again and again and again"
```

<br />

## `array::last`

The `array::last` function returns the last value from an array. You can also use the [`[$]` idiom](/docs/reference/query-language/language-primitives/idioms.md#last-element) on array values (for example, `my_array[$]`).

```surql title="API DEFINITION"
array::last(array) -> any
```

The following example shows this function, and its output:

```surql
array::last([ 's', 'u', 'r', 'r', 'e', 'a', 'l' ]);
```

```surql title="Output"
'l'
```

<br />

## `array::len`

The `array::len` function calculates the length of an array, returning a number. This function includes all items when counting the number of items in the array. If you want to only count [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness) values, then use the [count()](/docs/reference/query-language/functions/database-functions/count.md) function.

```surql title="API DEFINITION"
array::len(array) -> number
```

The following example shows this function, and its output:

```surql
array::len([ 1, 2, 1, null, "something", 3, 3, 4, 0 ]);
```

```surql title="Output"
9
```

<br />

## `array::logical_and`

The `array::logical_and` function performs the [`AND` logical operation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND) element-wise between two arrays.
The resulting array will have a length of the longer of the two input arrays, where each element is the result of the logical `AND` operation performed between an element from the left hand side array and an element from the right hand side array.

When both of the compared elements are truthy, the resulting element will have the type and value of one of the two truthy values, prioritizing the value and type of the element from the left hand side (the first array).

When one or both of the compared elements are not truthy, the resulting element will have the type and value of one of the non-truthy value(s), prioritizing the value and type of the element from the left hand side (the first array).

```surql title="API DEFINITION"
array::logical_and($lh: array, $rh: array)
```

The following example shows this function, and its output:

```surql
array::logical_and([true,
  false,
  true,
  false],
  [true,
  true,
  false,
  false]);
```

```surql title="Output"
[ true, false, false, false ]
```

For those that take two arrays, missing elements (if one array is shorter than the other) are considered `null` and thus false.

```surql
array::logical_and([0, 1], [])
```

```surql title="Output"
[ 0, NULL ]
```

<br />

## `array::logical_or`

The `array::logical_or` function performs the [`OR` logical operations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR) element-wise between two arrays.

The resulting array will have a length of the longer of the two input arrays, where each element is the result of the logical `OR` operation performed between an element from the left hand side array and an element from the right hand side array.

When one or both of the compared elements are truthy, the resulting element will have the type and value of one of the two truthy value(s), prioritizing the value and type of the element from the left hand side (the first array).

When both of the compared elements are not truthy, the resulting element will have the type and value of one of the non-truthy values, prioritizing the value and type of the element from the left hand side (the first array).

```surql title="API DEFINITION"
array::logical_or($lh: array, $rh: array)
```

The following example shows this function, and its output:

```surql
array::logical_or([true,
  false,
  true,
  false],
  [true,
  true,
  false,
  false]);
```

```surql title="Output"
[ true, true, true, false ]
```

If one of the arrays is empty, the first array is returned.

```surql
array::logical_or([0, 1], []);

[ 0, 1 ]
```

<br />

## `array::logical_xor`

The `array::logical_xor` function performs the [`XOR` logical operations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR) element-wise between two arrays.

The resulting array will have a length of the longer of the two input arrays, where each element is the result of the logical `XOR` operation performed between an element from the left hand side array and an element from the right hand side array.

When exactly one of the compared elements is truthy, the resulting element will have the type and value of the truthy value.

When both of the compared elements are truthy, the resulting element will be the `bool` value `false`.

When neither of the compared elements are truthy, the resulting element will have the type and value of one of the non-truthy values, prioritizing the value and type of the element from the left hand side (the first array).

```surql title="API DEFINITION"
array::logical_xor($lh: array, $rh: array)
```

The following example shows this function, and its output:

```surql
array::logical_xor([true,
  false,
  true,
  false],
  [true,
  true,
  false,
  false]);
```

```surql title="Output"
[ false, true, true, false ]
```

If one of the array is empty, the first array is returned.

```surql
array::logical_xor([0, 1], [])
```

```surql title="Output"
[ 0, 1 ]
```

<br />

## `array::map`

The `array::map` function allows the user to call an [anonymous function](/docs/reference/query-language/language-primitives/data-types/closures.md) (closure) that is performed on every item in the array before passing it on.

```surql title="API DEFINITION"
array::map(array, $operator: closure) -> array;
```

The most basic use of `array::map` involves choosing a parameter name for each item in the array and a desired output. The following example gives each item the parameter name `$v`, which can then be used to double the value.

```surql
[1, 2, 3].map(|$v| $v * 2);
```

```surql title="Output"
[
  2,
  4,
  6
]
```

An example of a longer operation that uses `{}` to allow the closure to take multiple lines of code:

```surql
["1", "2", "3"].map(|$val| {
  LET $num = <number>$val;
  LET $is_even = IF $num % 2 = 0 { true } ELSE { false };
  {
    value: $num,
    is_even: $is_even
  }
});
```

```surql title="Output"
[
	{
		is_even: false,
		value: 1
	},
	{
		is_even: true,
		value: 2
	},
	{
		is_even: false,
		value: 3
	}
]

```

The types for the closure arguments and output can be annotated for extra type safety. Take the following simple closure:

```surql
[1, 2, 3].map(|$num| $num + 1.1);
```

The output is `[2.1f, 3.1f, 4.1f]`.

However, if the `1.1` inside the function was actually a typo and should have been the integer 11, the following would have prevented it from running.

```surql
[1, 2, 3].map(|$num: int| -> int { $num + 1.1 });
```

```surql title="Error output"
"Couldn't coerce return value from function `ANONYMOUS`: Expected `int` but found `2.1f`"
```

The `array::map` function also allows access to the index of each item if a second parameter is added.

```surql
[
  ": first used in the year 876",
  ": the number of moons in the sky",
  ": also called a pair"
]
  .map(|$item, $index| <string>$index + $item);
```

```surql title="Output"
[
	'0: first used in the year 876',
	'1: the number of moons in the sky',
	'2: also called a pair'
]
```

The `array::map()` function can be used to generate an array of values that can then be passed on to statements like [`INSERT`](/docs/reference/query-language/statements/insert.md) for bulk insertion.

```surql
INSERT INTO person ((<array>0..=1000).map(|| {id: rand::ulid()}));
```

For a similar function that allows using a closure on entire values instead of each item in an array, see the [chain](/docs/reference/query-language/functions/database-functions/value.md#chain) method.

## `array::max`

The `array::max` function returns the greatest value from an array of values.

```surql title="API DEFINITION"
array::max(array<any>) -> any
```

The following example shows this function, and its output:

```surql
array::max([0, 1, 2]);
```

```surql title="Output"
2
```

As any value can be compared with another value, the array can be an array of any SurrealQL value.

```surql
array::max([NONE, NULL, 9, 9.9]);
```

```surql title="Output"
9.9f
```

See also:

* [`math::max`](/docs/reference/query-language/functions/database-functions/math.md#mathmax), which extracts the greatest number from an array of numbers
* [`time::max`](/docs/reference/query-language/functions/database-functions/time.md#timemax), which extracts the greatest datetime from an array of datetimes
* [How values are compared and ordered in SurrealDB](/docs/reference/query-language/language-primitives/data-types/values.md#comparing-and-ordering-values)

## `array::matches`

The `array::matches` function returns an array of booleans indicating which elements of the input array contain a specified value.

```surql title="API DEFINITION"
array::matches(array, $predicate: value) -> array<bool>
```

The following example shows this function, and its output:

```surql
array::matches([0, 1, 2], 1);
```

```surql title="Output"
[false, true, false]
```

The following example shows this function when the array contains objects.

```surql
array::matches([{id: r"ohno:0"},
  {id: r"ohno:1"}],
  {id: r"ohno:1"});
```

```surql title="Output"
[false, true]
```

<br />

## `array::min`

The `array::min` function returns the least value from an array of values.

```surql title="API DEFINITION"
array::min(array<any>) -> any
```

The following example shows this function, and its output:

```surql
array::min([0, 1, 2]);
```

```surql title="Output"
0
```

As any value can be compared with another value, the array can be an array of any SurrealQL value.

```surql
array::min([NONE, NULL, 9, 9.9]);
```

```surql title="Output"
NONE
```

See also:

* [`math::min`](/docs/reference/query-language/functions/database-functions/math.md#mathmin), which extracts the least number from an array of numbers
* [`time::min`](/docs/reference/query-language/functions/database-functions/time.md#timemin), which extracts the least datetime from an array of datetimes
* [How values are compared and ordered in SurrealDB](/docs/reference/query-language/language-primitives/data-types/values.md#comparing-and-ordering-values)

## `array::pop`

The `array::pop` function removes a value from the end of an array and returns it. If the array is empty, NONE is returned.

```surql title="API DEFINITION"
array::pop(array) -> value
```

The following example shows this function, and its output:

```surql
array::pop([ 1, 2, 3, 4 ]);
```

```surql title="Output"
4
```

<br />

## `array::prepend`

The `array::prepend` function prepends a value to the beginning of an array.

```surql title="API DEFINITION"
array::prepend(array, $new_val: value) -> array
```

The following example shows this function, and its output:

```surql
array::prepend([1, 2, 3, 4], 5);
```

```surql title="Output"
[ 5, 1, 2, 3, 4 ]
```

<br />

## `array::push`

The `array::push` function appends a value to the end of an array.

```surql title="API DEFINITION"
array::push(array, $new_val: value) -> array
```

The following example shows this function, and its output:

```surql
array::push([1, 2, 3, 4], 5);
```

```surql title="Output"
[ 1, 2, 3, 4, 5 ]
```

<br />

## `array::range`

The `array::range` function creates an array of numbers from a given range. With two arguments, the array starts at `$start` and stops before `$end`.

```surql title="API DEFINITION"
array::range($start: int, $end: int) -> array
-- Also since 3.0.0
array::range(range) -> array;
```

The following example shows this function, and its output:

```surql
array::range(1, 10);
```

```surql title="Output"
[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
```

```surql
array::range(1..=5);
```

```surql title="Output"
[1, 2, 3, 4, 5]
```

<br />

## `array::reduce`

The `array::reduce` function reduces the elements of an array to a single final value by allowing an operation to be performed at each step of the way as each subsequent item in the array is encountered. To use `array::reduce`, pass in parameter names for the current value and the next value and an operation to perform on them. If you need an initial value to pass in before the other items are operated on, use the [`array::fold`](#arrayfold) function instead.

```surql title="API DEFINITION"
array::reduce(array, $operator: closure) -> value
```

This function is commonly used to sum or perform some other mathematical operation on the items in an array.

```surql
[10,20,30,40].reduce(|$a, $b| $a + $b);
```

The function will then perform the following operation for each step of the way.

* `$a` = 10, `$b` = 20. Operation `$a + $b` = 30. 30 is passed on.
* `$a` = 30, `$b` = 30. Operation `$a + $b` = 60. 60 is passed on.
* `$a` = 60, `$b` = 40. Operation `$a + $b` = 100. No more items to operate on in the array, 100 is returned.

Another example showing `array::reduce()` used to reverse a `string`:

```surql
"I am a forwards string"
  .split('')
  .reduce(|$one, $two| $two + $one);
```

```surql title="Output"
'gnirts sdrawrof a ma I'
```

Or to modify a string in some other way.

```surql
"I don't like whitespace"
  .split(" ")
  .reduce(|$one, $two| $one + "_" + $two);
```

```surql title="Output"
"I_don't_like_whitespace"
```

It is often nice to know which item of the array one is working with. A third parameter holds the index of the current step, starting at `0` for the step that combines the first two items. The following example combines the first three items only. From the step with index `2` on, the value is simply passed on.

```surql
[
    {
        name: "Daughter",
        money: 100
    },
    {
        name: "Father",
        money: 1000
    },
    {
        name: "Grandfather",
        money: 550
    },
    {
        name: "Great-grandmother",
        money: 10000
    }
].reduce(|$one, $two, $index| IF $index > 1 { $one } ELSE {
    {
        name: $one.name + " and " + $two.name,
        money: $one.money + $two.money
    }
});
```

```surql title="Output"
{
	money: 1650,
	name: 'Daughter and Father and Grandfather'
}
```

## `array::remove`

The `array::remove` function removes an item from a specific position in an array. A negative index can be provided to specify a position relative to the end of the array.

```surql title="API DEFINITION"
array::remove(array, $index: number) -> array
```

The following example shows this function, and its output:

```surql
array::remove([1, 2, 3, 4, 5], 2);
```

```surql title="Output"
[ 1, 2, 4, 5 ]
```

The following example shows this function using a negative index.

```surql
array::remove([1, 2, 3, 4, 5], -2);
```

```surql title="Output"
[ 1, 2, 3, 5 ]
```

<br />

## `array::repeat`

The `array::repeat` function creates an array of a given size containing the specified value for each element. The `count` argument must be non-negative; negative values return an error.

```surql title="API DEFINITION"
array::repeat(any, $count: int) -> array
```

The following example shows this function, and its output:

```surql
array::repeat(1, 10);
```

```surql title="Output"
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
```

```surql
array::repeat("hello", 2);
```

```surql title="Output"
[ "hello", "hello" ]
```

<br />

## `array::reverse`

The `array::reverse` function reverses the sorting order of an array.

```surql title="API DEFINITION"
array::reverse(array) -> array
```

The following example shows this function, and its output:

```surql
array::reverse([ 1, 2, 3, 4, 5 ]);
```

```surql title="Output"
[ 5, 4, 3, 2, 1 ]
```

<br />

## `array::sequence`

_(since v3.0.0)_

The `array::sequence` function creates an array of sequential integers.

```surql title="API DEFINITION"
array::sequence($length: int) -> array
array::sequence($start: int, $length: int) -> array
```

A single number passed in as an argument will create an array beginning at 0 with a length of the number indicated.

```surql
array::sequence(5);
```

```surql title="Output"
[0, 1, 2, 3, 4]
```

If a second argument is passed into this function, the first argument will be used as the starting point for the array and the second for the length.

```surql
array::sequence(-5, 6);
```

```surql title="Output"
[-5, -4, -3, -2, -1, 0]
```

## `array::shuffle`

The `array::shuffle` function randomly shuffles the items of an array.

```surql title="API DEFINITION"
array::shuffle(array) -> array
```

The following example shows this function, and its possible output:

```surql
array::shuffle([ 1, 2, 3, 4, 5 ]);
```

```surql title="Possible output"
[ 2, 1, 4, 3, 5 ]
```

<br />

## `array::slice`

The `array::slice` function returns a slice of an array, based on a starting position, and a length or negative position.

```surql title="API DEFINITION"
array::slice(array, $start: int, $len: int) -> array
-- Also since 3.0.0
array::slice(array, $slice: range) -> array;
```

The following example shows this function, and its output:

```surql
array::slice([ 1, 2, 3, 4, 5 ], 1, 3);
```

```surql title="Output"
[2, 3]
```

The following example shows how you can use this function with a starting position, and a negative position, which will slice off the first and last element from the array:

```surql
array::slice([ 1, 2, 3, 4, 5 ], 1, -1);
```

```surql title="Output"
[2, 3, 4]
```

The following example shows how you can use this function with just a starting position, which will only slice from the beginning of the array:

```surql
array::slice([ 1, 2, 3, 4, 5 ], 2);
```

```surql title="Output"
[ 3, 4, 5 ]
```

The following example shows how you can use this function with just a negative position, which will only slice from the end of the array:

```surql
array::slice([ 1, 2, 3, 4, 5 ], -2);
```

```surql title="Output"
[ 4, 5 ]
```

The following example shows how you can use this function with a negative position, and a length of the slice:

```surql
array::slice([ 1, 2, 3, 4, 5 ], -3, 2);

[ 3, 4 ]
```

An example of post SurrealDB 3.0 syntax in which the function can also take a range:

```surql
['a', 'b', 'c', 'd', 'e'].slice(2..=3);
```

```surql title="Output"
[ 'c', 'd' ]
```

<br />

## `array::sort`

The `array::sort` function sorts the values in an array in ascending or descending order.

```surql title="API DEFINITION"
array::sort(array) -> array
```

The function also accepts a second boolean parameter which determines the sorting direction. The second parameter can be `true` for ascending order, or `false` for descending order.

```surql title="API DEFINITION"
array::sort(array, $asc: bool) -> array
```

The function also accepts a second string parameter which determines the sorting direction. The second parameter can be `'asc'` for ascending order, or `'desc'` for descending order.

```surql title="API DEFINITION"
array::sort(array, $order: string) -> array
```

The following example shows this function, and its output:

```surql
array::sort([ 1, 2, 1, null, "something", 3, 3, 4, 0 ]);
```

```surql title="Output"
[ null, 0, 1, 1, 2, 3, 3, 4, "something" ]
```

```surql
array::sort([1, 2, 1, null, "something", 3, 3, 4, 0], false);
```

```surql title="Output"
[ "something", 4, 3, 3, 2, 1, 1, 0, null ]
```

```surql
array::sort([1, 2, 1, null, "something", 3, 3, 4, 0], "asc");
```

```surql title="Output"
[ null, 0, 1, 1, 2, 3, 3, 4, "something" ]
```

```surql
array::sort([1, 2, 1, null, "something", 3, 3, 4, 0], "desc");
```

```surql title="Output"
[ "something", 4, 3, 3, 2, 1, 1, 0, null ]
```

## `array::sort_lexical`

The `array::sort_natural_lexical` function sorts the values in an array in ascending or descending order, with alphabetical strings sorted in lexical order instead of unicode list order.

```surql title="API DEFINITION"
array::sort_lexical(array) -> array
```

The function also accepts a second boolean parameter which determines the sorting direction. The second parameter can be `true` for ascending order, or `false` for descending order.

```surql title="API DEFINITION"
array::sort_lexical(array, $asc: bool) -> array
```

The function also accepts a second string parameter which determines the sorting direction. The second parameter can be `'asc'` for ascending order, or `'desc'` for descending order.

```surql title="API DEFINITION"
array::sort_lexical(array, $order: string) -> array
```

The following example shows that `array::sort_lexical` will sort strings in lexical (alphabetical) order instead of Unicode list order. As an accented 'Á' is listed later in Unicode than regular ASCII letters, the function `array::sort` will show the name 'Álvares' listed after the word 'senhor', but `array::sort_lexical` will show the name at the front of the array instead.

```surql
['Obrigado', 'senhor', 'Álvares'].sort();
['Obrigado', 'senhor', 'Álvares'].sort_lexical();
```

```surql title="Output"
-------- Query 1 --------

[ 'Obrigado', 'senhor', 'Álvares' ]

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

[ 'Álvares', 'Obrigado', 'senhor' ]
```

## `array::sort_natural`

The `array::sort_natural` function sorts the values in an array in ascending or descending order, with numeric strings sorted in numeric order instead of regular string order.

```surql title="API DEFINITION"
array::sort_natural(array) -> array
```

The function also accepts a second boolean parameter which determines the sorting direction. The second parameter can be `true` for ascending order, or `false` for descending order.

```surql title="API DEFINITION"
array::sort_natural(array, $asc: bool) -> array
```

The function also accepts a second string parameter which determines the sorting direction. The second parameter can be `'asc'` for ascending order, or `'desc'` for descending order.

```surql title="API DEFINITION"
array::sort_natural(array, $order: string) -> array
```

The following example shows that `array::sort_natural` will sort numeric strings as if they were numbers. The `array::sort` function, on the other hand, treats a string like '3' as greater than '11' due to the first character in '3' being greater than '1'.

Note that strings sorted in numeric order will still appear after actual numbers, as [a string will always be greater than a number](/docs/reference/query-language/language-primitives/data-types/values.md).

```surql
[8, 9, 10, '3', '2.2', '11'].sort();
[8, 9, 10, '3', '2.2', '11'].sort_natural();
```

```surql title="Output"
-------- Query --------

[ 8, 9, 10, '11', '2.2', '3' ]

-------- Query 2 (332.667µs) --------

[ 8, 9, 10, '2.2', '3', '11' ]
```

## `array::sort_natural_lexical`

The `array::sort_natural_lexical` function sorts the values in an array in ascending or descending order, while sorting numeric strings in numeric order and alphabetical strings in lexical order.

```surql title="API DEFINITION"
array::sort_natural_lexical(array) -> array
```

The function also accepts a second boolean parameter which determines the sorting direction. The second parameter can be `true` for ascending order, or `false` for descending order.

```surql title="API DEFINITION"
array::sort_natural_lexical(array, $asc: bool) -> array
```

The function also accepts a second string parameter which determines the sorting direction. The second parameter can be `'asc'` for ascending order, or `'desc'` for descending order.

```surql title="API DEFINITION"
array::sort_natural_lexical(array, $order: string) -> array
```

The following example shows that `array::sort_natural_lexical` will sort numeric strings as if they were numbers, and alphabetical strings in lexical order instead of Unicode order. The `array::sort` function, on the other hand, treats a string like '3' as greater than '11' due to the first character in '3' being greater than '1', and sorts the name 'Álvares' after the string 'senhor' because the 'Á' character comes after regular ASCII characters in Unicode.

```surql
['Obrigado', 'senhor', 'Álvares', 8, 9, 10, '3', '2.2', '11'].sort();
['Obrigado',
  'senhor',
  'Álvares',
  8,
  9,
  10,
  '3',
  '2.2',
  '11'].sort_natural_lexical();
```

```surql title="Output"
-------- Query --------

[ 8, 9, 10, '11', '2.2', '3', 'Obrigado', 'senhor', 'Álvares' ]

-------- Query 2 (332.667µs) --------

[ 8, 9, 10, '2.2', '3', '11', 'Álvares', 'Obrigado', 'senhor' ]
```

## `array::sort::asc`

The `array::sort::asc` function is a shorthand convenience function for the `array::sort` function, to sort values in an array in ascending order.

```surql title="API DEFINITION"
array::sort::asc(array) -> array
```

The following example shows this function, and its output:

```surql
array::sort::asc([ 1, 2, 1, null, "something", 3, 3, 4, 0 ]);
```

```surql title="Output"
[ null, 0, 1, 1, 2, 3, 3, 4, "something" ]
```

<br />

## `array::sort::desc`

The `array::sort::desc` function is a shorthand convenience function for the `array::sort` function, to sort values in an array in descending order.

```surql title="API DEFINITION"
array::sort::desc(array) -> array
```

The following example shows this function, and its output:

```surql
array::sort::desc([ 1, 2, 1, null, "something", 3, 3, 4, 0 ]);
```

```surql title="Output"
[ "something", 4, 3, 3, 2, 1, 1, 0, null ]
```

<br />

## `array::swap`

The `array::swap` function swaps two values of an array based on indexes.

```surql title="API DEFINITION"
array::swap(array, $from: int, $to: int) -> array
```

The following example shows this function, and its output:

```surql
array::swap(["What's",
  "its",
  "got",
  "in",
  "it",
  "pocketses?"],
  1,
  4);
```

```surql title="Output"
[
	"What's",
	'it',
	'got',
	'in',
	'its',
	'pocketses?'
]
```

The following example shows how you can use this function with a positive index, and a negative index, which will swap the first and last element from the array:

```surql
array::swap([ 1, 2, 3, 4, 5 ], 0, -1);
```

```surql title="Output"
[ 5, 2, 3, 4, 1 ]
```

An error will be returned if any of the indexes are invalid that informs of range of possible indexes that can be used.

```surql
array::swap([0, 1], 100, 1000000);
```

```surql title="Output"
'Incorrect arguments for function array::swap(). Argument 1 is out of range. Expected a number between -2 and 2'
```

<br />

## `array::transpose`

The `array::transpose` function is used to perform 2d array transposition. It is similar to zipping in other programming languages except that when arrays of differing sizes are transposed they are 'layered' on top of each other, producing an output with the same length as the longer array instead of stopping at the length of the smaller array.

```surql title="API DEFINITION"
array::transpose(array<array>) -> array<array>
```

The following example shows this function, and its output:

```surql
array::transpose([[0, 1], [2, 3]]);
```

```surql title="Output"
[ [0, 2], [1, 3] ]
```

The layering of the above example can be visualised as follows.

```text
0 1
2 3
↓ ↓ 
0 1   
2 3
```

Imagining a Rubik's Cube is another easy way to conceptualize this function.

```surql
array::transpose([
    ['🟦', '🟥', '🟩'],
    ['⬜', '🟦', '🟨'],
    ['🟧', '🟧', '🟥']
]);
```

The output shows the same blocks, but lined up top to bottom instead of left to right.

```surql
[
	[
		'🟦',
		'⬜',
		'🟧'
	],
	[
		'🟥',
		'🟦',
		'🟧'
	],
	[
		'🟩',
		'🟨',
		'🟥'
	]
]
```

Another example of the function used for the statistics of two people:

```surql
[["Name", "Age"], ["Billy", 25], ["Alice", 30]].transpose();
```

```surql title="Output"
[
	[
		'Name',
		'Billy',
		'Alice'
	],
	[
		'Age',
		25,
		30
	]
]
```

When the input arrays differ in length, `NONE` is added at indices where no item is found. Take the following movies for example, in which one - Groundhog Day - does not have a bad guy.

```surql
[
    ['Movie', 'Bad guy'], 
    ['Avengers: Infinity War', 'Thanos'], 
    ['Groundhog Day'],
    ['Star Wars', 'Palpatine']
].transpose();
```

**Output since 2.2**

```surql
[
	[
		'Movie',
		'Avengers: Infinity War',
		'Groundhog Day',
		'Star Wars'
	],
	[
		'Bad guy',
		'Thanos',
		NONE,
		'Palpatine'
	]
]
```

**Output before 2.2**

```surql
[
	[
		'Movie',
		'Avengers: Infinity War',
		'Groundhog Day',
		'Star Wars'
	],
	[
		'Bad guy',
		'Thanos',
		'Palpatine'
	]
]
```

This new behaviour allows transposed arrays to be transposed once more to restore the original output, except with `NONE` added in all the indexes that lack in any array.

```surql
[
	[
		'Movie',
		'Bad guy'
	],
	[
		'Avengers: Infinity War',
		'Thanos'
	],
	[
		'Groundhog Day'
	],
	[
		'Star Wars',
		'Palpatine'
	]
].transpose().transpose();
```

```surql title="Output"
[
	[
		'Movie',
		'Bad guy'
	],
	[
		'Avengers: Infinity War',
		'Thanos'
	],
	[
		'Groundhog Day',
		NONE
	],
	[
		'Star Wars',
		'Palpatine'
	]
]
```

## `array::union`

The `array::union` function combines two arrays together, removing duplicate values, and returning a single array.

```surql title="API DEFINITION"
array::union(array, $other: array) -> array
```

The following example shows this function, and its output:

```surql
array::union([1, 2, 1, 6], [1, 3, 4, 5, 6]);
```

```surql title="Output"
[ 1, 2, 6, 3, 4, 5 ]
```

<br /><br />

## `array::windows`

```surql title="API DEFINITION"
array::windows(array, $window_size: int) -> array
```

The `array::windows` function returns a number of arrays of length `size` created by moving one index at a time down the original array. The arrays returned are guaranteed to be of length `size`. As a result, the function will return an empty array if the length of the original array is not large enough to create a single output array.

The following examples show this function, and its output:

```surql
LET $array = [1, 2, 3, 4];
RETURN array::windows($array, 2);
RETURN array::windows($array, 5);
```

```surql title="Output"
[ [1, 2], [2, 3], [3, 4] ];
[];
```

An example of the same function used in a `RELATE` statement:

```surql
CREATE person:grandfather, person:father, person:son;

FOR $pair IN array::windows(["grandfather", "father", "son"], 2) {
    LET $first = type::record("person", $pair[0]);
    LET $second = type::record("person", $pair[1]);
    RELATE $first->father_of->$second;
};

SELECT 
  id, 
  ->father_of->person AS sons,
  ->father_of->person->father_of->person AS grandsons
FROM person;
```

## Method chaining

Method chaining allows functions to be called using the `.` dot operator on a value of a certain type instead of the full path of the function followed by the value.

```surql
-- Traditional syntax
array::push(["Again", "again"], "again");

-- Method chaining syntax
["Again", "again"].push("again");
```

```surql title="Output"
["Again", "again", "again"]
```

This is particularly useful for readability when a function is called multiple times.

```surql
-- Traditional syntax
array::join(array::push(["Again", "again"], "again"), " and ");

-- Method chaining syntax
["Again", "again"].push("again").join(" and ");
```

```surql title="Output"
"Again and again and again"
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/bytes

# Bytes

These functions can be used when working with bytes.

These functions can be used when working with bytes in SurrealQL.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#byteslen"><code>bytes::len()</code></a></td>
      <td scope="row" data-label="Description">Gives the length in bytes</td>
    </tr>
  </tbody>
</table>

## `bytes::len`

The `bytes::len` function returns the length in bytes of a `bytes` value.

```surql title="API DEFINITION"
bytes::len(bytes) -> int
```

The following example shows this function, and its output:

```surql
[
    bytes::len(<bytes>"Simple ASCII string"),
    bytes::len(<bytes>"οὐ γὰρ δυνατόν ἐστιν ἔτι καθεύδειν"),
    bytes::len(<bytes>"청춘예찬 靑春禮讚")
];
```

```surql title="Output"
[ 19, 67, 25 ]
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/count

# Count

This function can be used when counting field values and expressions.

This function can be used when counting field values and expressions.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#count"><code>count()</code></a></td>
      <td scope="row" data-label="Description">Counts a record, or whether a given value is truthy</td>
    </tr>
  </tbody>
</table>

## `count`

The count function counts the number of times that the function is called. In a [`SELECT`](/docs/reference/query-language/statements/select.md) that aggregates with [`GROUP BY`](/docs/reference/query-language/clauses/group.md) or `GROUP ALL`, that call count is the size of each group. Without a `GROUP BY` or `GROUP ALL` clause there is no group to size, so `count()` returns `1` once per record.

```surql title="API DEFINITION"
count() -> 1
```
If a value is given as the first argument, then this function checks whether a given value is [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness). This is useful for returning the total number of records which match a certain condition in a [`SELECT`](/docs/reference/query-language/statements/select.md) with a `GROUP BY` or `GROUP ALL` clause.

```surql title="API DEFINITION"
count(any) -> number
```

If an array is given, this function counts the number of items in the array which are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness). If, instead, you want to count the total number of items in the given array, then use the [`array::len()`](/docs/reference/query-language/functions/database-functions/array.md#arraylen) function.

```surql title="API DEFINITION"
count(array) -> number
```
The following example shows this function, and its output:

```surql 
count();
```

```surql title="Output"
1
```

```surql
count(true);
```

```surql title="Output"
1
```

```surql
count(10 > 15);
```

```surql title="Output"
0
```

```surql
count([ 1, 2, 3, null, 0, false, (15 > 10), rand::uuid() ]);

5
```

The following examples show this function being used in a [`SELECT`](/docs/reference/query-language/statements/select.md) statement with a `GROUP ALL` clause.

```surql
SELECT 
	count() 
FROM [
	{ age: 33 }, 
	{ age: 45 }, 
	{ age: 39 }
] 
GROUP ALL;
```

```surql title="Output"
[
	{ count: 3 }
]
```

```surql
SELECT 
	count(age > 35) 
FROM [
	{ age: 33 }, 
	{ age: 45 }, 
	{ age: 39 }
] 
GROUP ALL;
```

```surql title="Output"
[
	{ count: 2 }
]
```

An advanced example of the count function can be seen below:

```surql
SELECT
	country,
	count(age > 30) AS total
FROM [
	{ age: 33, country: 'GBR' },
	{ age: 45, country: 'GBR' },
	{ age: 39, country: 'USA' },
	{ age: 29, country: 'GBR' },
	{ age: 43, country: 'USA' }
]
GROUP BY country;
```

```surql title="Output"
[
	{
		country: 'GBR',
		total: 2
	},
	{
		country: 'USA',
		total: 2
	}
]
```

<br /><br />

## Counting the records that link to each record

To count the records that link to each record, such as the comments on each post, count inside the projection. Where the link field is defined with [`REFERENCE`](/docs/reference/query-language/language-primitives/record-references.md), `<~comment` gives the comments that link to the current post:

```surql
DEFINE FIELD post ON comment TYPE record<post> REFERENCE;

CREATE post:a, post:b, post:c;
CREATE comment SET post = post:a;
CREATE comment SET post = post:a;
CREATE comment SET post = post:b;

SELECT id, count(<~comment) AS comments FROM post;
```

```surql title="Output"
[
	{ comments: 2, id: post:a },
	{ comments: 1, id: post:b },
	{ comments: 0, id: post:c }
]
```

Without `REFERENCE`, a subquery gives the same result, with `$parent` as the current post:

```surql
SELECT id, (SELECT VALUE id FROM comment WHERE post = $parent.id).len() AS comments FROM post;
```

Grouping the comments instead, with `SELECT post, count() FROM comment GROUP BY post`, gives the same counts but leaves out every post that has no comments, because no comment carries its ID.

## Using a `COUNT` index with `count()`

_(since v3.0.0)_

A `COUNT` index can be defined to speed up `count()` when used with a `GROUP ALL` clause. This allows `count()` to access a single stored value when it is called instead of iterating over the entire table.

```surql
CREATE user;
-- One record in table, very fast
SELECT count() FROM user GROUP ALL;

-- 10,000 new records,
-- count() takes a bit longer than before
CREATE |user:10000| RETURN NONE;
SELECT count() FROM user GROUP ALL;

-- Add index, wait a moment for it to build
DEFINE INDEX user_count ON user COUNT;
-- count() very performant again
SELECT count() FROM user GROUP ALL;
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/crypto

# Crypto

These functions can be used when hashing data, encrypting data, and for securely authenticating users into the database.

These functions can be used when hashing data, encrypting data, and for securely authenticating users into the database.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptoblake3"><code>crypto::blake3()</code></a></td>
      <td scope="row" data-label="Description">Returns the blake3 hash of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptojoaat"><code>crypto::joaat()</code></a></td>
      <td scope="row" data-label="Description">Returns the joaat hash of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptomd5"><code>crypto::md5()</code></a></td>
      <td scope="row" data-label="Description">Returns the md5 hash of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptosha1"><code>crypto::sha1()</code></a></td>
      <td scope="row" data-label="Description">Returns the sha1 hash of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptosha256"><code>crypto::sha256()</code></a></td>
      <td scope="row" data-label="Description">Returns the sha256 hash of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptosha512"><code>crypto::sha512()</code></a></td>
      <td scope="row" data-label="Description">Returns the sha512 hash of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptoargon2compare"><code>crypto::argon2::compare()</code></a></td>
      <td scope="row" data-label="Description">Compares an argon2 hash to a password</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptoargon2generate"><code>crypto::argon2::generate()</code></a></td>
      <td scope="row" data-label="Description">Generates a new argon2 hashed password</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptobcryptcompare"><code>crypto::bcrypt::compare()</code></a></td>
      <td scope="row" data-label="Description">Compares an bcrypt hash to a password</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptobcryptgenerate"><code>crypto::bcrypt::generate()</code></a></td>
      <td scope="row" data-label="Description">Generates a new bcrypt hashed password</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptopbkdf2compare"><code>crypto::pbkdf2::compare()</code></a></td>
      <td scope="row" data-label="Description">Compares an pbkdf2 hash to a password</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptopbkdf2generate"><code>crypto::pbkdf2::generate()</code></a></td>
      <td scope="row" data-label="Description">Generates a new pbkdf2 hashed password</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptoscryptcompare"><code>crypto::scrypt::compare()</code></a></td>
      <td scope="row" data-label="Description">Compares an scrypt hash to a password</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#cryptoscryptgenerate"><code>crypto::scrypt::generate()</code></a></td>
      <td scope="row" data-label="Description">Generates a new scrypt hashed password</td>
    </tr>
  </tbody>
</table>

## `crypto::blake3`

The `crypto::blake3` function returns the blake3 hash of the input value.

```surql title="API DEFINITION"
crypto::blake3(string) -> string
```
The following example shows this function, and its output:

```surql
crypto::blake3("tobie");
```

```surql title="Output"
'85052e9aab1b67b6622d94a08441b09fd5b7aca61ee360416d70de5da67d86ca'
```

<br />

## `crypto::joaat`

_(since v3.0.0)_

The `crypto::joaat` function returns the joaat hash of the input value.

```surql title="API DEFINITION"
crypto::joaat(string) -> number
```
The following example shows this function, and its output:

```surql
crypto::joaat("tobie");
```

```surql title="Output"
2129482046
```

<br />

## `crypto::md5`

The `crypto::md5` function returns the md5 hash of the input value.

```surql title="API DEFINITION"
crypto::md5(string) -> string
```
The following example shows this function, and its output:

```surql
crypto::md5("tobie");
```

```surql title="Output"
"4768b3fc7ac751e03a614e2349abf3bf"
```

<br />

## `crypto::sha1`

The `crypto::sha1` function returns the sha1 hash of the input value.

```surql title="API DEFINITION"
crypto::sha1(string) -> string
```
The following example shows this function, and its output:

```surql
crypto::sha1("tobie");
```

```surql title="Output"
"c6be709a1b6429472e0c5745b411f1693c4717be"
```

<br />

## `crypto::sha256`

The `crypto::sha256` function returns the sha256 hash of the input value.

```surql title="API DEFINITION"
crypto::sha256(string) -> string
```
The following example shows this function, and its output:

```surql
crypto::sha256("tobie");
```

```surql title="Output"
"33fe1859daba927ea5674813adc1cf34b9e2795f2b7e91602fae19c0d0c493af"
```

<br />

## `crypto::sha512`

The `crypto::sha512` function returns the sha512 hash of the input value.

```surql title="API DEFINITION"
crypto::sha512(string) -> string
```
The following example shows this function, and its output:

```surql
crypto::sha512("tobie");

"39f0160c946c4c53702112d6ef3eea7957ea8e1c78787a482a89f8b0a8860a20ecd543432e4a187d9fdcd1c415cf61008e51a7e8bf2f22ac77e458789c9cdccc"
```

<br />

## `crypto::argon2::compare`

The `crypto::argon2::compare` function compares a hashed-and-salted argon2 password value with an unhashed password value.

```surql title="API DEFINITION"
crypto::argon2::compare(string, $against: string) -> bool
```
The following example shows this function, and its output:

```surql
LET $hash = "$argon2id$v=19$m=19456,t=2,p=1$gM/X3p83p+Kbs+OOOqvtkg$oFQyOl1PEf8BHMEh055YZmgDglc8Nf8F5u3v8faCxHg";
LET $pass = "this is a strong password";
RETURN crypto::argon2::compare($hash, $pass);
```

```surql title="Output"
true
```

<br />

## `crypto::argon2::generate`

The `crypto::argon2::generate` function hashes and salts a password using the argon2 hashing algorithm.

> [!IMPORTANT]
> At this time, there is no way to customise the parameters for this function. This applies to: memory, iterations and parallelism.

```surql title="API DEFINITION"
crypto::argon2::generate(string) -> string
```
The following example shows this function, and its output:

```surql
crypto::argon2::generate("this is a strong password");
```

```surql title="Possible output"
"$argon2id$v=19$m=19456,t=2,p=1$gM/X3p83p+Kbs+OOOqvtkg$oFQyOl1PEf8BHMEh055YZmgDglc8Nf8F5u3v8faCxHg"
```

The function runs on the server, as part of a query. A parameter sent from a client SDK or with a sign-up is a value, not SurrealQL, so a string such as `"crypto::argon2::generate('428913')"` is stored as that text and the function is never called. To hash a value that the client sends, call the function in the query that writes it, for example in the `SIGNUP` clause of a [record access method](/docs/reference/query-language/statements/define/access/record.md). A field's `VALUE` clause is not a good place for it: the clause runs again on every update of the record, so `VALUE crypto::argon2::generate($value)` hashes the stored hash again and `crypto::argon2::compare` no longer matches.

<br />

## `crypto::bcrypt::compare`

The `crypto::bcrypt::compare` function compares a hashed-and-salted bcrypt password value with an unhashed password value.

```surql title="API DEFINITION"
crypto::bcrypt::compare(string, $against: string) -> bool
```
The following example shows this function, and its output:

```surql
LET $hash = "$2b$12$OD7hrr1Hycyk8NUwOekYY.cogCICpUnwNvDZ9NiC1qCPHzpVAQ9BO";
LET $pass = "this is a strong password";
RETURN crypto::bcrypt::compare($hash, $pass);

true
```

<br />

## `crypto::bcrypt::generate`

The `crypto::bcrypt::generate` function hashes and salts a password using the bcrypt hashing algorithm.

> [!IMPORTANT]
> At this time, there is no way to customise the work factor for bcrypt.

```surql title="API DEFINITION"
crypto::bcrypt::generate(string) -> string
```
The following example shows this function, and its output:

```surql
crypto::bcrypt::generate("this is a strong password");

"$2b$12$OD7hrr1Hycyk8NUwOekYY.cogCICpUnwNvDZ9NiC1qCPHzpVAQ9BO"
```

<br />

## `crypto::pbkdf2::compare`

The `crypto::pbkdf2::compare` function compares a hashed-and-salted pbkdf2 password value with an unhashed password value.

```surql title="API DEFINITION"
crypto::pbkdf2::compare(string, $against: string) -> bool
```
The following example shows this function, and its output:

```surql
LET $hash = "$pbkdf2-sha256$i=10000,l=32$DBURRPJODKEt0IId1Lqe+w$Ve8Z00mibHDSKLbyKTceEBBcDpGoK0AEUl7QzDTIec4";
LET $pass = "this is a strong password";
RETURN crypto::pbkdf2::compare($hash, $pass);


true
```

<br />

## `crypto::pbkdf2::generate`

The `crypto::pbkdf2::generate` function hashes and salts a password using the pbkdf2 hashing algorithm.

> [!IMPORTANT]
> At this time, there is no way to customise the number of iterations for pbkdf2.

```surql title="API DEFINITION"
crypto::pbkdf2::generate(string) -> string
```
The following example shows this function, and its output:

```surql
crypto::pbkdf2::generate("this is a strong password");

"$pbkdf2-sha256$i=10000,l=32$DBURRPJODKEt0IId1Lqe+w$Ve8Z00mibHDSKLbyKTceEB"
```

<br />

## `crypto::scrypt::compare`

The `crypto::scrypt::compare` function compares a hashed-and-salted scrypt password value with an unhashed password value.

```surql title="API DEFINITION"
crypto::scrypt::compare(string, $against: string) -> bool
```
The following example shows this function, and its output:

```surql
LET $hash = "$scrypt$ln=15,r=8,p=1$8gl7bipl0FELTy46YJOBrw$eRcS1qR22GI8VHo58WOXn9JyfDivGo5yTJFvpDyivuw";
LET $pass = "this is a strong password";
RETURN crypto::scrypt::compare($hash, $pass);


true
```

<br />

## `crypto::scrypt::generate`

The `crypto::scrypt::generate` function hashes and salts a password using the scrypt hashing algorithm.

> [!IMPORTANT]
> At this time, there is no way to customise the parameters for this function. This applies to: cost parameter, block size and parallelism.

```surql title="API DEFINITION"
crypto::scrypt::generate(string) -> string
```
The following example shows this function, and its output:

```surql
crypto::scrypt::generate("this is a strong password");

"$scrypt$ln=15,r=8,p=1$8gl7bipl0FELTy46YJOBrw$eRcS1qR22GI8VHo58WOXn9JyfDivGo5yTJFvpDyivuw"
```

<br /><br />

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/duration

# Duration

Functions and constants for working with duration-related data.

This page contains built-in functions and constants for converting between numeric and [duration](/docs/reference/query-language/language-primitives/data-types/durations.md) data.

> [!NOTE]
> Since version 3.0.0, the `::from::` functions (e.g. `duration::from::millis()`) now use underscores (e.g. `duration::from_millis()`) to better match the intent of the function and method syntax.

## Duration functions

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationdays"><code>duration::days()</code></a></td>
      <td scope="row" data-label="Description">Counts how many days fit in a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationhours"><code>duration::hours()</code></a></td>
      <td scope="row" data-label="Description">Counts how many hours fit in a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationmicros"><code>duration::micros()</code></a></td>
      <td scope="row" data-label="Description">Counts how many microseconds fit in a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationmillis"><code>duration::millis()</code></a></td>
      <td scope="row" data-label="Description">Counts how many milliseconds fit in a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationmins"><code>duration::mins()</code></a></td>
      <td scope="row" data-label="Description">Counts how many minutes fit in a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationnanos"><code>duration::nanos()</code></a></td>
      <td scope="row" data-label="Description">Counts how many nanoseconds fit in a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationsecs"><code>duration::secs()</code></a></td>
      <td scope="row" data-label="Description">Counts how many seconds fit in a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationweeks"><code>duration::weeks()</code></a></td>
      <td scope="row" data-label="Description">Counts how many weeks fit in a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationyears"><code>duration::years()</code></a></td>
      <td scope="row" data-label="Description">Counts how many years fit in a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationfrom_days"><code>duration::from_days()</code></a></td>
      <td scope="row" data-label="Description">Converts a numeric amount of days into a duration that represents days</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationfrom_hours"><code>duration::from_hours()</code></a></td>
      <td scope="row" data-label="Description">Converts a numeric amount of hours into a duration that represents hours</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationfrom_micros"><code>duration::from_micros()</code></a></td>
      <td scope="row" data-label="Description">Converts a numeric amount of microseconds into a duration that represents microseconds</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationfrom_millis"><code>duration::from_millis()</code></a></td>
      <td scope="row" data-label="Description">Converts a numeric amount of milliseconds into a duration that represents milliseconds</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationfrom_mins"><code>duration::from_mins()</code></a></td>
      <td scope="row" data-label="Description">Converts a numeric amount of minutes into a duration that represents minutes</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationfrom_nanos"><code>duration::from_nanos()</code></a></td>
      <td scope="row" data-label="Description">Converts a numeric amount of nanoseconds into a duration that represents nanoseconds</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationfrom_secs"><code>duration::from_secs()</code></a></td>
      <td scope="row" data-label="Description">Converts a numeric amount of seconds into a duration that represents seconds</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#durationfrom_weeks"><code>duration::from_weeks()</code></a></td>
      <td scope="row" data-label="Description">Converts a numeric amount of weeks into a duration that represents weeks</td>
    </tr>
  </tbody>
</table>

## Duration constants

<table>
  <thead>
    <tr>
      <th scope="col">Constant</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Constant"><a href="#durationmax"><code>duration::max</code></a></td>
      <td scope="row" data-label="Description">Constant representing the greatest possible duration</td>
    </tr>
  </tbody>
</table>

## `duration::days`

The `duration::days` function counts how many days fit into a duration.

```surql title="API DEFINITION"
duration::days(duration) -> number
```

The following example shows this function, and its output:

```surql
duration::days(3w);
```

```surql title="Output"
21
```

<br />

## `duration::hours`

The `duration::hours` function counts how many hours fit into a duration.

```surql title="API DEFINITION"
duration::hours(duration) -> number
```

The following example shows this function, and its output:

```surql
duration::hours(3w);
```

```surql title="Output"
504
```

<br />

## `duration::max`

_(since v2.3.0)_

The `duration::max` constant represents the greatest possible duration that can be used.

```surql title="API DEFINITION"
duration::max -> duration
```

Some examples of the constant in use:

```surql
duration::max;

duration::max + 1ns;

100y IN 0ns..duration::max
```

```surql title="Output"
-------- Query 1 --------

584942417355y3w5d7h15s999ms999µs999ns

-------- Query 2 --------
'Failed to compute: "584942417355y3w5d7h15s999ms999µs999ns + 1ns", as the operation results in an arithmetic overflow.'

-------- Query 3 --------
true
```

<br />

## `duration::micros`

The `duration::micros` function counts how many microseconds fit into a duration.

```surql title="API DEFINITION"
duration::micros(duration) -> number
```

The following example shows this function, and its output:

```surql
duration::micros(3w);
```

```surql title="Output"
1814400000000
```

<br />

## `duration::millis`

The `duration::millis` function counts how many milliseconds fit into a duration.

```surql title="API DEFINITION"
duration::millis(duration) -> number
```

The following example shows this function, and its output:

```surql
duration::millis(3w);
```

```surql title="Output"
1814400000
```

<br />

## `duration::mins`

The `duration::mins` function counts how many minutes fit into a duration.

```surql title="API DEFINITION"
duration::mins(duration) -> number
```

The following example shows this function, and its output:

```surql
duration::mins(3w);
```

```surql title="Output"
30240
```

<br />

## `duration::nanos`

The `duration::nanos` function counts how many nanoseconds fit into a duration.

```surql title="API DEFINITION"
duration::nanos(duration) -> number
```

The following example shows this function, and its output:

```surql
duration::nanos(3w);
```

```surql title="Output"
1814400000000000
```

<br />

## `duration::secs`

The `duration::secs` function counts how many seconds fit into a duration.

```surql title="API DEFINITION"
duration::secs(duration) -> number
```

The following example shows this function, and its output:

```surql
duration::secs(3w);
```

```surql title="Output"
1814400
```

<br />

## `duration::weeks`

The `duration::weeks` function counts how many weeks fit into a duration.

```surql title="API DEFINITION"
duration::weeks(duration) -> number
```

The following example shows this function, and its output:

```surql
duration::weeks(3w);
```

```surql title="Output"
3
```

<br />

## `duration::years`

The `duration::years` function counts how many years fit into a duration.

```surql title="API DEFINITION"
duration::years(duration) -> number
```

The following example shows this function, and its output:

```surql
duration::years(300w);
```

```surql title="Output"
5
```

<br />

## `duration::from_days`

The `duration::from_days` function counts how many years fit into a duration. The argument must be non-negative; negative values return an error.

```surql title="API DEFINITION"
duration::from_days(number) -> duration
```

The following example shows this function, and its output:

```surql
duration::from_days(3);
```

```surql title="Output"
3d
```

<br />

## `duration::from_hours`

The `duration::from_hours` function converts a numeric amount of hours into a duration that represents hours. The argument must be non-negative; negative values return an error.

```surql title="API DEFINITION"
duration::from_hours(number) -> duration
```

The following example shows this function, and its output:

```surql
duration::from_hours(3);
```

```surql title="Output"
3h
```

<br />

## `duration::from_micros`

The `duration::from_micros` function converts a numeric amount of microseconds into a duration that represents microseconds. The argument must be non-negative; negative values return an error.

```surql title="API DEFINITION"
duration::from_micros(number) -> duration
```

The following example shows this function, and its output:

```surql
duration::from_micros(3);
```

```surql title="Output"
3μs
```

<br />

## `duration::from_millis`

The `duration::from_millis` function converts a numeric amount of milliseconds into a duration that represents milliseconds. The argument must be non-negative; negative values return an error.

```surql title="API DEFINITION"
duration::from_millis(number) -> duration
```

The following example shows this function, and its output:

```surql
duration::from_millis(3);
```

```surql title="Output"
3ms
```

<br />

## `duration::from_mins`

The `duration::from_mins` function converts a numeric amount of minutes into a duration that represents minutes. The argument must be non-negative; negative values return an error.

```surql title="API DEFINITION"
duration::from_mins(number) -> duration
```

The following example shows this function, and its output:

```surql
duration::from_mins(3);
```

```surql title="Output"
3m
```

<br />

## `duration::from_nanos`

The `duration::from_nanos` function converts a numeric amount of nanoseconds into a duration that represents nanoseconds. The argument must be non-negative; negative values return an error.

```surql title="API DEFINITION"
duration::from_nanos(number) -> duration
```

The following example shows this function, and its output:

```surql
duration::from_nanos(3);
```

```surql title="Output"
3ns
```

<br />

## `duration::from_secs`

The `duration::from_secs` function converts a numeric amount of seconds into a duration that represents seconds. The argument must be non-negative; negative values return an error.

```surql title="API DEFINITION"
duration::from_secs(number) -> duration
```

The following example shows this function, and its output:

```surql
duration::from_secs(3);
```

```surql title="Output"
3s
```

<br />

## `duration::from_weeks`

The `duration::from_weeks` function converts a numeric amount of weeks into a duration that represents weeks. The argument must be non-negative; negative values return an error.

```surql title="API DEFINITION"
duration::from_weeks(number) -> duration
```

The following example shows this function, and its output:

```surql
duration::from_weeks(3);
```

```surql title="Output"
3w
```

<br /><br />

## Method chaining

Method chaining allows functions to be called using the `.` dot operator on a value of a certain type instead of the full path of the function followed by the value.

```surql
-- Traditional syntax
duration::mins(2d6h);

-- Method chaining syntax
2d6h.mins();
```

```surql title="Output"
3240
```

This is particularly useful for readability when a function is called multiple times.

```surql
-- Traditional syntax
duration::mins(duration::from_millis(98734234));

-- Method chaining syntax
duration::from_millis(98734234).mins();
```

```surql title="Output"
1645
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/encoding

# Encoding

These functions can be used to encode and decode data in base64. It is particularly used when that data needs to be stored and transferred over media that are designed to deal with text. This encoding and decoding helps to ensure that the data remains intact without modification during transport.

These functions can be used to encode and decode data into other formats, such as `base64` and [`CBOR`](/docs/reference/rest-api/cbor-protocol.md) (Concise Binary Object Representation). It is particularly used when that data needs to be stored and transferred over media that are designed to deal with text. This encoding and decoding helps to ensure that the data remains intact without modification during transport.

> [!TIP]
> For an overview of serialisation, parsing, analysis, and other representation transformations, see [Representations and codecs](/docs/learn/querying/concepts-and-guides/representations-and-codecs.md).

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function">
        <a href="#encodingbase64decode"><code>encoding::base64::decode()</code></a>
      </td>
      <td scope="row" data-label="Description">
        Decodes a base64-encoded string into bytes.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Function">
        <a href="#encodingbase64encode"><code>encoding::base64::encode()</code></a>
      </td>
      <td scope="row" data-label="Description">
        Encodes bytes into a base64 string, with optional padding.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Function">
        <a href="#encodingcbordecode"><code>encoding::cbor::decode()</code></a>
      </td>
      <td scope="row" data-label="Description">
        Decodes CBOR-formatted bytes into a SurrealQL value.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Function">
        <a href="#encodingcborencode"><code>encoding::cbor::encode()</code></a>
      </td>
      <td scope="row" data-label="Description">
        Encodes a SurrealQL value into CBOR-formatted bytes.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Function">
        <a href="#encodingjsondecode"><code>encoding::json::decode()</code></a>
      </td>
      <td scope="row" data-label="Description">
        Decodes a JSON string into a SurrealQL value.
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Function">
        <a href="#encodingjsonencode"><code>encoding::json::encode()</code></a>
      </td>
      <td scope="row" data-label="Description">
        Encodes a SurrealQL value into a JSON string.
      </td>
    </tr>
  </tbody>
</table>

<br></br>

## `encoding::base64::decode()`

The `encoding::base64::decode()` function decodes a string into bytes.

```surql title="API DEFINITION"
encoding::base64::decode(string) -> bytes
```

The following example shows this function, and its output:

```surql
encoding::base64::decode("MjMyMw");
```

```surql title="Output"
b"32333233"
```

You can also verify that the output of the encoded value matches the original value.

```surql
encoding::base64::decode("aGVsbG8") = <bytes>"hello";
```

```surql title="Output"
true
```

<br /><br />

## `encoding::base64::encode()`

The `encoding::base64::encode()` function encodes bytes to base64 with optionally padded output.

**API DEFINITION (after 2.3.0)**

```surql
encoding::base64::encode(bytes, $pad_output: option<bool>) -> string
```

**API DEFINITION (before 2.3.0)**

```surql
encoding::base64::encode(bytes) -> string
```

The following example shows this function, and its output:

```surql
encoding::base64::encode(<bytes>"");
```

```surql title="Output"
''
```

```surql
encoding::base64::encode(<bytes>"2323");
```

```surql title="Output"
'MjMyMw'
```

```surql
encoding::base64::encode(<bytes>"hello");
```

```surql title="Output"
'aGVsbG8'
```

You can pass `true` as the second argument to enable padded base64 outputs:

```surql
encoding::base64::encode(<bytes>"", true);
```

```surql title="Output"
""
```

```surql
encoding::base64::encode(<bytes>"2323", true);

"MjMyMw=="
```

```surql
encoding::base64::encode(<bytes>"hello", true);

"aGVsbG8="
```

<br />

## `encoding::cbor::decode()`

_(since v3.0.0)_

The `encoding::cbor::decode()` function decodes bytes in valid CBOR format into a SurrealQL value.

```surql title="API DEFINITION"
encoding::cbor::decode(string) -> any
```

```surql
LET $some_bytes = encoding::base64::decode("omRjYm9yaGVuY29kaW5nYmlza3ByZXR0eSBuZWF0");
encoding::cbor::decode($some_bytes);
```

```surql title="Output"
{
	cbor: 'encoding',
	is: 'pretty neat'
}
```

<br /><br />

## `encoding::cbor::encode()`

_(since v3.0.0)_

The `encoding::cbor::encode()` function encodes any SurrealQL value into bytes in CBOR format.

```surql title="API DEFINITION"
encoding::cbor::encode(any) -> bytes
```

```surql
encoding::cbor::encode({
    cbor: "encoding",
    is: "pretty neat"
});
```

```surql title="Output"
b"A26463626F7268656E636F64696E676269736B707265747479206E656174"
```

## `encoding::json::decode()`

_(since v3.1.0)_

The `encoding::json::decode()` function decodes a JSON string into a SurrealQL value.

```surql title="API DEFINITION"
encoding::json::decode(string) -> any
```

Examples of use:

```surql
encoding::json::decode('NONE');

LET $json = '{"user":{"name":"Tobie","tags":["admin","user"]}}';
encoding::json::decode($json);
```

```surql title="Output"
-------- Query --------
NONE

-------- Query --------
{
	user: {
		name: 'Tobie',
		tags: [
			'admin',
			'user'
		]
	}
}
```

## `encoding::json::encode()`

_(since v3.1.0)_

The `encoding::json::encode()` function encodes a SurrealQL value into a JSON string.

```surql title="API DEFINITION"
encoding::json::encode(any) -> string
```

Examples of use:

```surql
encoding::json::encode("8.8");

encoding::json::encode({ some: "data"});
```

```surql title="Output"
-------- Query --------
'"8.8"'

-------- Query --------
'{"some":"data"}'
```

As JSON has fewer data types than SurrealDB, note that a round trip from SurrealQL to JSON and back to SurrealQL is not guaranteed to be the same type.

```surql
encoding::json::decode(encoding::json::encode(NONE));
```

```surql title="Output"
NULL
```

If a round trip is required, a combination of JSON and CBOR functions can be used.

```surql
-- Value is '[198,246]'
-- (array from CBOR byte representation of NONE)
LET $json_byte_array = encoding::json::encode(
    <array>encoding::cbor::encode(NONE)
);

-- Value is b"C6F6"
-- (CBOR byte representation of NONE)
LET $byte_string = <bytes>encoding::json::decode($json_byte_array);

-- Returns NONE, not NULL
encoding::cbor::decode($byte_string);
```

## See also

- [Representations and codecs](/docs/learn/querying/concepts-and-guides/representations-and-codecs.md)
- [CBOR protocol](/docs/reference/rest-api/cbor-protocol.md)

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/eval

# Eval

Evaluate a SurrealQL or ISO GQL (Cypher Query Language) query string at runtime inside the caller's transaction, gated by dedicated capabilities.

_(since v3.2.0)_

> [!NOTE]
> `eval::*` is **denied for every subject by default**, including under [`--allow-all`](/docs/learn/security/authorization/capabilities.md). You must explicitly enable it with [`--allow-eval-query`](/docs/learn/security/authorization/capabilities.md#eval-queries) **and** satisfy the [arbitrary-query](/docs/learn/security/authorization/capabilities.md#arbitrary-queries) gate for the same subject. See [Security](#security) below.

The `eval::*` functions run a **query supplied as a string** inside the **caller's open transaction and session context**. They are intended for workloads where the query text is only known at runtime - for example, analytical queries stored in a table, where you want to blend [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md) and SurrealQL in one transaction, or to learn SurrealQL or rewrite existing queries from another graph database.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#evalgql"><code>eval::gql()</code></a></td>
      <td scope="row" data-label="Description">Evaluate a nested ISO GQL (Cypher Query Language) query and return its result</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#evalsurql"><code>eval::surql()</code></a></td>
      <td scope="row" data-label="Description">Evaluate a nested SurrealQL statement and return its value</td>
    </tr>
  </tbody>
</table>

For how `eval::*` fits with encoding, parsing, and other representation transforms, see [Representations and codecs](/docs/learn/querying/concepts-and-guides/representations-and-codecs.md).

## `eval::gql`

Evaluates an [ISO GQL (Cypher Query Language)](/docs/learn/querying/gql/overview.md) query string. One GQL query may lower to several internal statements. Unlike `eval::surql`, it is not limited to a single SurrealQL statement.

```surql title="API DEFINITION"
eval::gql(query: string, bindings: option<object>) -> any
```

`eval::gql` runs on the streaming query engine. Queries that include GQL **mutations** (`INSERT`, `SET`, `REMOVE`, `DELETE`) participate in the caller's write transaction - see [GQL mutations](/docs/learn/querying/gql/mutations.md). From **3.3.0**, GQL itself needs no experimental flag; you still need `--allow-eval-query`. On **3.2.x**, also pass `--allow-experimental gql`.

Enable eval on the server, for example:

```bash
surreal db start --user root --pass secret --allow-eval-query
```

GQL syntax is **Cypher-like**: parenthesised **node patterns** `(variable:label)`, arrow **edge patterns** `-[variable:type]->`, a mandatory **`MATCH`** clause, and **`RETURN`** for projection. See [Notable syntax differences from openCypher](/docs/learn/querying/gql/overview.md#notable-syntax-differences-from-opencypher) before pasting Neo4j queries verbatim.

### Sample graph

The following queries create a number of `person` and `city` records, followed by `INSERT RELATION` to join them together. The following `eval::gql` queries assume this seed data when indicating output.

```surql title="Seed data"
CREATE person:1 SET name = 'A', age = 30, active = true, city = 'London';
CREATE person:2 SET name = 'B', age = 20, active = false, city = 'Paris';
CREATE person:3 SET name = 'C', city = 'London';
CREATE city:1 SET name = 'London';
INSERT RELATION INTO knows [
	{ id: knows:k12, in: person:1, out: person:2, since: 2021 },
	{ id: knows:k21, in: person:2, out: person:1, since: 2018 },
	{ id: knows:k23, in: person:2, out: person:3, since: 2020 },
	{ id: knows:k1c, in: person:1, out: city:1, since: 2019 },
	{ id: knows:k31, in: person:3, out: person:1 }
];
```

| GQL pattern | Meaning in SurrealDB |
| --- | --- |
| `(n:person)` | Records in table `person` bound to variable `n` |
| `-[k:knows]->` | Directed edges in relation table `knows` (`in` / `out` record IDs) |
| `n.name` | Field `name` on the bound record |
| `$min` | Parameter - pass via the optional `bindings` object |

### Match nodes by label

`MATCH` finds graph patterns; `RETURN` projects columns (like `SELECT`).

```surql
eval::gql("MATCH (n:person) RETURN n.name AS name ORDER BY name");
```

```surql title="Output"
[{ name: 'A' }, { name: 'B' }, { name: 'C' }]
```

### Filter on node properties

`WHERE` filters rows after the pattern matches. Missing properties (person `C` has no `age`) are treated as unknown and excluded from comparisons.

```surql
eval::gql("MATCH (n:person) WHERE n.age < 25 RETURN n.name AS name ORDER BY name");
```

```surql title="Output"
{ name: 'B' }
```

### Traverse a typed edge

Chain node and edge patterns to walk the graph. Label both endpoints as `:person` to ignore the `person → city` edge.

```surql
eval::gql(
	"MATCH (a:person)-[k:knows]->(b:person) WHERE k.since > 2020 RETURN a.name, b.name ORDER BY a.name"
);
```

```surql title="Output"
{ 'a.name': 'A', 'b.name': 'B' }
```

### Optional match

`OPTIONAL MATCH` keeps every anchor row from the preceding `MATCH` even when the optional pattern misses - similar to a left outer join.

```surql
eval::gql(
	"MATCH (a:person) OPTIONAL MATCH (a)-[k:knows]->(b:city) RETURN a.name AS name, b.name AS city ORDER BY name"
);
```

```surql title="Output"
[
  { city: 'London', name: 'A' },
  { city: NONE, name: 'B' },
  { city: NONE, name: 'C' }
]
```

Only person `A` has a `knows` edge to the `city` node; everyone else still appears with `city: NONE`.

### Aggregate with GROUP BY

Count outgoing **person → person** `knows` edges per person.

```surql
eval::gql(
	"MATCH (a:person)-[:knows]->(b:person) RETURN a.name AS name, count(*) AS friends GROUP BY a.name ORDER BY name"
);
```

```surql title="Output"
[{ friends: 1, name: 'A' }, { friends: 2, name: 'B' }, { friends: 1, name: 'C' }]
```

### Multi-hop paths

In ISO GQL (Cypher Query Language) on SurrealDB, variable-length hops are a **postfix quantifier on the edge**, not Cypher's `*1..3` inside the brackets:

```surql
-- From A, who is reachable in 1-2 `knows` hops (staying on `:person` nodes)?
eval::gql(
	"MATCH (a:person)-[:knows]->{1,2}(b:person) WHERE a.name = 'A' RETURN a.name AS source, b.name AS target ORDER BY target"
);
```

```surql title="Output"
[
  { source: 'A', target: 'A' },
  { source: 'A', target: 'B' },
  { source: 'A', target: 'C' }
]
```

One hop reaches `B`; two hops loop back to `A` via `B` or reach `C` via `B`.

### Parameters

GQL parameters can be passed as the second argument to this function.

```surql
eval::gql(
	"MATCH (n:person) WHERE n.age > $min RETURN n.name AS name ORDER BY name",
	{ min: 25 }
);
```

```surql title="Output"
{ name: 'A' }
```

Bindings are **isolated** from the caller's scope (same as `eval::surql`); only keys you pass in the object are visible inside the GQL query.

> [!NOTE]
> When a GQL query returns a single record, `eval::gql` yields that record as an object; multiple records are returned as an array.

For more patterns - path search (`ALL SHORTEST`), comma-separated joins, and side-by-side SurrealQL - see [Sample GQL and SurrealQL queries](/docs/learn/querying/gql/sample-queries.md).

## `eval::surql`

Evaluates a **single** SurrealQL statement and returns its value. To run several statements, wrap them in a block `{ ... }` - the block's final value is returned.

```surql title="API DEFINITION"
eval::surql(query: string, bindings: option<object>) -> any
```

```surql
-- Simple expression
eval::surql("RETURN 1 + 1");
//- 2

-- Caller bindings become $parameters inside the nested query
eval::surql("RETURN $a + $b", { a: 2, b: 3 });
//- 5

-- Multiple statements: use a block
eval::surql("{ LET $x = 10; RETURN $x * 2 }");
//- 20
```

Nested writes run in the **caller's transaction**:

```surql
eval::surql("CREATE person:1 SET name = 'A'");
SELECT name FROM person;
```

```surql title="Output"
[{ name: 'A' }]
```

The evaluated query runs in an **isolated scope**: parameters visible at the call site are not inherited - only keys you pass in the bindings object are bound.

```surql
LET $secret = 42;
eval::surql("RETURN $secret ?? 'isolated'");
```

```surql title="Output"
'isolated'
```

## Notes on using eval:: functions

### Rejected inputs

The following are rejected inside `eval::*`:

- **Transaction and session control** - `BEGIN`, `CANCEL`, `COMMIT`, `USE`, `LIVE`, `KILL`, `OPTION`, `SHOW`, and access statements.
- **Bare multi-statement SurrealQL** in `eval::surql` - use `{ ... }` instead of semicolon-separated top-level statements.
- **Protected binding names** - caller bindings cannot overwrite reserved parameters such as `$session`.
- **Excessive nesting** - recursive `eval` calls share the engine's computation depth limit.

### Security

Every `eval::*` call is checked against the **current execution auth** (guest, record, or system). Auth limiting in [`DEFINE FUNCTION`](/docs/reference/query-language/statements/define/function.md) bodies never **raises** the subject - a record-scoped caller that invokes an owner-defined function which calls `eval` is still evaluated as `record`.

All of the following must pass:

| Gate | Purpose |
| --- | --- |
| [`allow-funcs` / `deny-funcs`](/docs/learn/security/authorization/capabilities.md) | The `eval` function family must be permitted |
| [`deny-arbitrary-query`](/docs/learn/security/authorization/capabilities.md#arbitrary-queries) (and related allow rules) | `eval` counts as an arbitrary query - denied subjects cannot use `eval` to bypass `/sql` or API lockdown |
| [`allow-eval-query` / `deny-eval-query`](/docs/learn/security/authorization/capabilities.md#eval-queries) | Dedicated opt-in for `eval::surql` and `eval::gql` |

`eval` cannot grant a subject more query power than arbitrary-query policy allows - including when called from inside a `DEFINE FUNCTION` or [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) handler. You do **not** need `--allow-arbitrary-query` for eval on a default server; you **do** need `--allow-eval-query`.

From **3.3.0**, `eval::gql` does not require the experimental `gql` capability (still required on **3.2.x**).

### Enabling on the server

`eval::*` is enforced by the **database engine** that executes your query, not by the client you type into.

#### Remote server (`surreal db start` + `surreal db sql`)

When the REPL connects over `ws://`, `http://`, or similar, pass capability flags on **`surreal db start`** only. The same flags on `surreal db sql` do not enable or disable `eval` at runtime.

```bash
# Terminal 1 - start the server with eval enabled for system users
surreal db start --user root --pass secret --allow-eval-query

# Terminal 2 - no --allow-eval-query needed on the client
surreal db sql -e ws://localhost:8000 --user root --pass secret
```

Environment variable equivalent on the **server** process:

**Bash**

```bash
export SURREAL_CAPS_ALLOW_EVAL_QUERY=system
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EVAL_QUERY = "system"
```

If you use `--deny-arbitrary-query` (for example to steer record users toward [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) only), add matching `--allow-arbitrary-query` entries for any subject that should call `eval`.

#### Embedded engine (`surreal db sql` only)

When you open the REPL against embedded storage (`memory`, `rocksdb://…`, …) without a separate `surreal db start` process, configure capabilities on **`surreal db sql`** instead:

```bash
surreal db sql --user root --pass secret --allow-eval-query
```

See [Capabilities and remote connections](/docs/reference/cli/db/commands/sql.md#capabilities-and-remote-connections) for the full remote versus embedded model.

See [`SURREAL_CAPS_ALLOW_EVAL_QUERY`](/docs/reference/cli/db/environment-variables.md) and [`SURREAL_CAPS_DENY_EVAL_QUERY`](/docs/reference/cli/db/environment-variables.md) in the environment variable reference.

## See also

- [Representations and codecs](/docs/learn/querying/concepts-and-guides/representations-and-codecs.md) - when to use `eval::*` versus encode, parse, or analyse functions
- [GQL overview](/docs/learn/querying/gql/overview.md) - ISO GQL (Cypher Query Language) on the wire and via `eval::gql`
- [Capabilities](/docs/learn/security/authorization/capabilities.md) - full capability model

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/file

# File

These functions can be used to work with files.

_(since v3.0.0)_

# File functions

These functions can be used to work with files.

> [!NOTE]
> Files support is currently experimental and subject to change. To use these functions and `f"..."` file pointers, either pass `--allow-experimental files` when [starting the database](/docs/reference/cli/db/commands/start.md) or set the `SURREAL_CAPS_ALLOW_EXPERIMENTAL` environment variable to `files`. On a server without the flag, file syntax is rejected at parse time.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#filebucket"><code>file::bucket()</code></a></td>
      <td scope="row" data-label="Description">Returns the bucket path from a file pointer</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#filecopy"><code>file::copy()</code></a></td>
      <td scope="row" data-label="Description">Copies the contents of a file</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#filecopy_if_not_exists"><code>file::copy_if_not_exists()</code></a></td>
      <td scope="row" data-label="Description">Copies the contents of a file to a new file if the name is available</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#filedelete"><code>file::delete()</code></a></td>
      <td scope="row" data-label="Description">Deletes a file</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#fileexists"><code>file::exists()</code></a></td>
      <td scope="row" data-label="Description">Checks if a file already exists</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#fileget"><code>file::get()</code></a></td>
      <td scope="row" data-label="Description">Loads a file</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#filehead"><code>file::head()</code></a></td>
      <td scope="row" data-label="Description">Returns the metadata of a file</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#filekey"><code>file::key()</code></a></td>
      <td scope="row" data-label="Description">Returns the key (the portion following the bucket) from a file pointer</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#filelist"><code>file::list()</code></a></td>
      <td scope="row" data-label="Description">Returns a list of files inside a bucket</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#fileput"><code>file::put()</code></a></td>
      <td scope="row" data-label="Description">Writes bytes to a file</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#fileput_if_not_exists"><code>file::put_if_not_exists()</code></a></td>
      <td scope="row" data-label="Description">Attempts to write bytes to a file</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#filerename"><code>file::rename()</code></a></td>
      <td scope="row" data-label="Description">Renames a file</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#filerename_if_not_exists"><code>file::rename_if_not_exists()</code></a></td>
      <td scope="row" data-label="Description">Renames a file if the new name is not already in use</td>
    </tr>
  </tbody>
</table>

## `file::bucket`

```surql title="API DEFINITION"
file::bucket(file) -> string
```

The `file::bucket` function returns the name of the bucket in which a file is located.

```surql
file::bucket(f"my_bucket:/file_name");

DEFINE PARAM $SOME_DATABASE_FILE VALUE f"my_bucket:/file_name";
file::bucket($SOME_DATABASE_FILE);
```

```surql title="Output"
'my_bucket'
```

The counterpart to this function is `file::key`, which returns the latter part of a file pointer.

## `file::copy`

The `file::copy` function copies the contents of a file to a new file, overwriting any existing file that has the same name as the new file.

```surql title="API DEFINITION"
file::copy(string)
```

Example of a file `my_book.txt` being copied to a new location `lion_witch_wardrobe.txt`:

```surql
f"my_bucket:/my_book.txt".copy("lion_witch_wardrobe.txt");
```

<br></br>

## `file::copy_if_not_exists`

The `file::copy_if_not_exists` function copies the contents of a file to a new file, returning an error if a file already exists that has the same name as that of the intended copy.

```surql title="API DEFINITION"
file::copy_if_not_exists(string)
```

Example of a file `my_book.txt` attempting to copy to a new location `lion_witch_wardrobe.txt`:

```surql
DEFINE BUCKET my_bucket BACKEND "memory";

f"my_bucket:/lion_witch_wardrobe.txt".put("Once there were four children whose names were Peter, Susan...");
f"my_bucket:/other_book.txt".put("Um meine Geschichte zu erzählen, muß ich weit vorn anfangen.");
f"my_bucket:/other_book.txt".copy_if_not_exists("lion_witch_wardrobe.txt");
```

```surql title="Output"
'Operation for bucket `my_bucket` failed: Object at location lion_witch_wardrobe.txt already exists:
Object already exists at that location: lion_witch_wardrobe.txt'
```

<br></br>

## `file::delete`

The `file::delete` function deletes a file.

```surql title="API DEFINITION"
file::delete(string)
```

Example of a file `my_book.txt` being deleted:

```surql
f"my_bucket:/my_book.txt".delete();
```

<br></br>

## `file::exists`

The `file::exists` function checks to see if a file exists at the path and file name indicated.

```surql title="API DEFINITION"
file::exists(string) -> bool
```

Example of an `IF` else `STATEMENT` used to check if a file exists before writing content to the location:

```surql
IF f"my_bucket:/my_book.txt".exists() {
    THROW "Whoops, already there!"
} ELSE {
    f"my_bucket:/my_book.txt".put("Some content")
};
```

<br></br>

## `file::get`

The `file::get` function retrieves a file for use.

```surql title="API DEFINITION"
file::get(string) -> bytes
```

A retrieved file will display as bytes. If valid text, these can be cast into a `string`.

```surql
f"my_bucket:/my_book.txt".get();
<string>f"my_bucket:/my_book.txt".get();
```

```surql title="Output"
-------- Query --------

b"536F6D6520636F6E74656E74"

-------- Query --------

'Once there were four children whose names were Peter, Susan...'
```

<br></br>

## `file::head`

The `file::head` function returns the metadata for a file.

```surql title="API DEFINITION"
file::head() -> object
```

If a file is found, the metadata will be returned as an object with the following fields:

* `e_data` (`option<string>`): the unique identifier for the file.
* `last_modified` (`datetime`)
* `location` (`string`)
* `size` (`int`)
* `version` (`option<string>`)

An example of this function and its output:

```surql
f"my_bucket:/my_book.txt".head();
```

```surql title="Output"
{
	e_tag: '1',
	key: 'my_book.txt',
	last_modified: d'2025-03-26T06:29:18.988Z',
	size: 78,
	version: NONE
}
```

<br></br>

## `file::key`

```surql title="API DEFINITION"
file::key(file) -> string
```

The `file::key` function returns the key of a file: the part of a file pointer following the bucket name.

```surql
file::key(f"my_bucket:/file_name");

DEFINE PARAM $SOME_DATABASE_FILE VALUE f"my_bucket:/file_name";
file::key($SOME_DATABASE_FILE);
```

```surql title="Output"
'/file_name'
```

The counterpart to this function is `file::bucket`, which returns the bucket name of a file pointer.

## `file::list`

```surql title="API DEFINITION"
file::list(string, $list_options: option<object>) -> array<object>
```

The `file::list` returns the metadata for the files inside a certain bucket. The output is an array of objects, each containing the following fields:

* `file`: the pointer to the file.
* `size` (`int`): the file size in bytes.
* `updated` (`datetime`): the last time a change was made to the file.

```surql
DEFINE BUCKET my_bucket BACKEND "memory";

f"my_bucket:/some_book".put("Once upon a time...");
f"my_bucket:/some_book".rename("awesome_book");
f"my_bucket:/some_book".put("In a hole in the ground lived a Hobbit.");
file::list("my_bucket");
```

```surql title="Output"
[
	{
		file: f"my_bucket:/awesome_book",
		size: 19,
		updated: d'2025-04-08T03:28:20.530511Z'
	},
	{
		file: f"my_bucket:/some_book",
		size: 39,
		updated: d'2025-04-08T03:28:20.530704Z'
	}
]
```

To modify the output, a second argument can be passed in that contains a single object with up to three fields:

* `limit` (`int`): the maximum number of files to display.
* `start` (`string`): displays files ordered after `start`.
* `prefix` (`string`): displays files whose names begin with `prefix`.

Some examples of the function containing the second object and their responses:

```surql
file::list("my_bucket", { limit: 1 });
file::list("my_bucket", { limit: 0 });
```

```surql title="Output"
-------- Query --------
[
	{
		file: f"my_bucket:/awesome_book",
		size: 19,
		updated: d'2025-04-15T05:35:40.913221Z'
	}
]

-------- Query --------
[]
```

```surql
file::list("my_bucket", { prefix: "some" });
file::list("my_bucket", { prefix: "someBOOOEOEOK" });
```

```surql title="Output"
-------- Query --------
[
	{
		file: f"my_bucket:/some_book",
		size: 39,
		updated: d'2025-04-15T05:35:40.913554Z'
	}
]

-------- Query --------
[]
```

```surql
file::list("my_bucket", { start: "a" });
file::list("my_bucket", { start: "m" });
```

```surql title="Output"
-------- Query --------
[
	{
		file: f"my_bucket:/awesome_book",
		size: 19,
		updated: d'2025-04-15T05:55:41.973869Z'
	},
	{
		file: f"my_bucket:/some_book",
		size: 39,
		updated: d'2025-04-15T05:55:41.974370Z'
	}
]

-------- Query --------
[
	{
		file: f"my_bucket:/some_book",
		size: 39,
		updated: d'2025-04-15T05:55:41.974370Z'
	}
]
```

```surql
file::list("my_bucket", { prefix: "some", start: "a", limit: 1 });
```

```surql title="Output"
[
	{
		file: f"my_bucket:/some_book",
		size: 39,
		updated: d'2025-04-15T05:35:40.913554Z'
	}
]
```

## `file::put`

The `file::put` function adds data into a file, overwriting any existing data.

```surql title="API DEFINITION"
file::put()
```

An example of this function followed by `file::get()` to display the contents:

```surql
DEFINE BUCKET my_bucket BACKEND "memory";

f"my_bucket:/my_book.txt".put("Once there were four children whose names were Peter, Susan...");
f"my_bucket:/my_book.txt".put("Or were there? I don't quite remember.");
<string>f"my_bucket:/my_book.txt".get();
```

```surql title="Output"
"Or were there? I don't quite remember."
```

<br></br>

## `file::put_if_not_exists`

The `file::put` function adds data into a file, unless a file of the same name already exists.

```surql title="API DEFINITION"
file::put_if_not_exists()
```

An example of this function followed by `file::get()` to display the contents:

```surql
DEFINE BUCKET my_bucket BACKEND "memory";

-- Creates file and adds data
f"my_bucket:/my_book.txt".put_if_not_exists("Once there were four children whose names were Peter, Susan...");
-- Does nothing
f"my_bucket:/my_book.txt".put_if_not_exists("Or were there? I don't quite remember.");
<string>f"my_bucket:/my_book.txt".get();
```

```surql title="Output"
'Once there were four children whose names were Peter, Susan...'
```

<br></br>

## `file::rename`

The `file::rename` function renames a file, overwriting any existing file that has the same name as the target name.

```surql title="API DEFINITION"
file::rename()
```

An example of a file being renamed over an existing file:

```surql
DEFINE BUCKET my_bucket BACKEND "memory";

f"my_bucket:/my_book.txt".put("Once there were four children whose names were Peter, Susan...");
f"my_bucket:/other_book.txt".put("Or were there? I don't quite remember.");
-- Rename to my_book.txt, overwriting existing file of the same name
f"my_bucket:/other_book.txt".rename("my_book.txt");
<string>f"my_bucket:/my_book.txt".get();
```

```surql title="Output"
"Or were there? I don't quite remember."
```

<br></br>

## `file::rename_if_not_exists`

The `file::rename_if_not_exists` function renames a file, returning an error if a file already exists that has the same name as the target name.

```surql title="API DEFINITION"
file::rename_if_not_exists()
```

```surql title="Output"
-------- Query --------

'Operation for bucket `my_bucket` failed: Object at location my_book.txt already exists:
Object already exists at that location: my_book.txt'

-------- Query --------

'Once there were four children whose names were Peter, Susan...
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/geo

# Geo

These functions can be used when working with and analysing geospatial data.

These functions can be used when working with and analysing geospatial data.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#geoarea"><code>geo::area()</code></a></td>
      <td scope="row" data-label="Description">Calculates the area of a geometry</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#geobearing"><code>geo::bearing()</code></a></td>
      <td scope="row" data-label="Description">Calculates the bearing between two geolocation points</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#geocentroid"><code>geo::centroid()</code></a></td>
      <td scope="row" data-label="Description">Calculates the centroid of a geometry</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#geodistance"><code>geo::distance()</code></a></td>
      <td scope="row" data-label="Description">Calculates the distance between two geolocation points</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#geohashdecode"><code>geo::hash::decode()</code></a></td>
      <td scope="row" data-label="Description">Decodes a geohash into a geometry point</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#geohashencode"><code>geo::hash::encode()</code></a></td>
      <td scope="row" data-label="Description">Encodes a geometry point into a geohash</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#geois_valid"><code>geo::is_valid()</code></a></td>
      <td scope="row" data-label="Description">Determines if a geometry type is a geography type</td>
    </tr>
  </tbody>
</table>

## Point and geometry

* A `point` is composed of two floats that represent the longitude (east/west) and latitude (north/south) of a location.
* A `geometry` is a type of object defined in the [GeoJSON](https://en.wikipedia.org/wiki/GeoJSON) spec, of which Polygon is the most common. They can be passed in to the geo functions as objects that contain a "type" (such as "Polygon") and "coordinates" (an array of points).

## `geo::area`

The `geo::area` function calculates the area of a geometry in square metres.

```surql title="API DEFINITION"
geo::area(geometry) -> number
```

The following example shows this function, and its output for four approximate points found on a map for the US state of Wyoming which has an area of 253,340 km<sup>2</sup> and a mostly rectangular shape. Note: the doubled square brackets are because the function takes an array of an array to allow for more complex types such as MultiPolygon.

<img src="~/assets/img/image/light/geo-wyoming.png" darkSrc="~/assets/img/image/dark/geo-wyoming.png" alt="A map of Wyoming in the United States with four approximate points on each corner used to approximate its total surface area in SurrealDB's geo area function." />

```surql
geo::area({
  type: "Polygon",
  coordinates: [[
    [-111.0690, 45.0032],
    [-104.0838, 44.9893],
    [-104.0910, 40.9974],
    [-111.0672, 40.9862]
  ]]
});
```

```surql title="Output"
253317731850.3478f
```

If the argument is not a geometry type, then an error will be returned.

```surql
geo::area(12345);
```

```surql title="Output"
'Incorrect arguments for function geo::area(). Argument 1 was the wrong type. Expected `geometry` but found `12345`'
```

<br />

## `geo::bearing`

The `geo::bearing` function calculates the bearing between two geolocation points. Bearing begins at 0 degrees to indicate north, increasing clockwise into positive values and decreasing counterclockwise into negative values that converge at 180 degrees.

<img src="~/assets/img/image/light/geo-bearing.png" darkSrc="~/assets/img/image/dark/geo-bearing.png" alt="A circle showing how bearing is defined from 0 degrees to 360 degrees." />

```surql title="API DEFINITION"
geo::bearing($from: point, $to: point) -> number
```

The following example shows this function, and its output:

```surql
-- LET used here for readability
LET $paris = (2.358058597411099, 48.861109346459536);
LET $le_puy_en_velay = (3.883428431947686, 45.04383588468415);
RETURN geo::bearing($paris, $le_puy_en_velay);
RETURN geo::bearing($le_puy_en_velay, $paris);
```

```surql title="Output"
-- Slightly east of directly south
164.18154786094604f
-- Slightly west of directly north
-14.70308114652183f
```

<img src="~/assets/img/image/light/geo-paris.png" darkSrc="~/assets/img/image/dark/geo-paris.png" alt="A map showing the path from Paris, the capital of France, to a French town called Le Puy En Velay. The bearing is south southeast." />

<br />

## `geo::centroid`

The `geo::centroid` function calculates the centroid between multiple geolocation points.

```surql title="API DEFINITION"
geo::centroid(geometry) -> number
```
The following example shows this function, and its output. Note: the doubled square brackets are because the function takes an array of an array to allow for more complex types such as MultiPolygon.

```surql
geo::centroid({
  type: "Polygon",
  coordinates: [[
    [-0.03921743611083, 51.88106875736589], -- London
    [30.48112752349519, 50.68377089794912], -- Kyiv
    [23.66174524001544, 42.94500782833793], -- Sofia
    [ 1.92481534361859, 41.69698118125476] -- Barcelona
  ]]
});
```

The return value is a mountainous region somewhere in Austria:

```surql title="Output"
(13.483896437936192, 47.07117241195589)
```

<img src="~/assets/img/image/light/geo-centroid.png" darkSrc="~/assets/img/image/dark/geo-centroid.png" alt="A map showing the centroid between four points in Europe: London, Kyiv, Sofia, and Barcelona. The centroid itself is located in Austria." />

<br />

## `geo::distance`

The `geo::distance` function calculates the haversine distance, in metres, between two geolocation points.

```surql title="API DEFINITION"
geo::distance($from: point, $to: point) -> number
```

The following example shows this function, and its output:

```surql
let $london = (-0.04592553673505285, 51.555282574465764);
let $harare = (30.463880214538577, -17.865161568822085);
RETURN geo::distance($london, $harare);
```

```surql title="Output"
8268604.251890703f
```

<img src="~/assets/img/image/light/geo-london.png" darkSrc="~/assets/img/image/dark/geo-london.png" alt="A map showing the distance in a straight line from London, the capital of the United Kingdom, to Harare, the capital of Zimbabwe" />

When the points are stored in records, pass the field of each record, such as `city:london.location`. A `SELECT` returns an array of objects rather than a point, so passing its result fails:

```surql
CREATE city:london SET location = (-0.1278, 51.5074);
CREATE city:paris SET location = (2.3522, 48.8566);

geo::distance(city:london.location, city:paris.location);
//- 343556.53488088324f

geo::distance((SELECT location FROM city:london), (SELECT location FROM city:paris));
//- "Incorrect arguments for function geo::distance(). Argument 1 was the wrong type. Expected `geometry` but found `[{ location: (-0.1278, 51.5074) }]`"
```

To compute the distance from one point to every record in a table, use the field in the projection, as in `SELECT id, geo::distance(location, city:london.location) AS distance FROM city`.

<br />

## `geo::hash::decode`

The `geo::hash::decode` function converts a geohash into a geolocation point.

```surql title="API DEFINITION"
geo::hash::decode(point) -> string
```
The following example shows this function, and its output:

```surql
geo::hash::decode("mpuxk4s24f51");
```

```surql title="Output"
(51.50986494496465, -0.11809204705059528)
```

<br />

## `geo::hash::encode`

The `geo::hash::encode` function converts a geolocation point into a geohash.

```surql title="API DEFINITION"
geo::hash::encode(point) -> string
```

The function accepts a second argument, which determines the accuracy and granularity of the geohash.

```surql title="API DEFINITION"
geo::hash::encode(point, $granularity: number) -> string
```

The following example shows this function, and its output:

```surql
geo::hash::encode( (51.509865, -0.118092) );
```

```surql title="Output"
'mpuxk4s24f51'
```

The following example shows this function with two arguments, and its output, when used in a select statement:

```surql
geo::hash::encode( (51.509865, -0.118092), 5 );
```

```surql title="Output"
'mpuxk'
```

<br />

## `geo::is_valid`

The `geo::is_valid` function determines if a geometry type is a geography type. Geography types are used to store geolocation data in a [Geographic Coordinate System (GCS)](https://en.wikipedia.org/wiki/Geographic_coordinate_system), whereas geometry types can store geolocation data in any coordinate system, including GCS, mathematical planes, board game layouts, etc...

A geography type adds the following constraint: each `Point` coordinate is in the range of -180° to 180° for longitude and -90° to 90° for latitude.

```surql title="API DEFINITION"
geo::is_valid(geometry) -> bool
```

The following examples show this function, and its output:

```surql title="A valid geography point"
geo::is_valid( (51.509865, -0.118092) );

-- true
```

```surql title="Out of range geometry point"
geo::is_valid( (-181.0, -0.118092) );

-- false
```

<br /><br />

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/http

# HTTP

These functions can be used when opening and submitting remote web requests, and webhooks.

These functions can be used when opening and submitting remote web requests, and webhooks.

> [!IMPORTANT]
> All `http::*` functions require network capabilities, which are **denied by default**. Start the server with `--allow-net` (optionally scoped to specific targets); otherwise calls fail with `Access to network target '…' is not allowed` (verified on v3.2.0). See [Capabilities](/docs/learn/security/authorization/capabilities.md).

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#httphead"><code>http::head()</code></a></td>
      <td scope="row" data-label="Description">Perform a remote HTTP HEAD request</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#httpget"><code>http::get()</code></a></td>
      <td scope="row" data-label="Description">Perform a remote HTTP GET request</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#httpput"><code>http::put()</code></a></td>
      <td scope="row" data-label="Description">Perform a remote HTTP PUT request</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#httppost"><code>http::post()</code></a></td>
      <td scope="row" data-label="Description">Perform a remote HTTP POST request</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#httppatch"><code>http::patch()</code></a></td>
      <td scope="row" data-label="Description">Perform a remote HTTP PATCH request</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#httpdelete"><code>http::delete()</code></a></td>
      <td scope="row" data-label="Description">Perform a remote HTTP DELETE request</td>
    </tr>
  </tbody>
</table>

## Response encoding and errors

Failed requests return descriptive errors with the relevant HTTP status code when the remote server provides one.

Response bodies encode SurrealQL values as follows:

- **Bytes**, sent as raw bytes (not base64- or JSON-encoded).
- **Strings**, sent as raw strings.
- **Other values** (numbers, arrays, objects, booleans, and so on), JSON-encoded.

SurrealDB does not add `Content-Type: application/octet-stream` automatically when the body contains byte values. You can set this header yourself if a client requires it.

## `http::head`

The `http::head` function performs a remote HTTP `HEAD` request. The first parameter is the URL of the remote endpoint. If the response does not return a `2XX` status code, then the function will fail and return the error.

```surql title="API DEFINITION"
http::head(string) -> null
```

If an object is given as the second argument, then this can be used to set the request headers.

```surql title="API DEFINITION"
http::head(string, $headers: object) -> null
```

The following example shows this function, and its output:

```surql
http::head('https://surrealdb.com');

null
```

To specify custom headers with the HTTP request, pass an object as the second argument:

```surql
http::head('https://surrealdb.com', {
	'x-my-header': 'some unique string'
});

null
```

<br />

## `http::get`

The `http::get` function performs a remote HTTP `GET` request. The first parameter is the URL of the remote endpoint. If the response does not return a 2XX status code, then the function will fail and return the error.

If the remote endpoint returns an `application/json content-type`, then the response is parsed and returned as a value, otherwise the response is treated as text.

```surql title="API DEFINITION"
http::get(string) -> value
```
If an object is given as the second argument, then this can be used to set the request headers.

```surql title="API DEFINITION"
http::get(string, $headers: object) -> value
```

The following example shows this function, and its output:

```surql
http::get('https://surrealdb.com');

-- The HTML code is returned
```

To specify custom headers with the HTTP request, pass an object as the second argument:

```surql
http::get('https://surrealdb.com', {
	'x-my-header': 'some unique string'
});

-- The HTML code is returned
```

<br />

## `http::put`

The `http::put` function performs a remote HTTP `PUT` request. The first parameter is the URL of the remote endpoint, and the second parameter is the value to use as the request body, which will be converted to JSON. If the response does not return a `2XX` status code, then the function will fail and return the error. If the remote endpoint returns an `application/json` content-type, then the response is parsed and returned as a value, otherwise the response is treated as text.

```surql title="API DEFINITION"
http::put(string, $body: object) -> value
```

If an object is given as the third argument, then this can be used to set the request headers.

```surql title="API DEFINITION"
http::put(string, $body: object, $headers: object) -> value
```

The following example shows this function, and its output:

```surql title="Request without headers"
http::put('https://jsonplaceholder.typicode.com/posts/1', {
  id: 1,
  body: "This is some awesome thinking!",
  postId: 100,
  user: {
    id: 63,
    username: 'eburras1q'
  }
});
```

```surql title="Request with headers"
http::put('https://jsonplaceholder.typicode.com/posts/1', {
  id: 1,
  body: "This is some awesome thinking!",
  postId: 100,
  user: {
    id: 63,
    username: 'eburras1q'
  }
}, {
  'Authorization': 'Bearer your-token-here',
  'Content-Type': 'application/json',
  'x-custom-header': 'custom-value'
});
```

```surql title="Output"
{
	body: 'This is some awesome thinking!',
	id: 1,
	postId: 100,
	user: {
		id: 63,
		username: 'eburras1q'
	}
}
```

<br />

## `http::post`

The `http::post` function performs a remote HTTP `POST` request. The first parameter is the URL of the remote endpoint, and the second parameter is the value to use as the request body, which will be converted to JSON. If the response does not return a `2XX` status code, then the function will fail and return the error. If the remote endpoint returns an `application/json` content-type, then the response is parsed and returned as a value, otherwise the response is treated as text.

```surql title="API DEFINITION"
http::post(string, $body: object) -> value
```
If an object is given as the third argument, then this can be used to set the request headers.

```surql title="API DEFINITION"
http::post(string, $body: object, $headers: object) -> value
```

The following example shows this function, and its output:

```surql title="Request without headers"
http::post('https://jsonplaceholder.typicode.com/posts/', {
  id: 1,
  body: "This is some awesome thinking!",
  postId: 100,
  user: {
    id: 63,
    username: "eburras1q"
  }
});
```

```surql title="Request with headers"
http::post('https://jsonplaceholder.typicode.com/posts/', {
  id: 1,
  body: "This is some awesome thinking!",
  postId: 100,
  user: {
    id: 63,
    username: "eburras1q"
  }
}, {
  'Authorization': 'Bearer your-token-here',
  'Content-Type': 'application/json',
  'x-custom-header': 'custom-value'
});
```

```surql title="Output"
{
	body: 'This is some awesome thinking!',
	id: 101,
	postId: 100,
	user: {
		id: 63,
		username: 'eburras1q'
	}
}
```

<br />

## `http::patch`

The `http::patch` function performs a remote HTTP `PATCH` request. The first parameter is the URL of the remote endpoint, and the second parameter is the value to use as the request body, which will be converted to JSON. If the response does not return a `2XX` status code, then the function will fail and return the error. If the remote endpoint returns an `application/json` content-type, then the response is parsed and returned as a value, otherwise the response is treated as text.

```surql title="API DEFINITION"
http::patch(string, $body: object) -> value
```
If an object is given as the third argument, then this can be used to set the request headers.

```surql title="API DEFINITION"
http::patch(string, $body: object, $headers: object) -> value
```

The following example shows this function, and its output:

```surql title="Request without headers"
http::patch('https://jsonplaceholder.typicode.com/posts/1', {
  id: 1,
  body: "This is some awesome thinking!",
  postId: 100,
  user: {
    id: 63,
    username: "eburras1q"
  }
});
```

```surql title="Setting the request headers"
http::patch('https://jsonplaceholder.typicode.com/posts/1', {
  id: 1,
  body: "This is some awesome thinking!",
  postId: 100,
  user: {
    id: 63,
    username: "eburras1q"
  }
}, {
  'Authorization': 'Bearer your-token-here',
  'Content-Type': 'application/json',
  'x-custom-header': 'custom-value'
});
```

```surql title="Output"
{
	body: 'This is some awesome thinking!',
	id: 1,
	postId: 100,
	title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
	user: {
		id: 63,
		username: 'eburras1q'
	},
	userId: 1
}
```

<br />

## `http::delete`

The `http::delete` function performs a remote HTTP `DELETE` request. The first parameter is the URL of the remote endpoint, and the second parameter is the value to use as the request body, which will be converted to JSON. If the response does not return a `2XX` status code, then the function will fail and return the error. If the remote endpoint returns an `application/json` content-type, then the response is parsed and returned as a value, otherwise the response is treated as text.

```surql title="API DEFINITION"
http::delete(string) -> value
```
If an object is given as the second argument, then this can be used to set the request headers.

```surql title="API DEFINITION"
http::delete(string, $headers: object) -> value
```

The following example shows this function, and its output:

```surql
http::delete('https://jsonplaceholder.typicode.com/posts/1');

{}
```
To specify custom headers with the HTTP request, pass an object as the second argument:

```surql
http::delete('https://jsonplaceholder.typicode.com/posts/1', {
	'x-my-header': 'some unique string'
});

{}
```

<br /><br />

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/math

# Math

Built-in math functions and consts for analysing numeric data and collections.

This page contains built-in functions and constants on the `math` module for analysing numeric data and numeric collections.

## Math functions

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathabs"><code>math::abs()</code></a></td>
      <td scope="row" data-label="Description">Returns the absolute value of a number</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathacos"><code>math::acos()</code></a></td>
      <td scope="row" data-label="Description">Computes the arccosine (inverse cosine) of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathacot"><code>math::acot()</code></a></td>
      <td scope="row" data-label="Description">Computes the arccotangent (inverse cotangent) of an angle given in radians</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathasin"><code>math::asin()</code></a></td>
      <td scope="row" data-label="Description">Computes the arcsine (inverse sine) of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathatan"><code>math::atan()</code></a></td>
      <td scope="row" data-label="Description">Computes the arctangent (inverse tangent) of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathbottom"><code>math::bottom()</code></a></td>
      <td scope="row" data-label="Description">Returns the bottom X set of numbers in a set of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathceil"><code>math::ceil()</code></a></td>
      <td scope="row" data-label="Description">Rounds a number up to the next largest integer</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathclamp"><code>math::clamp()</code></a></td>
      <td scope="row" data-label="Description">Clamps a value between a specified minimum and maximum</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathcos"><code>math::cos()</code></a></td>
      <td scope="row" data-label="Description">Computes the cosine of an angle given in radians</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathcot"><code>math::cot()</code></a></td>
      <td scope="row" data-label="Description">Computes the cotangent of an angle given in radians</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathdeg2rad"><code>math::deg2rad()</code></a></td>
      <td scope="row" data-label="Description">Converts an angle from degrees to radians</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathfixed"><code>math::fixed()</code></a></td>
      <td scope="row" data-label="Description">Returns a number with the specified number of decimal places</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathfloor"><code>math::floor()</code></a></td>
      <td scope="row" data-label="Description">Rounds a number down to the nearest integer</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathinterquartile"><code>math::interquartile()</code></a></td>
      <td scope="row" data-label="Description">Returns the interquartile of an array of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathlerp"><code>math::lerp()</code></a></td>
      <td scope="row" data-label="Description">Linearly interpolates between two values based on a factor</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathlerpangle"><code>math::lerpangle()</code></a></td>
      <td scope="row" data-label="Description">Linearly interpolates between two angles in degrees</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathln"><code>math::ln()</code></a></td>
      <td scope="row" data-label="Description">Computes the natural logarithm (base e) of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathlog"><code>math::log()</code></a></td>
      <td scope="row" data-label="Description">Computes the logarithm of a value with the specified base</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathlog10"><code>math::log10()</code></a></td>
      <td scope="row" data-label="Description">Computes the base-10 logarithm of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathlog2"><code>math::log2()</code></a></td>
      <td scope="row" data-label="Description">Computes the base-2 logarithm of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathmax"><code>math::max()</code></a></td>
      <td scope="row" data-label="Description">Returns the greatest number from an array of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathmean"><code>math::mean()</code></a></td>
      <td scope="row" data-label="Description">Returns the mean of a set of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathmedian"><code>math::median()</code></a></td>
      <td scope="row" data-label="Description">Returns the median of a set of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathmidhinge"><code>math::midhinge()</code></a></td>
      <td scope="row" data-label="Description">Returns the midhinge of a set of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathmin"><code>math::min()</code></a></td>
      <td scope="row" data-label="Description">Returns the least number from an array of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathmode"><code>math::mode()</code></a></td>
      <td scope="row" data-label="Description">Returns the value that occurs most often in a set of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathnearestrank"><code>math::nearestrank()</code></a></td>
      <td scope="row" data-label="Description">Returns the nearest rank of an array of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathpercentile"><code>math::percentile()</code></a></td>
      <td scope="row" data-label="Description">Returns the value below which a percentage of data falls</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathpow"><code>math::pow()</code></a></td>
      <td scope="row" data-label="Description">Returns a number raised to a power</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathproduct"><code>math::product()</code></a></td>
      <td scope="row" data-label="Description">Returns the product of a set of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathrad2deg"><code>math::rad2deg()</code></a></td>
      <td scope="row" data-label="Description">Converts an angle from radians to degrees</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathround"><code>math::round()</code></a></td>
      <td scope="row" data-label="Description">Rounds a number up or down to the nearest integer</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathsign"><code>math::sign()</code></a></td>
      <td scope="row" data-label="Description">Returns the sign of a value (-1, 0, or 1)</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathsin"><code>math::sin()</code></a></td>
      <td scope="row" data-label="Description">Computes the sine of an angle given in radians</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathspread"><code>math::spread()</code></a></td>
      <td scope="row" data-label="Description">Returns the spread of an array of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathsqrt"><code>math::sqrt()</code></a></td>
      <td scope="row" data-label="Description">Returns the square root of a number</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathstddev"><code>math::stddev()</code></a></td>
      <td scope="row" data-label="Description">Calculates how far a set of numbers are away from the mean</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathsum"><code>math::sum()</code></a></td>
      <td scope="row" data-label="Description">Returns the total sum of a set of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathtan"><code>math::tan()</code></a></td>
      <td scope="row" data-label="Description">Computes the tangent of an angle given in radians.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathtop"><code>math::top()</code></a></td>
      <td scope="row" data-label="Description">Returns the top X set of numbers in a set of numbers</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathtrimean"><code>math::trimean()</code></a></td>
      <td scope="row" data-label="Description">The weighted average of the median and the two quartiles</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#mathvariance"><code>math::variance()</code></a></td>
      <td scope="row" data-label="Description">Calculates how far a set of numbers are spread out from the mean</td>
    </tr>
  </tbody>
</table>

## Math constants

<table>
  <thead>
    <tr>
      <th scope="col">Constant</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathe"><code>math::e</code></a></td>
      <td scope="row" data-label="Description">Constant representing the base of the natural logarithm (Euler's number)</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathfrac_1_pi"><code>math::frac_1_pi</code></a></td>
      <td scope="row" data-label="Description">Constant representing the fraction 1/π</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathfrac_1_sqrt_2"><code>math::frac_1_sqrt_2</code></a></td>
      <td scope="row" data-label="Description">Constant representing the fraction 1/sqrt(2)</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathfrac_2_pi"><code>math::frac_2_pi</code></a></td>
      <td scope="row" data-label="Description">Constant representing the fraction 2/π</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathfrac_2_sqrt_pi"><code>math::frac_2_sqrt_pi</code></a></td>
      <td scope="row" data-label="Description">Constant representing the fraction 2/sqrt(π)</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathfrac_pi_2"><code>math::frac_pi_2</code></a></td>
      <td scope="row" data-label="Description">Constant representing the fraction π/2</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathfrac_pi_3"><code>math::frac_pi_3</code></a></td>
      <td scope="row" data-label="Description">Constant representing the fraction π/3</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathfrac_pi_4"><code>math::frac_pi_4</code></a></td>
      <td scope="row" data-label="Description">Constant representing the fraction π/4</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathfrac_pi_6"><code>math::frac_pi_6</code></a></td>
      <td scope="row" data-label="Description">Constant representing the fraction π/6</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathfrac_pi_8"><code>math::frac_pi_8</code></a></td>
      <td scope="row" data-label="Description">Constant representing the fraction π/8</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathinfinity"><code>math::infinity</code></a></td>
      <td scope="row" data-label="Description">Constant representing positive infinity</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathln_10"><code>math::ln_10</code></a></td>
      <td scope="row" data-label="Description">Constant representing the natural logarithm (base e) of 10</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathln_2"><code>math::ln_2</code></a></td>
      <td scope="row" data-label="Description">Constant representing the natural logarithm (base e) of 2</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathlog10_2"><code>math::log10_2</code></a></td>
      <td scope="row" data-label="Description">Constant representing the base-10 logarithm of 2</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathlog10_e"><code>math::log10_e</code></a></td>
      <td scope="row" data-label="Description">Constant representing the base-10 logarithm of e, the base of the natural logarithm (Euler’s number)</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathlog2_10"><code>math::log2_10</code></a></td>
      <td scope="row" data-label="Description">Constant representing the base-2 logarithm of 10</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathlog2_e"><code>math::log2_e</code></a></td>
      <td scope="row" data-label="Description">Constant representing the base-2 logarithm of e, the base of the natural logarithm (Euler’s number)</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathneg_infinity"><code>math::neg_infinity</code></a></td>
      <td scope="row" data-label="Description">Constant representing negative infinity</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathpi"><code>math::pi</code></a></td>
      <td scope="row" data-label="Description">Constant representing the mathematical constant π.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathsqrt_2"><code>math::sqrt_2</code></a></td>
      <td scope="row" data-label="Description">Constant representing the square root of 2</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#mathtau"><code>math::tau</code></a></td>
      <td scope="row" data-label="Description">Represents the mathematical constant τ, which is equal to 2π</td>
    </tr>
  </tbody>
</table>

## `math::abs`

The `math::abs` function returns the absolute value of a number.

```surql title="API DEFINITION"
math::abs(number) -> number
```

The following example shows this function, and its output:

```surql
math::abs(-13.746189);
```

```surql title="Output"
13.746189f
```

<br />

## `math::acos`

The `math::acos` function returns the arccosine (inverse cosine) of a number, which must be in the range -1 to 1. The result is expressed in radians.

```surql title="API DEFINITION"
math::acos(number) -> number
```

The following example shows this function, and its output:

```surql
math::acos(0.5);
```

```surql title="Output"
1.0471975511965976f
```

<br />

## `math::acot`

The `math::acot` function returns the arccotangent (inverse cotangent) of a number. The result is expressed in radians.

```surql title="API DEFINITION"
math::acot(number) -> number
```

The following example shows this function, and its output:

```surql
math::acot(1);
```

```surql title="Output"
0.7853981633974483f
```

## `math::asin`

The `math::asin` function returns the arcsine (inverse sine) of a number, which must be in the range -1 to 1. The result is expressed in radians.

```surql title="API DEFINITION"
math::asin(number) -> number
```

The following example shows this function, and its output:

```surql
math::asin(0.5);
```

```surql title="Output"
0.5235987755982988f
```

<br />

## `math::atan`
The `math::atan` function returns the arctangent (inverse tangent) of a number. The result is expressed in radians.

```surql title="API DEFINITION"
math::atan(number) -> number
```

The following example shows this function, and its output:

```surql
math::atan(1);
```

```surql title="Output"
0.7853981633974483f
```

<br />

## `math::bottom`

The `math::bottom` function returns the bottom X set of numbers in an array of numbers.

```surql title="API DEFINITION"
math::bottom(array<number>, $quantity: number) -> number
```

The following example shows this function, and its output:

```surql
math::bottom([1, 2, 3], 2);
```

```surql title="Output"
[2, 1]
```

<br />

## `math::ceil`

The `math::ceil` function rounds a number up to the next largest whole number.

```surql title="API DEFINITION"
math::ceil(number) -> number
```

The following example shows this function, and its output:

```surql
math::ceil(13.146572);
```

```surql title="Output"
14f
```

<br />

## `math::clamp`

The `math::clamp` function constrains a number within the specified range, defined by a minimum and a maximum value. If the number is less than the minimum, it returns the minimum. If it is greater than the maximum, it returns the maximum.

```surql title="API DEFINITION"
math::clamp(number, $min: number, $max: number) -> number
```

The following example shows this function, and its output:

```surql
math::clamp(1, 5, 10);
```

```surql title="Output"
5
```

<br />

## `math::cos`

The `math::cos` function returns the cosine of a number, which is assumed to be in radians. The result is a value between -1 and 1.

```surql title="API DEFINITION"
math::cos(number) -> number
```

The following example shows this function, and its output:

```surql
math::cos(1);
```

```surql title="Output"
0.5403023058681398f
```

<br />

## `math::cot`

The `math::cot` function returns the cotangent of a number, which is assumed to be in radians. The cotangent is the reciprocal of the tangent function.

```surql title="API DEFINITION"
math::cot(number) -> number
```

The following example shows this function, and its output:

```surql
math::cot(1);
```

```surql title="Output"
0.6420926159343308f
```

<br />

## `math::deg2rad`
The `math::deg2rad` function converts an angle from degrees to radians.

```surql title="API DEFINITION"
math::deg2rad(number) -> number
```

The following example shows this function, and its output:

```surql
math::deg2rad(180);
```

```surql title="Output"
3.141592653589793f
```

<br />

## `math::e`

The `math::e` constant represents the base of the natural logarithm (Euler’s number).

```surql title="API DEFINITION"
math::e -> number
```

The following example shows this function, and its output:

```surql
math::e;
```

```surql title="Output"
2.718281828459045f
```

<br />

## `math::fixed`

The `math::fixed` function returns a number with the specified number of decimal places.

```surql title="API DEFINITION"
math::fixed(number, $places: number) -> number
```

The following example shows this function, and its output:

```surql
math::fixed(13.146572, 2);
```

```surql title="Output"
13.15f
```

<br />

## `math::floor`

The `math::floor` function rounds a number down to the nearest integer.

```surql title="API DEFINITION"
math::floor(number) -> number
```

The following example shows this function, and its output:

```surql
math::floor(13.746189);
```

```surql title="Output"
13f
```

<br />

## `math::frac_1_pi`

The `math::frac_1_pi` constant represents the fraction 1/π.

```surql title="API DEFINITION"
math::frac_1_pi -> number
```

The following example shows this function, and its output:

```surql
math::frac_1_pi;
```

```surql title="Output"
0.3183098861837907f
```

<br />

## `math::frac_1_sqrt_2`

The `math::frac_1_sqrt_2` constant represents the fraction 1/sqrt(2).

```surql title="API DEFINITION"
math::frac_1_sqrt_2 -> number
```

The following example shows this function, and its output:

```surql
math::frac_1_sqrt_2;
```

```surql title="Output"
0.7071067811865476f
```

<br />

## `math::frac_2_pi`

The `math::frac_2_pi` constant represents the fraction 2/π.

```surql title="API DEFINITION"
math::frac_2_pi -> number
```

The following example shows this function, and its output:

```surql
math::frac_2_pi;
```

```surql title="Output"
0.6366197723675814f
```

<br />

## `math::frac_2_sqrt_pi`

The `math::frac_2_sqrt_pi` constant represents the fraction 2/sqrt(π).

```surql title="API DEFINITION"
math::frac_2_sqrt_pi -> number
```

The following example shows this function, and its output:

```surql
math::frac_2_sqrt_pi;
```

```surql title="Output"
1.1283791670955126f
```

<br />

## `math::frac_pi_2`

The `math::frac_pi_2` constant represents the fraction π/2.

```surql title="API DEFINITION"
math::frac_pi_2 -> number
```

The following example shows this function, and its output:

```surql
math::frac_pi_2;
```

```surql title="Output"
1.5707963267948966f
```

<br />

## `math::frac_pi_3`

The `math::frac_pi_3` constant represents the fraction π/3.

```surql title="API DEFINITION"
math::frac_pi_3 -> number
```

The following example shows this function, and its output:

```surql
math::frac_pi_3;
```

```surql title="Output"
1.0471975511965979f
```

<br />

## `math::frac_pi_4`

The `math::frac_pi_4` constant represents the fraction π/4.

```surql title="API DEFINITION"
math::frac_pi_4 -> number
```

The following example shows this function, and its output:

```surql
math::frac_pi_4;
```

```surql title="Output"
0.7853981633974483f
```

<br />

## `math::frac_pi_6`

The `math::frac_pi_6` constant represents the fraction π/6.

```surql title="API DEFINITION"
math::frac_pi_6 -> number
```

The following example shows this function, and its output:

```surql
math::frac_pi_6;
```

```surql title="Output"
0.5235987755982989f
```

<br />

## `math::frac_pi_8`

The `math::frac_pi_8` constant represents the fraction π/8.

```surql title="API DEFINITION"
math::frac_pi_8 -> number
```

The following example shows this function, and its output:

```surql
math::frac_pi_8;
```

```surql title="Output"
0.39269908169872414f
```

<br />

## `math::infinity`

> [!NOTE]
> This constant was known as `math::inf` in versions before SurrealDB 3.0.5. The previous path also returns the same value and thus the change to `math::infinity` is not a breaking change.

The `math::infinity` constant represents positive infinity.

```surql title="API DEFINITION"
math::infinity -> number
```

The following example shows this function, and its output:

```surql
math::infinity;
```

```surql title="Output"
Infinity
```

<br />

## `math::interquartile`

The `math::interquartile` function returns the interquartile of an array of numbers.

```surql title="API DEFINITION"
math::interquartile(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::interquartile([ 1, 40, 60, 10, 2, 901 ]);
```

```surql title="Output"
51f
```

<br />

## `math::lerp`

The `math::lerp` function performs a linear interpolation between two numbers based on a given fraction. The fraction will usually be between 0 and 1, where 0 returns `$num_1` and 1 returns `$num_2`.

```surql title="API DEFINITION"
math::lerp($num_1: number, $num_2: number, $fraction: number) -> number
```

The following example shows this function, and its output:

```surql
math::lerp(0, 10, 0.5);
```

```surql title="Output"
5f
```

The function will not return an error if the third argument is not in the range of 0 to 1. Instead, it will extrapolate linearly beyond the first two numbers.

```surql
math::lerp(0, 10, 2);
```

```surql title="Output"
20
```

<br />

## `math::lerpangle`

The `math::lerpangle` function interpolates between two angles (`$num_1` and `$num_2`) by the given fraction. This is useful for smoothly transitioning between angles.

```surql title="API DEFINITION"
math::lerpangle($num_1: number, $num_2: number, $fraction: number) -> number
```

The following example shows this function, and its output:

```surql
math::lerpangle(0, 180, 0.5);
```

```surql title="Output"
90f
```

<br />

## `math::ln`
The `math::ln` function returns the natural logarithm (base e) of a number.

```surql title="API DEFINITION"
math::ln(number) -> number
```

The following example shows this function, and its output:

```surql
math::ln(10);
```

```surql title="Output"
2.302585092994046f
```

<br />

## `math::ln_10`

The `math::ln_10` constant represents the natural logarithm (base e) of 10.

```surql title="API DEFINITION"
math::ln_10 -> number
```

The following example shows this function, and its output:

```surql
math::ln_10;
```

```surql title="Output"
2.302585092994046f
```

<br />

## `math::ln_2`

The `math::ln_2` constant represents the natural logarithm (base e) of 2.

```surql title="API DEFINITION"
math::ln_2 -> number
```

The following example shows this function, and its output:

```surql
math::ln_2;
```

```surql title="Output"
0.6931471805599453f
```

<br />

## `math::log`

The `math::log` function returns the logarithm of a number with a specified base.

```surql title="API DEFINITION"
math::log(number, $base: number) -> number
```

The following example shows this function, and its output:

```surql
math::log(100, 10);
```

```surql title="Output"
2f
```

<br />

## `math::log10`

The `math::log10` function returns the base-10 logarithm of a number.

```surql title="API DEFINITION"
math::log10(number) -> number
```

The following example shows this function, and its output:

```surql
math::log10(1000);
```

```surql title="Output"
3f
```

<br />

## `math::log10_2`

The `math::log10_2` constant represents the base-10 logarithm of 2.

```surql title="API DEFINITION"
math::log10_2 -> number
```

The following example shows this function, and its output:

```surql
math::log10_2;
```

```surql title="Output"
0.3010299956639812f
```

<br />

## `math::log10_e`

The `math::log10_e` constant represents the base-10 logarithm of e, the base of the natural logarithm (Euler’s number).

```surql title="API DEFINITION"
math::log10_e -> number
```

The following example shows this function, and its output:

```surql
math::log10_e;
```

```surql title="Output"
0.4342944819032518f
```

<br />

## `math::log2`

The `math::log2` function returns the base-2 logarithm of a number.

```surql title="API DEFINITION"
math::log2(number) -> number
```

The following example shows this function, and its output:

```surql
math::log2(8);
```

```surql title="Output"
3f
```

<br />

## `math::log2_10`

The `math::log2_10` constant represents the base-2 logarithm of 10.

```surql title="API DEFINITION"
math::log2_10 -> number
```

The following example shows this function, and its output:

```surql
math::log2_10;
```

```surql title="Output"
3.321928094887362f
```

<br />

## `math::log2_e`

The `math::log2_e` constant represents the base-2 logarithm of e, the base of the natural logarithm (Euler’s number).

```surql title="API DEFINITION"
math::log2_e -> number
```

The following example shows this function, and its output:

```surql
math::log2_e;
```

```surql title="Output"
1.4426950408889634f
```

<br />

## `math::max`

The `math::max` function returns the greatest number from an array of numbers.

```surql title="API DEFINITION"
math::max(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::max([ 26.164, 13.746189, 23, 16.4, 41.42 ]);
```

```surql title="Output"
41.42f
```

See also:

* [`array::max`](/docs/reference/query-language/functions/database-functions/array.md#arraymax), which extracts the greatest value from an array of values
* [`time::max`](/docs/reference/query-language/functions/database-functions/time.md#timemax), which extracts the greatest datetime from an array of datetimes

## `math::mean`

The `math::mean` function returns the mean of a set of numbers.

```surql title="API DEFINITION"
math::mean(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::mean([ 26.164, 13.746189, 23, 16.4, 41.42 ]);
```

```surql title="Output"
24.146037800000002f
```

<br />

## `math::median`

The `math::median` function returns the median of a set of numbers.

```surql title="API DEFINITION"
math::median(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::median([ 26.164, 13.746189, 23, 16.4, 41.42 ]);
```

```surql title="Output"
23f
```

<br />

## `math::midhinge`

The `math::midhinge` function returns the midhinge of an array of numbers.

```surql title="API DEFINITION"
math::midhinge(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::midhinge([ 1, 40, 60, 10, 2, 901 ]);
```

```surql title="Output"
29.5f
```

<br />

## `math::min`

The `math::min` function returns the least number from an array of numbers.

```surql title="API DEFINITION"
math::min(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::min([ 26.164, 13.746189, 23, 16.4, 41.42 ]);
```

```surql title="Output"
13.746189f
```

See also:

* [`array::min`](/docs/reference/query-language/functions/database-functions/array.md#arraymin), which extracts the least value from an array of values
* [`time::min`](/docs/reference/query-language/functions/database-functions/time.md#timemin), which extracts the least datetime from an array of datetimes

## `math::mode`

The `math::mode` function returns the value that occurs most often in a set of numbers. In case of a tie, the highest one is returned.

```surql title="API DEFINITION"
math::mode(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::mode([ 1, 40, 60, 10, 2, 901 ]);
//- 901

math::mode([ 1, 40, 60, 10, 2, 901, 2 ]);
//- 2
```

<br />

## `math::nearestrank`

The `math::nearestrank` function returns the nearest rank of an array of numbers by pulling the closest extant record from the dataset at the %-th percentile.

```surql title="API DEFINITION"
math::nearestrank(array<number>, $percentile: number) -> number
```

The following example shows this function, and its output:

```surql
math::nearestrank([1, 40, 60, 10, 2, 901], 50);
```

```surql title="Output"
40
```

A number for the percentile outside of the range 0 to 100 will return the output `NaN`.

```surql
-- Nan
math::nearestrank([1, 40, 60, 10, 2, 901], 101);

-- Also Nan
math::nearestrank([1, 40, 60, 10, 2, 901], -1);
```

<br />

## `math::neg_infinity`

> [!NOTE]
> This constant was known as `math::neg_inf` in versions before SurrealDB 3.0.5. The previous path also returns the same value and thus the change to `math::neg_infinity` is not a breaking change.

The `math::neg_infinity` constant represents negative infinity.

```surql title="API DEFINITION"
math::neg_infinity -> number
```

The following example shows this function, and its output:

```surql
math::neg_infinity;

-- -Infinity
```

<br />

## `math::percentile`

The `math::percentile` function returns the value below which a percentage of data falls by getting the N percentile, averaging neighboring records if non-exact.

```surql title="API DEFINITION"
math::percentile(array<number>, $percentile: number) -> number
```

The following example shows this function, and its output:

```surql
math::percentile([1, 40, 60, 10, 2, 901], 50);
```

```surql title="Output"
25f
```

A number for the percentile outside of the range 0 to 100 will return the output `NaN`.

```surql
-- Nan
math::percentile([1, 40, 60, 10, 2, 901], 101);

-- Also Nan
math::percentile([1, 40, 60, 10, 2, 901], -1);
```

<br />

## `math::pi`

The `math::pi` constant represents the mathematical constant π.

```surql title="API DEFINITION"
math::pi -> number
```

The following example shows this function, and its output:

```surql
math::pi;
```

```surql title="Output"
3.141592653589793f
```

<br />

## `math::pow`

The `math::pow` function returns a number raised to the power of a second number.

```surql title="API DEFINITION"
math::pow(number, $raise_to: number) -> number
```

The following example shows this function, and its output:

```surql
math::pow(1.07, 10);
```

```surql title="Output"
1.9671513572895665f
```

<br />

## `math::product`

The `math::product` function returns the product of a set of numbers.

```surql title="API DEFINITION"
math::product(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::product([ 26.164, 13.746189, 23, 16.4, 41.42 ]);
```

```surql title="Output"
5619119.004884841f
```

<br />

## `math::rad2deg`

The `math::rad2deg` function converts an angle from radians to degrees.

```surql title="API DEFINITION"
math::rad2deg(number) -> number
```

The following example shows this function, and its output:

```surql
math::rad2deg(3.141592653589793);
```

```surql title="Output"
180f
```

<br />

## `math::round`

The `math::round` function rounds a number up or down to the nearest integer.

```surql title="API DEFINITION"
math::round(number) -> number
```

The following example shows this function, and its output:

```surql
math::round(13.53124);
```

```surql title="Output"
14f
```

<br />

## `math::sign`

The `math::sign` function returns the sign of a number, indicating whether the number is positive, negative, or zero.
It returns 1 for positive numbers, -1 for negative numbers, and 0 for zero.

```surql title="API DEFINITION"
math::sign(number) -> number
```

The following example shows this function, and its output:

```surql
math::sign(-42);
```

```surql title="Output"
-1
```

<br />

## `math::sin`

The `math::sin` function returns the sine of a number, which is assumed to be in radians.

```surql title="API DEFINITION"
math::sin(number) -> number
```

The following example shows this function, and its output:

```surql
math::sin(1);
```

```surql title="Output"
0.8414709848078965f
```

<br />

## `math::spread`

The `math::spread` function returns the spread of an array of numbers.

```surql title="API DEFINITION"
math::spread(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::spread([ 1, 40, 60, 10, 2, 901 ]);
```

```surql title="Output"
900
```

<br />

## `math::sqrt`

The `math::sqrt` function returns the square root of a number.

```surql title="API DEFINITION"
math::sqrt(number) -> number
```

The following example shows this function, and its output:

```surql
math::sqrt(15);
```

```surql title="Output"
3.872983346207417f
```

<br />

## `math::sqrt_2`

The `math::sqrt_2` constant represents the square root of 2.

```surql title="API DEFINITION"
math::sqrt_2 -> number
```

The following example shows this function, and its output:

```surql
math::sqrt_2;
```

```surql title="Output"
1.4142135623730951f
```

<br />

## `math::stddev`

The `math::stddev` function calculates how far a set of numbers are away from the mean.

```surql title="API DEFINITION"
math::stddev(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::stddev([ 1, 40, 60, 10, 2, 901 ]);
```

```surql title="Output"
359.37167389765153f
```

As of SurrealDB 3.0.0, this function can be used [inside a table view](/docs/reference/query-language/statements/select.md#mathstddev-and-mathvariance-in-table-views).

```surql
DEFINE TABLE person SCHEMALESS;
DEFINE TABLE person_stats AS
	SELECT
		count(),
		age,
		math::stddev(score) AS score_stddev
	FROM person
	GROUP BY age;
```

<br />

## `math::sum`

The `math::sum` function returns the total sum of an array of numbers.

```surql title="API DEFINITION"
math::sum(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::sum([ 26.164, 13.746189, 23, 16.4, 41.42 ]);
```

```surql title="Output"
120.730189
```

To add up a field across the records of a table, use `math::sum` as an [aggregate function](/docs/reference/query-language/clauses/group.md#aggregate-functions) with `GROUP ALL`, or pass it the values from a `SELECT VALUE`:

```surql
CREATE sale SET amount = 10;
CREATE sale SET amount = 25.5;

SELECT math::sum(amount) AS total FROM sale GROUP ALL;
//- [{ total: 35.5 }]

math::sum(SELECT VALUE amount FROM sale);
//- 35.5
```

`GROUP BY` instead of `GROUP ALL` gives a total for each group, such as `SELECT customer, math::sum(amount) AS total FROM sale GROUP BY customer`.

This function on its own expects a numeric value at each point in an array, meaning that on its own it will not be able to be used on an array that contains `NONE` or `NULL` values.

```surql
math::sum([0, NONE, 10dec, 10.7, NULL]);
```

```surql title="Output"
"Error: Incorrect arguments for function math::sum().
Argument 1 was the wrong type.
Expected `number` but found `NONE` when coercing an element of `array<number>`"
```

However, `NONE` and `NULL` can be coalesced into a default value by using the `??` operator (the "null coalescing operator").

```surql
NONE ?? 0; -- Finds NONE so returns latter value: 0
1000 ?? 0; -- Finds 1000 so returns 1000 instead of 0
```

Inside an array the [`array::map()`](/docs/reference/query-language/functions/database-functions/array.md#arraymap) function can be used to ensure that each value is the number 0 if a `NONE` or `NULL` is encountered.

Classic [array filtering](/docs/reference/query-language/language-primitives/data-types/arrays.md#mapping-and-filtering-on-arrays) can also be used to simply remove any `NONE` or `NULL` values before `math::sum()` is called.

```surql
-- Classic array filtering, removes NONE / NULL
[0,NONE,10dec,10.7,NULL][? $this];
-- array::map() function, turns NONE / NULL to 0
[0, NONE, 10dec, 10.7, NULL].map(|$num| $num ?? 0);
```

With this mapping in place, `math::sum()` will be guaranteed to work.

```surql
-- Classic array filtering
math::sum([0,NONE,10dec,10.7,NULL][? $this]);
-- array::map() function
math::sum([0, NONE, 10dec, 10.7, NULL].map(|$num| $num ?? 0));
```

```surql title="Output"
20.7dec
```

<br />

## `math::tan`

The `math::tan` function returns the tangent of a number, which is assumed to be in radians.

```surql title="API DEFINITION"
math::tan(number) -> number
```

The following example shows this function, and its output:

```surql
math::tan(1);
```

```surql title="Output"
1.557407724654902f
```

<br />

## `math::tau`

The `math::tau` constant represents the mathematical constant τ, which is equal to 2π.

```surql title="API DEFINITION"
math::tau -> number
```

The following example shows this function, and its output:

```surql
math::tau;
```

```surql title="Output"
6.283185307179586f
```

<br />

## `math::top`

The `math::top` function returns the top of an array of numbers.

```surql title="API DEFINITION"
math::top(array<number>, $quantity: number) -> number
```

The following example shows this function, and its output:

```surql
math::top([1, 40, 60, 10, 2, 901], 3);
```

```surql title="Output"
[40, 901, 60]
```

<br />

## `math::trimean`

The `math::trimean` function returns the trimean of an array of numbers.

```surql title="API DEFINITION"
math::trimean(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::trimean([ 1, 40, 60, 10, 2, 901 ]);
```

```surql title="Output"
27.25f
```

<br />

## `math::variance`

The `math::variance` function returns the variance of an array of numbers.

```surql title="API DEFINITION"
math::variance(array<number>) -> number
```

The following example shows this function, and its output:

```surql
math::variance([ 1, 40, 60, 10, 2, 901 ]);
```

```surql title="Output"
129148
```

As of SurrealDB 3.0.0, this function can be used [inside a table view](/docs/reference/query-language/statements/select.md#mathstddev-and-mathvariance-in-table-views).

```surql
DEFINE TABLE person SCHEMALESS;
DEFINE TABLE person_stats AS
	SELECT
		count(),
		age,
		math::variance(score) AS score_variance
	FROM person
	GROUP BY age;
```

<br /><br />

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/not

# Not

This function can be used to reverse the truthiness of a value.

This function can be used to reverse the truthiness of a value.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#not"><code>not()</code></a></td>
      <td scope="row" data-label="Description">Reverses the truthiness of a value.</td>
    </tr>
  </tbody>
</table>

## `not`

The `not` function reverses the truthiness of a value. It is functionally identical to `!`, the [NOT](/docs/reference/query-language/language-primitives/operators.md#not) operator.

```surql title="API DEFINITION"
not(any) -> bool
```

```surql
not("I speak the truth");
```

```surql title="Output"
false
```

A value is not [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness) if it is NONE, NULL, false, empty, or has a value of 0. As such, all the following return `true`.

```surql
[
    not(""),
    not(false),
    not([]),
    not({}),
    not(0)
];
```

Similarly, the function can be used twice to determine whether a value is truthy or not. As each item in the example below is truthy, calling `not()` twice will return the value `true` for each.

```surql
[
    not(not("I have value")),
    not(not(true)),
    not(not(["value!"])),
    not(not({i_have: "value"})),
    not(not(100))
];
```

Doubling the `!` operator is functionally identical to the above and is a more commonly seen pattern.

```surql
[
    !!"I have value",
    !!true,
    !!["value!"],
    !!{i_have: "value"},
    !!100
];
```

<br /><br />

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/object

# Object

These functions can be used when working with, and manipulating data objects.

These functions can be used when working with, and manipulating data objects.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#objectentries"><code>object::entries()</code></a></td>
      <td scope="row" data-label="Description">Transforms an object into an array with arrays of key-value combinations.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#objectextend"><code>object::extend()</code></a></td>
      <td scope="row" data-label="Description">Extends an object with the content of another one.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#objectfrom_entries"><code>object::from_entries()</code></a></td>
      <td scope="row" data-label="Description">Transforms an array with arrays of key-value combinations into an object.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#objectis_empty"><code>object::is_empty()</code></a></td>
      <td scope="row" data-label="Description">Checks if an object is empty</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#objectkeys"><code>object::keys()</code></a></td>
      <td scope="row" data-label="Description">Returns an array with all the keys of an object.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#objectlen"><code>object::len()</code></a></td>
      <td scope="row" data-label="Description">Returns the amount of key-value pairs an object holds.</td>
    </tr>
<tr>
      <td scope="row" data-label="Function"><a href="#objectremove"><code>object::remove()</code></a></td>
      <td scope="row" data-label="Description">Removes one or more fields from an object.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#objectvalues"><code>object::values()</code></a></td>
      <td scope="row" data-label="Description">Returns an array with all the values of an object.</td>
    </tr>
  </tbody>
</table>

## `object::entries`

The `object::entries` function transforms an object into an array with arrays of key-value combinations.

```surql title="API DEFINITION"
object::entries(object) -> array
```

The following example shows this function, and its output:

```surql
object::entries({
  a: 1,
  b: true
});
```

```surql title="Output"
[
  [ 'a', 1 ],
  [ 'b', true ],
]
```

<br />

## `object::extend`

_(since v3.0.0)_

The `object::extend` function extends an object with the fields and values of another one, essentially adding the two together.

```surql title="API DEFINITION"
object::extend(object, $other: object) -> object
```

An example of the function, resulting in one new field (`gold`) and one updated field (`last_updated`) in the final output.

```surql
{ name: "Mat Cauthon", last_updated: d'2013-01-08'}.extend( 
{ gold: 100, last_updated: time::now() });
```

```surql title="Output"
{
	gold: 100,
	last_updated: d'2025-05-07T06:15:00.768Z',
	name: 'Mat Cauthon'
}
```

Note: the same behaviour can also be achieved using the `+` operator.

```surql
{ name: "Mat Cauthon", last_updated: d'2013-01-08'} + 
{ gold: 100, last_updated: time::now() };
```

<br />

## `object::from_entries`

The `object::from_entries` function transforms an array with arrays of key-value combinations into an object.

```surql title="API DEFINITION"
object::from_entries(array) -> object
```

The following example shows this function, and its output:

```surql
object::from_entries([
  [ "a", 1 ],
  [ "b", true ],
]);
```

```surql title="Output"
{
  a: 1,
  b: true
}
```

## `object::is_empty`

The `object::is_empty` function checks whether the object contains values.

```surql title="API DEFINITION"
object::is_empty(object) -> bool
```

The following example shows this function, and its output:

```surql title="An object that contain values"
{
  name: "Aeon",
  age: 20
}.is_empty();

-- false
```

```surql title="An empty object"
object::is_empty({});

-- true
```

Example of `.is_empty()` being used in a [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md#asserting-rules-on-fields) statement to disallow empty objects:

```surql
DEFINE FIELD metadata
  ON house
  TYPE object
  ASSERT !$value.is_empty();
CREATE house SET metadata = {};
CREATE house SET metadata = { floors: 5 };
```

```surql title="Output"
-------- Query --------

'Found {  } for field `metadata`, with record `house:aei2fms2jccm46ceib8l`, but field must conform to: !$value.is_empty()'

-------- Query --------

[
	{
		id: house:g126ct3m0scbkockq32u,
		metadata: {
			floors: 5
		}
	}
]
```

## `object::keys`

The `object::keys` function returns an array with all the keys of an object.

```surql title="API DEFINITION"
object::keys(object) -> array
```

The following example shows this function, and its output:

```surql
object::keys({
  a: 1,
  b: true
});
```

```surql title="Output"
[ 'a', 'b' ]
```

<br />

## `object::len`

The `object::len` function returns the amount of key-value pairs an object holds.

```surql title="API DEFINITION"
object::len(object) -> number
```

The following example shows this function, and its output:

```surql
object::len({
  a: 1,
  b: true
});
```

```surql title="Output"
2
```

## `object::remove`

_(since v3.0.0)_

The `object::remove` function removes one or more fields from an object.

```surql title="API DEFINITION"
object::remove(object, $to_remove: string|array<string>) -> object
```

A single string can be used to remove a single field from an object, while an array of strings can be used to remove one or more fields at a time.

```surql
{ name: "Mat Cauthon", last_updated: d'2013-01-08', gold: 100 }.remove("gold");
{ name: "Mat Cauthon", last_updated: d'2013-01-08', gold: 100 }.remove(["gold", "last_updated"]);
```

```surql title="Output"
-------- Query 1 --------

{
	last_updated: d'2013-01-08T00:00:00Z',
	name: 'Mat Cauthon'
}

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

{
	name: 'Mat Cauthon'
}
```

## `object::values`

The `object::values` function returns an array with all the values of an object.

```surql title="API DEFINITION"
object::values(object) -> array
```

The following example shows this function, and its output:

```surql
object::values({
  a: 1,
  b: true
});
```

```surql title="Output"
[1, true]
```

<br /><br />

## Method chaining

Method chaining allows functions to be called using the `.` dot operator on a value of a certain type instead of the full path of the function followed by the value.

```surql
-- Traditional syntax
object::values({
  a: 1,
  b: true
});

-- Method chaining syntax
{
  a: 1,
  b: true
}.values();
```

```surql title="Output"
[
  1,
  true
]
```

This is particularly useful for readability when a function is called multiple times.

```surql
-- Traditional syntax
array::max(object::values(object::from_entries([["a", 1], ["b", 2]])));

-- Method chaining syntax
object::from_entries([["a", 1], ["b", 2]]).values().max();
```

```surql title="Output"
2
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/parse

# Parse

These functions can be used when parsing email addresses and URL web addresses.

These functions can be used when parsing email addresses and URL web addresses.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#parseemailhost"><code>parse::email::host()</code></a></td>
      <td scope="row" data-label="Description">Parses and returns an email host from an email address</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#parseemailuser"><code>parse::email::user()</code></a></td>
      <td scope="row" data-label="Description">Parses and returns an email username from an email address</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#parseurldomain"><code>parse::url::domain()</code></a></td>
      <td scope="row" data-label="Description">Parses and returns the domain from a URL</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#parseurlfragment"><code>parse::url::fragment()</code></a></td>
      <td scope="row" data-label="Description">Parses and returns the fragment from a URL</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#parseurlhost"><code>parse::url::host()</code></a></td>
      <td scope="row" data-label="Description">Parses and returns the hostname from a URL</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#parseurlpath"><code>parse::url::path()</code></a></td>
      <td scope="row" data-label="Description">Parses and returns the path from a URL</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#parseurlport"><code>parse::url::port()</code></a></td>
      <td scope="row" data-label="Description">Parses and returns the port number from a URL</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#parseurlscheme"><code>parse::url::scheme()</code></a></td>
      <td scope="row" data-label="Description">Parses and returns the scheme from a URL</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#parseurlquery"><code>parse::url::query()</code></a></td>
      <td scope="row" data-label="Description">Parses and returns the query string from a URL</td>
    </tr>
  </tbody>
</table>

## `parse::email::host`

The `parse::email::host` function parses and returns an email host from a valid email address.

```surql title="API DEFINITION"
parse::email::host(string) -> string
```
The following example shows this function, and its output:

```surql
parse::email::host("info@surrealdb.com");
```

```surql title="Output"
'surrealdb.com'
```

<br />

## `parse::email::user`

The `parse::email::user` function parses and returns an email username from a valid email address.

```surql title="API DEFINITION"
parse::email::user(string) -> string
```

The following example shows this function, and its output:

```surql
parse::email::user("info@surrealdb.com");
```

```surql title="Output"
"info"
```

<br />

## `parse::url::domain`

The `parse::url::domain` function parses and returns domain from a valid URL.
This function is similar to `parse::url::host` only that it will return `null` if the URL is an IP address.

```surql title="API DEFINITION"
parse::url::domain(string) -> string
```
The following example shows this function, and its output:

```surql
parse::url::domain("https://surrealdb.com:80/features?some=option#fragment");
parse::url::domain("http://127.0.0.1/index.html");
```

```surql title="Output"
"surrealdb.com"

NONE
```

<br />

## `parse::url::fragment`

The `parse::url::fragment` function parses and returns the fragment from a valid URL.

```surql title="API DEFINITION"
parse::url::fragment(string) -> string
```
The following example shows this function, and its output:

```surql
parse::url::fragment("https://surrealdb.com:80/features?some=option#fragment");
```

```surql title="Output"
'fragment'
```

<br />

## `parse::url::host`

The `parse::url::host` function parses and returns the hostname from a valid URL.

```surql title="API DEFINITION"
parse::url::host(string) -> string
```
The following example shows this function, and its output:

```surql
parse::url::host("https://surrealdb.com:80/features?some=option#fragment");
parse::url::host("http://127.0.0.1/index.html");
```

```surql title="Output"
'surrealdb.com'

'127.0.0.1'
```

<br />

## `parse::url::path`

The `parse::url::path`  function parses and returns the path from a valid URL.

```surql title="API DEFINITION"
parse::url::path(string) -> string
```
The following example shows this function, and its output:

```surql
parse::url::path("https://surrealdb.com:80/features?some=option#fragment");
```

```surql title="Output"
'/features'
```

<br />

## `parse::url::port`

The `parse::url::port` function parses and returns the port from a valid URL.

```surql title="API DEFINITION"
parse::url::port(string) -> number
```

The following example shows this function, and its output:

```surql
parse::url::port("https://surrealdb.com:80/features?some=option#fragment");
```

```surql title="Output"
80
```

<br />

## `parse::url::scheme`

The `parse::url::scheme` function parses and returns the scheme from a valid URL, in lowercase, as an ASCII string without the ':' delimiter.

```surql title="API DEFINITION"
parse::url::scheme(string) -> string
```

The following example shows this function, and its output:

```surql
parse::url::scheme("https://surrealdb.com:80/features?some=option#fragment");
```

```surql title="Output"
'https'
```

<br />

## `parse::url::query`

The `parse::url::query` function parses and returns the query from a valid URL.

```surql title="API DEFINITION"
parse::url::query(string) -> string
```
The following example shows this function, and its output:

```surql
parse::url::query("https://surrealdb.com:80/features?some=option#fragment");
```

```surql title="Output"
'some=option'
```

<br /><br />

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/rand

# Rand functions

These functions can be used when generating random data values.

These functions can be used when generating random data values.

> [!NOTE]
> For functions that take a lower and upper bound (`rand::float`, `rand::int`, `rand::duration`, `rand::time`, `rand::id`, `rand::string`), the **first argument is the minimum** and the **second is the maximum**. If the minimum is greater than the maximum, an error is returned. Length and bound arguments must be non-negative; negative values return an error.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#rand"><code>rand()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random floating point number</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randbool"><code>rand::bool()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random boolean</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randduration"><code>rand::duration()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randenum"><code>rand::enum()</code></a></td>
      <td scope="row" data-label="Description">Randomly picks a value from the specified values</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randfloat"><code>rand::float()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random floating point number</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randid"><code>rand::id()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random id</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randint"><code>rand::int()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random integer</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randstring"><code>rand::string()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random string</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randtime"><code>rand::time()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randuuid"><code>rand::uuid()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random UUID</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randuuidv4"><code>rand::uuid::v4()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random Version 4 UUID</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#randulid"><code>rand::ulid()</code></a></td>
      <td scope="row" data-label="Description">Generates and returns a random ULID</td>
    </tr>
  </tbody>
</table>

## `rand`

The rand function generates a random [`float`](/docs/reference/query-language/language-primitives/data-types/numbers.md#floating-point-numbers), between 0 and 1.

```surql title="API DEFINITION"
rand() -> number
```

The following example shows this function, and its output:

```surql
rand();

0.7062321084863658
```

The following example shows this function being used in a [`SELECT`](/docs/reference/query-language/statements/select.md) statement with an `ORDER BY` clause:

```surql
SELECT * FROM [{ age: 33 }, { age: 45 }, { age: 39 }] ORDER BY rand();

[
	{
		age: 45
	},
	{
		age: 39
	},
	{
		age: 33
	}
]
```

<br />

## `rand::bool`

The rand::bool function generates a random [`boolean`](/docs/reference/query-language/language-primitives/data-types/booleans.md) value.

```surql title="API DEFINITION"
rand::bool() -> bool
```

The following example shows this function, and its output:

```surql
rand::bool();

true
```

<br />

## `rand::duration`

_(since v2.3.0)_

The rand::duration function generates a random [`duration`](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) value between two `duration` arguments.

```surql title="API DEFINITION"
rand::duration($from: duration, $to: duration) -> duration
```

Some examples of the function in use:

```surql
rand::duration(1ns, 1ms);

rand::duration(0ns, duration::max);
```

```surql title="Output"
-------- Query 1 --------
435µs884ns

-------- Query 2 --------
405337457164y36w2d5h54m8s16ms76µs191ns
```

<br />

## `rand::enum`

The `rand::enum` function generates a random value, from a multitude of values.

```surql title="API DEFINITION"
rand::enum(value...) -> any
rand::enum(array<value>) -> any
```

The argument to this function can take either comma-separated values or an array of values.

```surql
rand::enum('one', 'two', 3, 4.15385, 'five', true);
rand::enum(['one', 'two', 3, 4.15385, 'five', true]);

"five"
```

As nested values are not combined at greater levels of depth, the following example will return either `[8, 9]` or `[10, 11]`, but never an individual number.

```surql
rand::enum([
    [8,9],
    [10,11]
]);
```

<br />

## `rand::float`

The `rand::float` function generates a random [`float`](/docs/reference/query-language/language-primitives/data-types/numbers.md#floating-point-numbers), between `0` and `1`.

```surql title="API DEFINITION"
rand::float() -> float
```

If two numbers are provided, the function generates a random [`float`](/docs/reference/query-language/language-primitives/data-types/numbers.md#floating-point-numbers) between them (inclusive of the bounds). The first argument is the **minimum** and the second is the **maximum**.

```surql title="API DEFINITION"
rand::float($from: number, $to: number) -> float
```

The following example shows this function, and its output:

```surql
rand::float();

0.7812733136200293
```

```surql
rand::float(10, 15);

11.305355983514927
```

<br />

## `rand::id`

> [!NOTE]
> This function was known as `rand::guid` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `rand::id` function generates a random alphanumeric ID, defaulting to a length of 20 characters.

```surql title="API DEFINITION"
rand::id() -> string
```

If a number is provided, then the function generates a random ID with a specific length.

```surql title="API DEFINITION"
rand::id(number) -> string
```

If a second number is provided, the function generates a random id with a length between the two numbers. The first argument is the **minimum** length and the second is the **maximum**. Both must be non-negative.

```surql title="API DEFINITION"
rand::id($min_len: int, $max_len: int) -> string
```

The following example shows this function, and its output:

```surql title="Default 20-char random id"
rand::id();

'4uqmrmtjhtjeg77et0dl'
```

```surql title="A 10-char random id"
rand::id(10);

'f3b6cjh0nt'
```

```surql title="A random id with a length between 1 and 9 chars"
rand::id(1, 9);

'894bqt4lp'
```

This function is used for default record ID keys in SurrealDB, and can be overridden to use a ULID or UUID instead by affixing `:ulid()` and `:uuid()` after the table name, respectively.

```surql
CREATE 
  person,
  person:ulid(),
  person:uuid()
-- Return only id values for nicer output
RETURN VALUE id;
```

Output:

```surql
[
	person:o9s1sl3ivckuxo0kglix,
	person:01K7JRP6KVAQGN2THR2T13X9WP,
	person:u'0199e58b-1a7b-7880-ad5b-01671678c11f'
]
```

<br />

## `rand::int`

The `rand::int` function generates a random int.

```surql title="API DEFINITION"
rand::int() -> int
```

If two numbers are provided, the function generates a random int between them (inclusive). The first argument is the **minimum** and the second is the **maximum**.

```surql title="API DEFINITION"
rand::int($from: int, $to: int) -> int
```

The following example shows this function, and its output:

```surql
rand::int();

6841551695902514727
```

```surql
rand::int(10, 15);

13
```

<br />

## `rand::string`

The `rand::string` function generates a random string, with 32 characters.

```surql title="API DEFINITION"
rand::string() -> string
```

The `rand::string` function generates a random string, with a specific length.

```surql title="API DEFINITION"
rand::string(number) -> string
```

If two numbers are provided, the function generates a random string with a length between the two numbers. The first argument is the **minimum** length and the second is the **maximum**. Both must be non-negative.

```surql title="API DEFINITION"
rand::string($from: int, $to: int) -> string
```

The following example shows this function, and its output:

```surql
rand::string();

"N8Q86mklN6U7kv0A2XCRh5UlpQMSvdoT"
```

```surql
rand::string(15);

"aSCtrfJj4pSJ7Xq"
```

```surql
rand::string(10, 15);

"rEUWFUMcx0YH"
```

<br />

## `rand::time`

The `rand::time` function generates a random [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md).

```surql title="API DEFINITION"
rand::time() -> datetime
rand::time($from: datetime|number, $to: datetime|number) -> datetime
```

The `rand::time` function generates a random [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md), either a completely random datetime when no arguments are passed in, or between two bounds. With two arguments, the first is the **earliest** bound and the second is the **latest** (each may be a unix timestamp or a `datetime`).

```surql
-- Possible output, as each call returns a different datetime
rand::time();
//- d'1327-07-12T01:00:32Z'

-- Possible output, this time somewhere between the two bounds
rand::time(198371, 1223138713);
//- d'1991-01-13T23:27:17Z'
```

This function can take two datetimes, returning a random datetime in between the least and greatest of the two.

```surql
rand::time(d'1970-01-01', d'2000-01-01');
```

```surql title="Output"
d'1999-05-29T17:02:16Z"
```

From SurrealDB 2.3, either of the arguments of this function can be either a number or a datetime.

```surql
rand::time(0, d'1990-01-01');
```

```surql title="Output"
d'1986-11-17T15:06:01Z'
```

As of this version, this function returns a datetime between 0000-01-01T00:00:00Z and 9999-12-31T23:59:59Z. Before this, the function returned a random datetime between 1970-01-01T00:00:00Z (0 seconds after the UNIX epoch) and +262142-12-31T23:59:59Z (the maximum possible value for a `datetime`).

## `rand::uuid`

The `rand::uuid` function generates a random Version 7 UUID.

```surql title="API DEFINITION"
rand::uuid() -> uuid
rand::uuid(datetime) -> uuid
```

The following example shows this function, and its output:

```surql
rand::uuid();

[u"e20b2836-e689-4643-998d-b17a16800323"]
```

The `rand::uuid` function can also generate a random UUID from a datetime.

```surql
rand::uuid(d"2021-09-07T04:27:53Z");
```

Note that a UUID has a precision of one millisecond, and thus one converted back to a datetime will truncate nanosecond precision.

```surql
LET $now = time::now();
[$now, time::from_uuid(rand::uuid($now))];

-- Output:
[
	d'2026-01-29T02:14:10.057075Z',
	d'2026-01-29T02:14:10.057Z'
]
```

The `rand::uuid` function can also be called using its alias `rand::uuid::v7`.

<br />

## `rand::uuid::v4`

The `rand::uuid::v4` function generates a random version 4 UUID.

```surql title="API DEFINITION"
rand::uuid::v4() -> uuid
```

The following example shows this function, and its output:

```surql
rand::uuid::v4();

[u"4def23a5-a847-4934-8dad-c64ccc48921b"]
```

<br />

## `rand::ulid`

The `rand::ulid` function generates a random ULID.

```surql title="API DEFINITION"
rand::ulid() -> uuid
rand::ulid(datetime) -> uuid
```

The following example shows this function, and its output:

```surql
rand::ulid();

[u"01H9QDG81Q7SB33RXB7BEZBK7G"]
```

The `rand::ulid` function can also generate a random ULID from a datetime type.

The following example shows this function, and its output:

```surql
rand::ulid(d"2021-09-07T04:27:53Z");
```

Note that a ULID has a precision of one millisecond, and thus one converted back to a datetime will truncate nanosecond precision.

```surql
LET $now = time::now();
[$now, time::from_ulid(rand::ulid($now))];

-- Output:
[
	d'2026-01-29T02:14:10.057075Z',
	d'2026-01-29T02:14:10.057Z'
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/record

# Record

These functions can be used to retrieve specific metadata from a SurrealDB Record ID.

These functions can be used to retrieve specific metadata from a SurrealDB Record ID.

> [!NOTE]
> These functions were called `meta::tb()` and `meta::id()` before 2.x. Both old names still work, so an existing query does not have to change, but `record::tb()` and `record::id()` are the names to use now.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#recordexists"><code>record::exists()</code></a></td>
      <td scope="row" data-label="Description">Checks to see if a SurrealDB Record ID exists</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#recordid"><code>record::id()</code></a></td>
      <td scope="row" data-label="Description">Extracts and returns the identifier from a SurrealDB Record ID</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#recordtb"><code>record::tb()</code></a></td>
      <td scope="row" data-label="Description">Extracts and returns the table name from a SurrealDB Record ID</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#recordis_edge"><code>record::is_edge()</code></a></td>
      <td scope="row" data-label="Description">Identifies whether the value passed in is a graph edge</td>
    </tr>
  </tbody>
</table>

## `record::exists`

The `record::exists` function checks to see if a given record exists.

```surql title="API DEFINITION"
record::exists(record) -> bool
```

A simple example showing the output of this function when a record does not exist and when it does:

```surql
RETURN record::exists(r"person:tobie");
//- false

CREATE person:tobie;
RETURN record::exists(r"person:tobie");
//- true
```

A longer example of `record::exists` using method syntax:

```surql
FOR $person IN ["Haakon_VII", "Ferdinand_I", "Manuel_II", "Wilhelm_II", "George_I", "Albert_I", "Alfonso_XIII", "George_V", "Frederick_VIII"] {
    LET $record_name = type::record("person", $person.lowercase());
    IF !$record_name.exists() {
        CREATE $record_name;
    }
}
```

## `record::id`

The `record::id` function extracts and returns the identifier from a SurrealDB Record ID.

```surql title="API DEFINITION"
record::id(record) -> value
```

The following example shows this function, and its output:

```surql
record::id(person:tobie);
```

```surql title="Output"
'tobie'
```

## `record::tb`

The `record::tb` function extracts and returns the table name from a SurrealDB Record ID.

```surql title="API DEFINITION"
record::tb(record) -> string
```
The following example shows this function, and its output:

```surql
record::tb(person:tobie);
```

```surql title="Output"
'person'
```

This function can also be called using the path `record::table`.

> [!NOTE]
> To retrieve the records that point at a record through a [reference](/docs/reference/query-language/language-primitives/record-references.md), define a computed field using the `<~` syntax rather than a function.

<br /><br />

## `record::is_edge`

_(since v3.0.0)_

The `record::is_edge` function checks to see if the value passed in is a graph edge.

```surql title="API DEFINITION"
record::is_edge(record | string) -> bool
```

```surql
RELATE person:one->likes:first_like->person:two;

-- Both return true
record::is_edge(likes:first_like);
record::is_edge("likes:first_like");
```

## Method chaining

Method chaining allows functions to be called using the `.` dot operator on a value of a certain type instead of the full path of the function followed by the value.

```surql
-- Traditional syntax
record::id(r"person:aeon");

-- Method chaining syntax
r"person:aeon".id();
```

```surql title="Output"
'aeon'
```

This is particularly useful for readability when a function is called multiple times.

```surql
-- Traditional syntax
record::table(array::max([r"person:aeon", r"person:landevin"]));

-- Method chaining syntax
[r"person:aeon", r"person:landevin"].max().table();
```

```surql title="Output"
'person'
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/search

# Search

These functions are used in conjunction with the 'matches' operator to either collect the relevance score or highlight the searched keywords within the content.

These functions are used in conjunction with the [`@@` operator (the 'matches' operator)](/docs/reference/query-language/language-primitives/operators.md#matches-a-idmatchesa) to either collect the relevance score or highlight the searched keywords within the content.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#searchanalyze"><code>search::analyze()</code></a></td>
      <td scope="row" data-label="Description">Returns the output of a defined search analyzer</td>
    </tr>
      <td scope="row" data-label="Function"><a href="#searchhighlight"><code>search::highlight()</code></a></td>
      <td scope="row" data-label="Description">Highlights the matching keywords</td>
    <tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#searchlinear"><code>search::linear()</code></a></td>
      <td scope="row" data-label="Description">_(since v3.0.0)_ Performs weighted linear search</td>
    </tr>
      <td scope="row" data-label="Function"><a href="#searchoffsets"><code>search::offsets()</code></a></td>
      <td scope="row" data-label="Description">Returns the position of the matching keywords</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#searchrrf"><code>search::rrf()</code></a></td>
      <td scope="row" data-label="Description">_(since v3.0.0)_ Performs RRF (reciprocal rank fusion) search</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#searchscore"><code>search::score()</code></a></td>
      <td scope="row" data-label="Description">Returns the relevance score</td>
    </tr>
  </tbody>
</table>

<br/>

> [!NOTE]
> Before SurrealDB version 3.0.0, the `FULLTEXT ANALYZER` clause used the syntax `SEARCH ANALYZER`.

The examples below assume the following queries:

```surql
CREATE book:1 SET title = "Rust Web Programming";
DEFINE ANALYZER book_analyzer TOKENIZERS blank, class, camel, punct FILTERS snowball(english);
DEFINE INDEX book_title ON book FIELDS title FULLTEXT ANALYZER book_analyzer BM25;
```

## `search::analyze`

The `search_analyze` function returns the output of a defined search analyzer on an input string.

```surql title="API DEFINITION"
search::analyze($analyzer: string, $input: string) -> array<string>
```

First define the analyzer using the [`DEFINE ANALYZER`](/docs/reference/query-language/statements/define/analyzer.md) statement

```surql title="Define book analyzer"
DEFINE ANALYZER book_analyzer TOKENIZERS blank, class, camel, punct FILTERS snowball(english); 
```

Next you can pass the analyzer to the `search::analyze` function. The following example shows this function, and its output:

```surql
search::analyze("book_analyzer", "A hands-on guide to developing, packaging, and deploying fully functional Rust web applications");
```

```surql title="Output"
[
	'a',
	'hand',
	'-',
	'on',
	'guid',
	'to',
	'develop',
	',',
	'packag',
	',',
	'and',
	'deploy',
	'fulli',
	'function',
	'rust',
	'web',
	'applic'
]
```

## `search::highlight`

The `search::highlight` function highlights the matching keywords for the predicate reference number.

```surql title="API DEFINITION"
search::highlight($prepend: string,
  $append: string,
  $predicate: number,
  $highlight_all: option<bool>) -> string | string[]
```

The following example shows this function, and its output:

```surql
SELECT id, search::highlight('<b>', '</b>', 1) AS title
	FROM book WHERE title @1@ 'rust web';
```

```surql title="Output"
[
	{
		id: book:1,
		title: [ '<b>Rust</b> <b>Web</b> Programming' ]
	}
]
```

The optional Boolean parameter can be set to `true` to explicitly request that the whole found term be highlighted, or set to `false` to highlight only the sequence of characters we are looking for. This must be used with an `edgengram` or `ngram` filter. The default value is true.

<br />

## `search::linear`

```surql title="API DEFINITION"
search::linear($lists: array,
  $weights: array,
  $limit: int,
  $norm: 'minmax' | 'zscore') -> array<object>
```

Notes on the arguments and output of this function:

- **Input**:
  - `lists` - array of result arrays. Each inner array must be pre‑sorted most‑relevant‑first (BM25 score descending, distance ascending already inverted, etc.).
  - `weights` - An array of numeric weights corresponding to each result(must have same length as results)
  - `limit` - Maximum number of documents to return (must be ≥ 1)
  - `norm` - Normalisation method: "minmax" for MinMax normalisation or "zscore" for Z-score normalisation
- **Processing:**
  - Computes the union of all candidate ids.
  - The function automatically extracts scores from documents using the following priority:
    1. `distance` field - converted using `1.0 / (1.0 + distance)` (lower distance = higher score)
    2. `ft_score` field - used directly (full-text search scores)
    3. `score` field - used directly (generic scores)
    4. Rank-based fallback - `1.0 / (1.0 + rank)` if no score field is found
  - Normalisation Methods:
    - **MinMax**: Scales scores to [0,1] range using `(score - min) / (max - min)`
    - **Z-score**: Standardizes scores using `(score - mean) / std_dev`
  - When merging field data from the per‑list rows, keeps the first non‑null value encountered in the order the lists were supplied, or the last one if there are several fields with the same key.
  - Sorts by `linear_score` descending and truncates to limit.
- **Output:**
  - Array of merged result objects, each containing original fields and an added `linear_score`.

```surql
-- Sample data --
CREATE test:1 SET text = "Graph databases are great.", embedding = [0.10, 0.20, 0.30];
CREATE test:2 SET text = "Relational databases store tables.", embedding = [0.05, 0.10, 0.00];
CREATE test:3 SET text = "This document mentions graphs and networks.", embedding = [0.20, 0.10, 0.25];

-- Analyzer used by the full‑text index
DEFINE ANALYZER simple TOKENIZERS class, punct FILTERS lowercase, ascii;

-- Full‑text index
DEFINE INDEX idx_text ON TABLE test FIELDS text FULLTEXT ANALYZER simple BM25;

-- Vector index (HNSW) on a 3‑dimensional embedding, using cosine distance
DEFINE INDEX idx_embedding ON TABLE test FIELDS embedding HNSW DIMENSION 3 DIST COSINE;

-- Query vector (whatever your embedding model produced for "graph databases")
LET $qvec = [0.12, 0.18, 0.27];

-- Vector search: top 2 nearest neighbours
LET $vs = SELECT id FROM test  WHERE embedding <|2,100|> $qvec;

-- Full‑text search: top 2 lexical matches
LET $ft = SELECT id, search::score(1) as score FROM test
          WHERE text @1@ 'graph' ORDER BY score DESC LIMIT 2;

-- Fuse with Linear / minmax
search::linear([$vs, $ft], [2, 1], 2, 'minmax');

-- Fuse with Linear / zscore
search::linear([$vs, $ft], [2, 1], 2, 'zscore');
```

Output of the final search::linear() queries:

```surql
-------- Query 1 --------

[
	{
		ft_score: 0.5366538763046265f,
		id: test:1,
		linear_score: 2
	},
	{
		id: test:3,
		linear_score: 0
	}
]

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

[
	{
		score: 0.5366538763046265f,
		id: test:1,
		linear_score: 1.9999999999999956f
	},
	{
		id: test:3,
		linear_score: -2.0000000000000044f
	}
]
```

## `search::offsets`

The `search::offsets` function returns the position of the matching keywords for the predicate reference number.

```surql title="API DEFINITION"
search::offsets($predicate: number, $highlight_all: option<bool>) -> object
```

The following example shows this function, and its output:

```surql
SELECT id, title, search::offsets(1) AS title_offsets
	FROM book WHERE title @1@ 'rust web';
```

```surql title="Output"
[
	{
		id: book:1,
		title: [ 'Rust Web Programming' ],
		title_offsets: {
			0: [
				{ e: 4, s: 0 },
				{ e: 8, s: 5 }
			]
		}
	}
]
```

The output returns the start `s` and end `e` positions of each matched term found within the original field.

The full-text index is capable of indexing both single strings and arrays of strings. In this example, the key `0` indicates that we're highlighting the first string within the `title` field, which contains an array of strings.

The optional boolean parameter can be set to `true` to explicitly request that the whole found term be highlighted, or set to `false` to highlight only the sequence of characters we are looking for. This must be used with an `edgengram` or `ngram` filter.

The default value is true.

<br />

## `search::rrf`

```surql title="API DEFINITION"
search::rrf($lists: array, $limit: int, $k: option<int>) -> array<object>
```

Notes on the arguments and output of this function:

- **Input**:
  - lists: array of result arrays. Each inner array must be pre‑sorted most‑relevant‑first (BM25 score descending, distance ascending already inverted, etc.).
  - limit: maximum number of fused results to return.
  - k (optional): RRF constant; defaults to 60.

See [this paper](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) for why 60 tends to be the default `k` value:

> Our intuition in choosing this formula derived from fact that while highly-ranked documents are more important, the importance of lower-ranked documents does not vanish as it would were, say, an exponential function used. The constant `k` mitigates the impact of high rankings by outlier systems.

- **Processing:**
  - Computes the union of all candidate ids.
  - For each candidate, derives its rank in each list and computes `rff_score = Σ 1/(k + rank)`.
  - When merging field data from the per‑list rows, keeps the first non‑null value encountered in the order the lists were supplied, or the last one if there are several fields with the same key.
  - Sorts by `rff_score` descending and truncates to limit.
- **Output:**
  - Array of merged result objects, each containing original fields and an added `rrf_score`.

```surql
-- Sample data --
CREATE test:1 SET text = "Graph databases are great.", embedding = [0.10, 0.20, 0.30];
CREATE test:2 SET text = "Relational databases store tables.", embedding = [0.05, 0.10, 0.00];
CREATE test:3 SET text = "This document mentions graphs.", embedding = [0.20, 0.10, 0.25];

-- Analyzer used by the full‑text index
DEFINE ANALYZER simple TOKENIZERS class, punct FILTERS lowercase, ascii;

-- Full‑text index
DEFINE INDEX idx_text ON TABLE test FIELDS text FULLTEXT ANALYZER simple BM25;

-- Vector index (HNSW) on a 3‑dimensional embedding, using cosine distance
DEFINE INDEX idx_embedding ON TABLE test FIELDS embedding HNSW DIMENSION 3 DIST COSINE;

-- Query vector (whatever your embedding model produced for "graph databases")
LET $qvec = [0.12, 0.18, 0.27];

-- Vector search: top 2 nearest neighbours
LET $vs = SELECT id FROM test  WHERE embedding <|2,100|> $qvec;

-- Full‑text search: top 2 lexical matches
LET $ft = SELECT id, search::score(1) as score FROM test
          WHERE text @1@ 'graph' ORDER BY score DESC LIMIT 2;

-- Fuse with Reciprocal Rank Fusion (k defaults to 60 if omitted)
search::rrf([$vs, $ft], 2, 60);
```

Output of the final search::rrf() query:

```surql
[
	{
		score: 0.5366538763046265f,
		id: test:1,
		rrf_score: 0.03278688524590164f
	},
	{
		id: test:3,
		rrf_score: 0.016129032258064516f
	}
];
```

## `search::score`

The `search::score` function returns the relevance score corresponding to the given 'matches' predicate reference numbers.

```surql title="API DEFINITION"
search::score(number) -> number
```

The following example shows this function, and its output:

```surql
SELECT id, title, search::score(1) AS score FROM book
	WHERE title @1@ 'rust web'
	ORDER BY score DESC;
```

```surql title="Output"
[
	{
		id: book:1,
		score: 0.9227996468544006,
		title: [ 'Rust Web Programming' ],
	}
]
```

> [!NOTE]
> This function returns `0` for a term that appears in half or more of the indexed documents, because BM25 clamps the inverse document frequency of such a term to zero. Small datasets meet that condition easily, so every score can come back as `0` while matching itself still works. See [why a score can be 0](/docs/learn/data-models/full-text-search/scoring-and-ranking.md#why-a-score-can-be-0).

## See also

- [Representations and codecs](/docs/learn/querying/concepts-and-guides/representations-and-codecs.md) - `search::analyze` as an offline preview of analyzer tokenization
- [`DEFINE ANALYZER`](/docs/reference/query-language/statements/define/analyzer.md)

<br />

<br /><br />

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/sequence

# Sequence

Functions to work with sequences.

_(since v3.0.0)_

These functions can be used to work with [sequences](/docs/reference/query-language/statements/define/sequence.md).

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#sequencenextval"><code>sequence::nextval()</code></a></td>
      <td scope="row" data-label="Description">Returns the next value in a sequence.</td>
    </tr>
  </tbody>
</table>

## `sequence::nextval`

The `sequence::nextval` function returns the next value in a sequence.

```surql title="API DEFINITION"
sequence::nextval($seq_name: string) -> int
```

```surql 
DEFINE SEQUENCE mySeq2 BATCH 1000 START 100 TIMEOUT 5s;
sequence::nextval('mySeq2');
```

```surql title="Output"
100
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/session

# Session

These functions return information about the current SurrealDB session.

These functions return information about the current SurrealDB session.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#sessionac"><code>session::ac()</code></a></td>
      <td scope="row" data-label="Description">Returns the current user's access method</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#sessiondb"><code>session::db()</code></a></td>
      <td scope="row" data-label="Description">Returns the currently selected database</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#sessionid"><code>session::id()</code></a></td>
      <td scope="row" data-label="Description">Returns the current user's session ID</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#sessionip"><code>session::ip()</code></a></td>
      <td scope="row" data-label="Description">Returns the current user's session IP address</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#sessionns"><code>session::ns()</code></a></td>
      <td scope="row" data-label="Description">Returns the currently selected namespace</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#sessionorigin"><code>session::origin()</code></a></td>
      <td scope="row" data-label="Description">Returns the current user's HTTP origin</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#sessionrd"><code>session::rd()</code></a></td>
      <td scope="row" data-label="Description">Returns the current user's record authentication data</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#sessiontoken"><code>session::token()</code></a></td>
      <td scope="row" data-label="Description">Returns the current user's authentication token</td>
    </tr>
  </tbody>
</table>

## `session::ac`

> [!NOTE]
> This function was known as `session::sc` in versions of SurrealDB before 2.0. The behaviour has not changed.

The `session::ac` function returns the current user's access method.

```surql title="API DEFINITION"
session::ac() -> string
```

The following example shows this function, and its output:

```surql
session::ac();

"user"
```

<br /><br />

## `session::db`

The `session::db` function returns the currently selected database.

```surql title="API DEFINITION"
session::db() -> string
```
The following example shows this function, and its output:

```surql
session::db();

"my_db"
```

<br />

## `session::id`

The `session::id` function returns the current user's session ID.

```surql title="API DEFINITION"
session::id() -> string
```
The following example shows this function, and its output:

```surql
session::id();

"I895rKuixHwCNIduyBIYH2M0Pga7oUmWnng5exEE4a7EB942GVElGrnRhE5scF5d"
```

<br />

## `session::ip`

The `session::ip` function returns the current user's session IP address.

```surql title="API DEFINITION"
session::ip() -> string
```
The following example shows this function, and its output:

```surql
session::ip();

"2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF"
```

<br />

## `session::ns`

The `session::ns` function returns the currently selected namespace.

```surql title="API DEFINITION"
session::ns() -> string
```
The following example shows this function, and its output:

```surql
session::ns();

"my_ns"
```

<br />

## `session::origin`

The `session::origin` function returns the current user's HTTP origin.

```surql title="API DEFINITION"
session::origin() -> string
```
The following example shows this function, and its output:

```surql
session::origin();

"http://localhost:3000"
```

<br />

## `session::rd`

The `session::rd` function returns the current user's record authentication.

```surql title="API DEFINITION"
session::rd() -> string
```

## `session::token`

The `session::token` function returns the current authentication token.

```surql title="API DEFINITION"
session::token() -> string
```

<br />

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/set

# Set

These functions can be used when working with, and manipulating sets of data.

_(since v3.0.0)_

These functions can be used when working with, and manipulating sets of data.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#setadd"><code>set::add()</code></a></td>
      <td scope="row" data-label="Description">Adds an item to a set if it does not exist</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setall"><code>set::all()</code></a></td>
      <td scope="row" data-label="Description">Checks if all elements in a set match a condition</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setany"><code>set::any()</code></a></td>
      <td scope="row" data-label="Description">Checks if any elements in a set match a condition</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setat"><code>set::at()</code></a></td>
      <td scope="row" data-label="Description">Accesses the element at a specific position in a set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setcomplement"><code>set::complement()</code></a></td>
      <td scope="row" data-label="Description">Returns the complement of two sets</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setcontains"><code>set::contains()</code></a></td>
      <td scope="row" data-label="Description">Checks to see if a value is present in a set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setdifference"><code>set::difference()</code></a></td>
      <td scope="row" data-label="Description">Returns the difference between two sets</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setfilter"><code>set::filter()</code></a></td>
      <td scope="row" data-label="Description">Filters elements in a set that match a condition</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setfind"><code>set::find()</code></a></td>
      <td scope="row" data-label="Description">Finds the first element in a set that matches a condition</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setfirst"><code>set::find()</code></a></td>
      <td scope="row" data-label="Description">Gets the first element in a set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setflatten"><code>set::flatten()</code></a></td>
      <td scope="row" data-label="Description">Flattens nested sets and arrays into a single set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setfold"><code>set::fold()</code></a></td>
      <td scope="row" data-label="Description">Folds over a set with an accumulator</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setintersect"><code>set::intersect()</code></a></td>
      <td scope="row" data-label="Description">Returns the values which intersect two sets</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setis_empty"><code>set::is_empty()</code></a></td>
      <td scope="row" data-label="Description">Checks if a set is empty</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setjoin"><code>set::join()</code></a></td>
      <td scope="row" data-label="Description">Joins set elements into a string with a separator</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setlast"><code>set::last()</code></a></td>
      <td scope="row" data-label="Description">Gets the last element in a set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setlen"><code>set::len()</code></a></td>
      <td scope="row" data-label="Description">Returns the length of a set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setmap"><code>set::map()</code></a></td>
      <td scope="row" data-label="Description">Maps over the elements of a set to return a new set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setmax"><code>set::max()</code></a></td>
      <td scope="row" data-label="Description">Returns the greatest value from a set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setmin"><code>set::min()</code></a></td>
      <td scope="row" data-label="Description">Returns the least value from a set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setreduce"><code>set::reduce()</code></a></td>
      <td scope="row" data-label="Description">Reduces a set via a closure</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setremove"><code>set::remove()</code></a></td>
      <td scope="row" data-label="Description">Removes an item at a specific position from a set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setslice"><code>set::slice()</code></a></td>
      <td scope="row" data-label="Description">Removes an item at a specific position from a set</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#setunion"><code>set::union()</code></a></td>
      <td scope="row" data-label="Description">Returns the unique merged values from two sets</td>
    </tr>
  </tbody>
</table>

## `set::add`

The `set::add` function adds an item to a set only if it does not already exist.

```surql title="API DEFINITION"
set::add(set, $new_val: value) -> set
```

The following example shows this function, and its output:

```surql
set::add({"one", "two"}, "three");
```

```surql title="Output"
{'one', 'three', 'two'}
```

A set can also be extended with the contents of an array or another set.

```surql
{1, 2}.add([2, 3, 4]);
//- {1, 2, 3, 4}

{1, 2, 3}.add({3, 4, 5});
//- {1, 2, 3, 4, 5}
```

## `set::all`

When called on a set without any extra arguments, the `set::all` function checks whether all set values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

```surql title="API DEFINITION"
set::all(set) -> bool
set::all(set, $predicate: value) -> bool
set::all(set, $predicate: closure) -> bool
```

The following example shows this function, and its output:

```surql
set::all({ 1, 2, 3, NONE, 'SurrealDB', 5 });
//- false
{'all', 'clear'}.all();
//- true
```

The `set::all` function can also be followed with a value or a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) to check whether all elements conform to a condition.

```surql
{'same',}.all('same');
//- true

{"What's", 'it', 'got', 'in', 'its', 'pocketses??'}.all(|$s| $s.len() > 1);
//- true

{1, 2, 'SurrealDB'}.all(|$var| $var.is_string());
//- false
```

## `set::any`

The `set::any` function checks whether any set values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

```surql title="API DEFINITION"
set::any(set) -> bool
set::any(set, $predicate: value) -> bool
set::any(set, $predicate: closure) -> bool
```

When called on a set without any extra arguments, the `set::any` function checks whether any set values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

```surql
set::any({ 1, 2, 3, NONE, 'SurrealDB', 5 });
//- true

{'', 0, NONE, NULL, [], {}}.any();
//- false
```

The `set::any` function can also be followed with a value or a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) to check whether any elements conform to a condition.

```surql
{'same', 'different'}.any('same');
//- true

{'ant', 'bear', 'cat'}.any(|$s| $s.len() > 3);
//- true

{1, 2, 3}.any(|$num| $num > 10);
//- false
```

## `set::at`

The `set::at` function returns the value at the specified index, or in reverse for a negative index.

Because sets are ordered, the position of the item is based on the set's sorted order.

```surql title="API DEFINITION"
set::at(set, $index: int) -> any
```

The following example shows this function, and its output:

```surql
set::at({3, 1, 2}, 1);
```

```surql title="Output"
2
```

You can also pass in a negative index.

```surql
{1, 2, 3}.at(-1);
//- 3

{1, 2, 3}.at(-4);
//- NONE
```

## `set::complement`

The `set::complement` function returns the complement of two sets, namely a single set containing items that are in the first set but not in the second set.

```surql title="API DEFINITION"
set::complement(set, $other: set) -> set
```

```surql title="Example"
{1, 2, 3, 4}.complement({3, 4, 5, 6});
```

```surql title="Output"
{1, 2}
```

## `set::contains`

The `set::contains` function checks to see if a value is contained within a set.

```surql title="API DEFINITION"
set::contains(set, $other: value) -> bool
```

```surql title="Example"
{1, 2, 3}.contains(3);
```

```surql title="Output"
true
```

## `set::difference`

The `set::difference` function determines the symmetric difference between two sets, returning a single set containing items that are not shared between them.

```surql title="API DEFINITION"
set::difference(set, $other: set) -> set
```

```surql title="Example"
{1, 2, 3, 4}.difference({3, 4, 5, 6});
```

```surql title="Output"
{1, 2, 5, 6}
```

## `set::filter`

The `set::filter` function filters out values that do not match a pattern.

```surql title="API DEFINITION"
set::filter(set, $predicate: value) -> set
set::filter(set, $predicate: closure) -> set
```

The following example shows a simple use of `set::filter()` with a closure.

```surql
{1, 2, 3, NONE, 0, '', [], {}}.filter(|$v| $v.is_int());
```

```surql title="Output"
{0, 1, 2, 3}
```

You can also pass a value to keep only exact matches.

```surql
{'a', 'b', 'c'}.filter('a');
```

```surql title="Output"
{'a',}
```

## `set::find`

The `set::find` function returns the first matching value from a set.

Because sets are ordered, the first matching value is the first match in the set's sorted order.

```surql title="API DEFINITION"
set::find(set, $predicate: value) -> value | NONE
set::find(set, $predicate: closure) -> value | NONE
```

The following example shows this function, and its output:

```surql
set::find({'a', 'b', 'c'}, 'b');
//- 'b'

{1, 2, 3}.find(4);
//- NONE
```

The `set::find` function is most useful when a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) is passed in, which allows for customised searching.

```surql
{1, 2, 5}.find(|$num| $num >= 3);
//- 5

{
	{ strength: 15, intelligence: 6, name: 'Dom the Magnificent' },
	{ strength: 10, intelligence: 15, name: 'Mardine' },
	{ strength: 20, intelligence: 3, name: 'Gub gub' },
	{ strength: 10, intelligence: 18, name: 'Lumin695' }
}.find(|$c| $c.strength > 9 AND $c.intelligence > 9);
//- { intelligence: 15, name: 'Mardine', strength: 10 }
```

## `set::first`

The `set::first` function returns the first value from a set.

Because sets are ordered, this returns the least value in the set's sorted order.

```surql title="API DEFINITION"
set::first(set) -> any
```

The following example shows this function, and its output:

```surql
set::first({ 3, 1, 2 });
```

```surql title="Output"
1
```

## `set::flatten`

The `set::flatten` function flattens nested sets and arrays into a single set.

```surql title="API DEFINITION"
set::flatten(set) -> set
```

Note that this function will remove a single layer of nesting, and may need to be called more than once if you have a set with another set or array inside it.

```surql
-- Flattens everything except the array contained inside an inner set
({ {1, 2}, [3, 4], 'SurrealDB', {5, 6, [7, 8]} }).flatten();

-- Call twice: flattens everything including the nested array
({ {1, 2}, [3, 4], 'SurrealDB', {5, 6, [7, 8]} }).flatten().flatten();
```

```surql title="Output"
-- Flattened once
{1, 2, 3, 4, 5, 6, 'SurrealDB', [
	7,
	8
]}

-- Flattened twice
{1, 2, 3, 4, 5, 6, 7, 8, 'SurrealDB'}
```

## `set::fold`

The `set::fold` function applies an operation on an initial value and every element in the set, returning the final result.

```surql title="API DEFINITION"
set::fold(set, $initial: value, $operator: closure) -> value
```

This function is commonly used to sum or otherwise accumulate values from a set.

```surql
{1, 2, 3, 4}.fold(0, |$acc, $val| $acc + $val);
```

```surql title="Output"
10
```

Because `set::fold()` takes an explicit initial value, it is useful when the result type should differ from the type of the set values.

```surql
{1, 2, 3}.fold('', |$acc, $val| $acc + <string>$val);
```

```surql title="Output"
'123'
```

## `set::intersect`

The `set::intersect` function calculates the values which intersect two sets, returning a single set containing the values which are in both sets.

```surql title="API DEFINITION"
set::intersect(set, $other: set) -> set
```

```surql title="Example"
{1, 2, 3, 4}.intersect({3, 4, 5, 6});
```

```surql title="Output"
{3, 4}
```

## `set::is_empty`

The `set::is_empty` function checks whether the set is empty or not.

```surql title="API DEFINITION"
set::is_empty(set) -> bool
```

```surql title="Example"
{1, 2, 3, 4}.is_empty();
```

```surql title="Output"
false
```

## `set::join`

The `set::join` function joins all values in a set together into a string, with a string separator in between each value.

Because sets are ordered, the joined string follows the set's sorted order.

```surql title="API DEFINITION"
set::join(set, $separator: string) -> string
```

```surql
set::join({3, 1, 2}, ' + ');
```

```surql title="Output"
'1 + 2 + 3'
```

## `set::last`

The `set::last` function returns the last value from a set.

Because sets are ordered, this returns the greatest value in the set's sorted order.

```surql title="API DEFINITION"
set::last(set) -> any
```

The following example shows this function, and its output:

```surql
set::last({ 3, 1, 2 });
```

```surql title="Output"
3
```

## `set::len`

The `set::len` function calculates the length of a set, returning a number. This function counts unique items only.

If you want to only count [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness) values, then use the [count()](/docs/reference/query-language/functions/database-functions/count.md) function.

```surql title="API DEFINITION"
set::len(set) -> number
```

```surql title="Example"
{1, 2, 1, null, 'something', 3, 3, 4, 0}.len();
```

```surql title="Output"
7
```

## `set::map`

The `set::map` function allows the user to call an [anonymous function](/docs/reference/query-language/language-primitives/data-types/closures.md) (closure) that is performed on every item in the set before passing it on.

Because the result is also a set, duplicate mapped values are removed.

```surql title="API DEFINITION"
set::map(set, $operator: closure) -> set
```

The most basic use of `set::map` involves choosing a parameter name for each item in the set and a desired output.

```surql
{1, 2, 3}.map(|$v| $v * 2);
```

```surql title="Output"
{2, 4, 6}
```

An example of mapping several values to the same result.

```surql
{1, 2, 3}.map(|$val| $val % 2 = 0);
```

```surql title="Output"
{false, true}
```

## `set::max`

The `set::max` function returns the greatest value from a set of values.

```surql title="API DEFINITION"
set::max(set) -> any
```

The following example shows this function, and its output:

```surql
set::max({0, 1, 2});
```

```surql title="Output"
2
```

As any value can be compared with another value, the set can contain any SurrealQL value.

## `set::min`

The `set::min` function returns the least value from a set of values.

```surql title="API DEFINITION"
set::min(set) -> any
```

The following example shows this function, and its output:

```surql
set::min({0, 1, 2});
```

```surql title="Output"
0
```

As any value can be compared with another value, the set can contain any SurrealQL value.

## `set::reduce`

The `set::reduce` function applies an operation on every element in the set, returning the final result.

If you need an initial value to pass in before the other items are operated on, use the [`set::fold`](#setfold) function instead.

```surql title="API DEFINITION"
set::reduce(set, $operator: closure) -> value
```

This function is commonly used to sum or perform some other mathematical operation on the items in a set.

```surql
{1, 2, 3, 4}.reduce(|$one, $two| $one + $two);
```

```surql title="Output"
10
```

Another example showing `set::reduce()` used to build a string:

```surql
{1, 2, 3, 4}.reduce(|$one, $two| <string>$one + <string>$two);
```

```surql title="Output"
'1234'
```

## `set::remove`

The `set::remove` function removes a value from a set.

```surql title="API DEFINITION"
set::remove(set, $remove: value) -> set
```

The following example removes the value `2` from a set.

```surql
{1, 2, 5}.remove(2);
```

```surql title="Output"
{1, 5}
```

If the value does not exist, the untouched set will be returned.

```surql
{1, 2, 5}.remove(3);
```

```surql title="Output"
{1, 2, 5}
```

You can also remove the contents of an array or another set.

```surql
{1, 2, 3, 4}.remove([2, 3]);
//- {1, 4}

{1, 2, 3, 4}.remove({2, 3, 4});
//- {1,}
```

## `set::slice`

The `set::slice` function returns a slice of a set by position.

Because sets are ordered, slicing is based on the set's sorted order.

```surql title="API DEFINITION"
set::slice(set) -> set
set::slice(set, $start: int) -> set
set::slice(set, $start: int, $end: int) -> set
set::slice(set, $range: range<int>) -> set
```

The following example shows this function, and its output:

```surql
set::slice({4, 1, 3, 2}, 1, 3);
//- {2, 3}

{1, 2, 3, 4, 5}.slice(-3..);
//- {3, 4, 5}
```

## `set::union`

The `set::union` function combines two sets together, removing duplicate values, and returning a single set.

```surql title="API DEFINITION"
set::union(set, $other: set) -> set
```

```surql title="Example"
{1, 2, 6}.union({1, 3, 4, 5, 6});
```

```surql title="Output"
{1, 2, 3, 4, 5, 6}
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/sleep

# Sleep

This function can be used to introduce a delay or pause in the execution of a query or a batch of queries for a specific amount of time.

This function can be used to introduce a delay or pause in the execution of a query or a batch of queries for a specific amount of time.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#sleep"><code>sleep()</code></a></td>
      <td scope="row" data-label="Description">Delays or pauses in the execution of a query or a batch of queries.</td>
    </tr>
  </tbody>
</table>

## `sleep`

The `sleep` function delays or pauses the execution of a query or a batch of statements.

```surql title="API DEFINITION"
sleep(duration) -> none
```
The following example shows this function, and its output:

```surql
sleep(1s);
sleep(500ms);
```

SurrealDB also has a [SLEEP statement](/docs/reference/query-language/statements/sleep.md) that accepts a datetime; however, the `sleep` function can be used in more dynamic ways such as the following example that simulates a 100ms delay between each record in a query.

```surql
-- Create 3 `person` records
CREATE |person:3|;

LET $now = time::now();

SELECT *, 
  sleep(100ms) AS _, 
  time::now() - $now AS elapsed
FROM person;
```

```surql title="Output"
[
	{
		_: NONE,
		elapsed: 101ms457µs,
		id: person:fkgvriz1kl2tcgv6yqfq
	},
	{
		_: NONE,
		elapsed: 203ms599µs,
		id: person:lgibwdgtvx4v8ck60guk
	},
	{
		_: NONE,
		elapsed: 305ms728µs,
		id: person:pr0uby896y1az2p44wtw
	}
]
```

## SLEEP during parallel operations

The `sleep()` function does not interfere with operations that are underway in the background, such as a [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md) statement using the `CONCURRENTLY` clause.

```surql
CREATE |user:1..=50000| RETURN NONE;
UPDATE user SET name = id.id() RETURN NONE;
DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE CONCURRENTLY;
INFO FOR INDEX unique_name ON TABLE user;
RETURN sleep(50ms);
INFO FOR INDEX unique_name ON TABLE user;
RETURN sleep(50ms);
INFO FOR INDEX unique_name ON TABLE user;
RETURN sleep(50ms);
INFO FOR INDEX unique_name ON TABLE user;
```

```surql title="Possible output"
-------- Query 1 --------
{
    building: {
        compacting: false,
        status: 'started'
    }
}

-------- Query 2 --------
{
    building: {
        compacting: false,
        initial: 9016,
        pending: 0,
        status: 'indexing'
    }
}

-------- Query 3 --------
{
    building: {
        compacting: false,
        initial: 17266,
        pending: 0,
        status: 'indexing'
    }
}

-------- Query 4 --------
{
    building: {
        compacting: false,
        initial: 25766,
        pending: 0,
        status: 'indexing'
    }
}
```

## Use cases

Putting a database to sleep can be useful in a small number of situations, such as:

* Testing and debugging: can be used to understand how concurrent transactions interact, test how systems handle timeouts and delays, simulate behaviour in more distant regions with longer latency
* Throttling: can be used to throttle the execution of operations to prevent the database from being overwhelmed by too many requests at once
* Security measures: can be used to slow down the response rate of login attempts to mitigate the risk of brute force attacks

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/string

# String

These functions can be used when working with and manipulating text and string values.

> [!NOTE]
> Since version 3.0.0-beta, the `::is::` functions (e.g. `string::is::domain()`) now use underscores (e.g. `string::is_domain()`) to better match the intent of the function and method syntax.

These functions can be used when working with and manipulating text and string values.

<table>
  <thead>
    <tr>
      <th >Function</th>
      <th >Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="#stringcapitalize"><code>string::capitalize()</code></a></td>
      <td>Capitalizes each word of a string</td>
    </tr>
    <tr>
      <td><a href="#stringconcat"><code>string::concat()</code></a></td>
      <td>Concatenates strings together</td>
    </tr>
    <tr>
      <td><a href="#stringcontains"><code>string::contains()</code></a></td>
      <td>Checks whether a string contains another string</td>
    </tr>
    <tr>
      <td><a href="#stringends_with"><code>string::ends_with()</code></a></td>
      <td>Checks whether a string ends with another string</td>
    </tr>
    <tr>
      <td><a href="#stringjoin"><code>string::join()</code></a></td>
      <td>Joins strings together with a delimiter</td>
    </tr>
    <tr>
      <td><a href="#stringlen"><code>string::len()</code></a></td>
      <td>Returns the length of a string</td>
    </tr>
    <tr>
      <td><a href="#stringlowercase"><code>string::lowercase()</code></a></td>
      <td>Converts a string to lowercase</td>
    </tr>
    <tr>
      <td><a href="#stringmatches"><code>string::matches()</code></a></td>
      <td>Performs a regex match on a string</td>
    </tr>
    <tr>
      <td><a href="#stringrepeat"><code>string::repeat()</code></a></td>
      <td>Repeats a string a number of times</td>
    </tr>
    <tr>
      <td><a href="#stringreplace"><code>string::replace()</code></a></td>
      <td>Replaces an occurrence of a string with another string</td>
    </tr>
    <tr>
      <td><a href="#stringreverse"><code>string::reverse()</code></a></td>
      <td>Reverses a string</td>
    </tr>
    <tr>
      <td><a href="#stringslice"><code>string::slice()</code></a></td>
      <td>Extracts and returns a section of a string</td>
    </tr>
    <tr>
      <td><a href="#stringslug"><code>string::slug()</code></a></td>
      <td>Converts a string into human and URL-friendly string</td>
    </tr>
    <tr>
      <td><a href="#stringsplit"><code>string::split()</code></a></td>
      <td>Divides a string into an ordered list of substrings</td>
    </tr>
    <tr>
      <td><a href="#stringstarts_with"><code>string::starts_with()</code></a></td>
      <td>Checks whether a string starts with another string</td>
    </tr>
    <tr>
      <td><a href="#stringtrim"><code>string::trim()</code></a></td>
      <td>Removes whitespace from the start and end of a string</td>
    </tr>
    <tr>
      <td><a href="#stringuppercase"><code>string::uppercase()</code></a></td>
      <td>Converts a string to uppercase</td>
    </tr>
    <tr>
      <td><a href="#stringwords"><code>string::words()</code></a></td>
      <td>Splits a string into an array of separate words</td>
    </tr>
    <tr>
      <td><a href="#stringdistancedamerau_levenshtein"><code>string::distance::damerau_levenshtein()</code></a></td>
      <td>Returns the Damerau - Levenshtein distance between two strings</td>
    </tr>
    <tr>
      <td><a href="#stringdistancenormalizeddamerau_levenshtein"><code>string::distance::normalized_damerau_levenshtein()</code></a></td>
      <td>Returns the normalised Damerau - Levenshtein distance between two strings</td>
    </tr>
    <tr>
      <td><a href="#stringdistancehamming"><code>string::distance::hamming()</code></a></td>
      <td>Returns the Hamming distance between two strings</td>
    </tr>
    <tr>
      <td><a href="#stringdistancelevenshtein"><code>string::distance::levenshtein()</code></a></td>
      <td>Returns the Levenshtein distance between two strings</td>
    </tr>
    <tr>
      <td><a href="#stringdistancenormalized_levenshtein"><code>string::distance::normalized_levenshtein()</code></a></td>
      <td>Returns the normalised Levenshtein distance between two strings</td>
    </tr>
    <tr>
      <td><a href="#stringdistanceosa"><code>string::distance::osa()</code></a></td>
      <td>Returns the OSA (Optimal String Alignment) distance between two strings</td>
    </tr>
    <tr>
      <td><a href="#stringhtmlencode"><code>string::html::encode()</code></a></td>
      <td>Encodes special characters into HTML entities to prevent HTML injection</td>
    </tr>
    <tr>
      <td><a href="#stringhtmlsanitize"><code>string::html::sanitize()</code></a></td>
      <td>Sanitizes HTML code to prevent the most dangerous subset of HTML injection</td>
    </tr>
    <tr>
      <td><a href="#stringis_alphanum"><code>string::is_alphanum()</code></a></td>
      <td>Checks whether a value has only alphanumeric characters</td>
    </tr>
    <tr>
      <td><a href="#stringis_alpha"><code>string::is_alpha()</code></a></td>
      <td>Checks whether a value has only alpha characters</td>
    </tr>
    <tr>
      <td><a href="#stringis_ascii"><code>string::is_ascii()</code></a></td>
      <td>Checks whether a value has only ascii characters</td>
    </tr>
    <tr>
      <td><a href="#stringis_datetime"><code>string::is_datetime()</code></a></td>
      <td>Checks whether a string representation of a date and time matches a specified format</td>
    </tr>
    <tr>
      <td><a href="#stringis_domain"><code>string::is_domain()</code></a></td>
      <td>Checks whether a value is a domain</td>
    </tr>
    <tr>
      <td><a href="#stringis_email"><code>string::is_email()</code></a></td>
      <td>Checks whether a value is an email</td>
    </tr>
    <tr>
      <td><a href="#stringis_hexadecimal"><code>string::is_hexadecimal()</code></a></td>
      <td>Checks whether a value is hexadecimal</td>
    </tr>
    <tr>
      <td><a href="#stringis_ip"><code>string::is_ip()</code></a></td>
      <td>Checks whether a value is an IP address</td>
    </tr>
    <tr>
      <td><a href="#stringis_ipv4"><code>string::is_ipv4()</code></a></td>
      <td>Checks whether a value is an IP v4 address</td>
    </tr>
    <tr>
      <td><a href="#stringis_ipv6"><code>string::is_ipv6()</code></a></td>
      <td>Checks whether a value is an IP v6 address</td>
    </tr>
    <tr>
      <td><a href="#stringis_latitude"><code>string::is_latitude()</code></a></td>
      <td>Checks whether a value is a latitude value</td>
    </tr>
    <tr>
      <td><a href="#stringis_longitude"><code>string::is_longitude()</code></a></td>
      <td>Checks whether a value is a longitude value</td>
    </tr>
    <tr>
      <td><a href="#stringis_numeric"><code>string::is_numeric()</code></a></td>
      <td>Checks whether a value has only numeric characters</td>
    </tr>
    <tr>
      <td><a href="#stringis_record"><code>string::is_record()</code></a></td>
      <td>Checks whether a string is a Record ID, optionally of a certain table</td>
    </tr>
    <tr>
      <td><a href="#stringis_semver"><code>string::is_semver()</code></a></td>
      <td>Checks whether a value matches a semver version</td>
    </tr>
    <tr>
      <td><a href="#stringis_ulid"><code>string::is_ulid()</code></a></td>
      <td>Checks whether a string is a ULID</td>
    </tr>
    <tr>
      <td><a href="#stringis_url"><code>string::is_url()</code></a></td>
      <td>Checks whether a value is a valid URL</td>
    </tr>
    <tr>
      <td><a href="#stringis_uuid"><code>string::is_uuid()</code></a></td>
      <td>Checks whether a string is a UUID</td>
    </tr>
    <tr>
      <td><a href="#stringsemvercompare"><code>string::semver::compare()</code></a></td>
      <td>Performs a comparison between two semver strings</td>
    </tr>
    <tr>
      <td><a href="#stringsemvermajor"><code>string::semver::major()</code></a></td>
      <td>Extract the major version from a semver string</td>
    </tr>
    <tr>
      <td><a href="#stringsemverminor"><code>string::semver::minor()</code></a></td>
      <td>Extract the minor version from a semver string</td>
    </tr>
    <tr>
      <td><a href="#stringsemverpatch"><code>string::semver::patch()</code></a></td>
      <td>Extract the patch version from a semver string</td>
    </tr>
    <tr>
      <td><a href="#stringsemverincmajor"><code>string::semver::inc::major()</code></a></td>
      <td>Increment the major version of a semver string</td>
    </tr>
    <tr>
      <td><a href="#stringsemverincminor"><code>string::semver::inc::minor()</code></a></td>
      <td>Increment the minor version of a semver string</td>
    </tr>
    <tr>
      <td><a href="#stringsemverincpatch"><code>string::semver::inc::patch()</code></a></td>
      <td>Increment the patch version of a semver string</td>
    </tr>
    <tr>
      <td><a href="#stringsemversetmajor"><code>string::semver::set::major()</code></a></td>
      <td>Set the major version of a semver string</td>
    </tr>
    <tr>
      <td><a href="#stringsemversetminor"><code>string::semver::set::minor()</code></a></td>
      <td>Set the minor version of a semver string</td>
    </tr>
    <tr>
      <td><a href="#stringsemversetpatch"><code>string::semver::set::patch()</code></a></td>
      <td>Set the patch version of a semver string</td>
    </tr>
    <tr>
      <td><a href="#stringsimilarityfuzzy"><code>string::similarity::fuzzy()</code></a></td>
      <td>Return the similarity score of fuzzy matching strings</td>
    </tr>
    <tr>
      <td><a href="#stringsimilarityjaro"><code>string::similarity::jaro()</code></a></td>
      <td>Returns the Jaro similarity between two strings</td>
    </tr>
    <tr>
      <td><a href="#stringsimilarityjarowinkler"><code>string::similarity::jaro_winkler()</code></a></td>
      <td>Return the Jaro-Winkler similarity between two strings</td>
    </tr>
  </tbody>
</table>

## `string::capitalize`

_(since v3.0.0)_

The `string::capitalize` function capitalizes the first letter of each word in a string.

```surql title="API DEFINITION"
string::capitalize(string) -> string
```

The following example shows this function, and its output:

```surql
string::capitalize("how to cook for forty humans");
```

```surql title="Output"
'How To Cook For Forty Humans'
```

<br />

## `string::concat`

The `string::concat` function concatenates values together into a single string.

```surql title="API DEFINITION"
string::concat(value, ...) -> string
```
The following example shows this function, and its output:

```surql
string::concat('this', ' ', 'is', ' ', 'a', ' ', 'test');
```

```surql title="Output"
'this is a test'
```

Any values received that are not a string will be stringified before concatenation.

```surql
string::concat(true, [], false);
```

```surql title="Output"
['true[]false']
```

Note that the stringified inputs are based on their actual computed values, and not the input tokens themselves. Even an expression can be

```surql
string::concat(not, actual, values);
//- ['NONENONENONE']

string::concat(CREATE ONLY person:aeon RETURN VALUE id, ' is ', 'cool!');
//- ['person:aeon is cool!']
```

<br />

## `string::contains`

The `string::contains` function checks whether a string contains another string.

```surql title="API DEFINITION"
string::contains(string, $predicate: string) -> bool
```
The following example shows this function, and its output:

```surql
string::contains('abcdefg', 'cde');
```

```surql title="Output"
true
```

<br />

## `string::ends_with`

> [!NOTE]
> This function was known as `string::endsWith` in versions of SurrealDB before 2.0. The behaviour has not changed.

The `string::ends_with` function checks whether a string ends with another string.

```surql title="API DEFINITION"
string::ends_with(string, $other: string) -> bool
```
The following example shows this function, and its output:

```surql
string::ends_with('some test', 'test');
```

```surql title="Output"
true
```

<br />

## `string::join`

The `string::join` function joins strings or stringified values together with a delimiter.

If you want to join an array of strings use [`array::join`](/docs/reference/query-language/functions/database-functions/array.md#arrayjoin).

```surql title="API DEFINITION"
string::join($delimiter: value, value...) -> string
```

The following example shows this function, and its output:

```surql
string::join(', ', 'a', 'list', 'of', 'items');
```

```surql title="Output"
"a, list, of, items"
```

<br />

## `string::len`

The `string::len` function returns the length of a given string in characters.

```surql title="API DEFINITION"
string::len(string) -> number
```

The following example shows this function, and its output:

```surql
string::len('this is a test');
```

```surql title="Output"
14
```

<br />

## `string::lowercase`

The `string::lowercase` function converts a string to lowercase.

```surql title="API DEFINITION"
string::lowercase(string) -> string
```

The following example shows this function, and its output:

```surql
string::lowercase('THIS IS A TEST');
```

```surql title="Output"
'this is a test'
```

<br />

## `string::matches`

The `string::matches` function performs a regex match on a string.

```surql title="API DEFINITION"
string::matches(string, $match_with: string|regex) -> bool
```

The following example shows this function, and its output:

```surql
[
  string::matches("grey", "gr(a|e)y"), 
  string::matches("gray", "gr(a|e)y")
];
```

```surql title="Output"
[true, true]
```

The second argument can be either a string or a [regex](/docs/reference/query-language/language-primitives/data-types/regex.md).

```surql
LET $input = "grey";
LET $string = "gr(a|e)y";
LET $regex = <regex>"gr(a|e)y";

[type::of($string), type::of($regex)];
//- ['string', 'regex']

[
  string::matches($input, $string),
  string::matches($input, $regex),
];
//- [true, true]
```

<br />

## `string::repeat`

The `string::repeat` function repeats a string a number of times. The repeat count must be non-negative; negative values return an error.

```surql title="API DEFINITION"
string::repeat(string, $times: number) -> string
```

The following example shows this function, and its output:

```surql
string::repeat('test', 3);
```

```surql title="Output"
'testtesttest'
```

<br />

## `string::replace`

The `string::replace` function replaces an occurrence of a string with another string.

**After 2.3**

```surql title="API DEFINITION"
string::replace(string, $from: string|regex, $to: string) -> string
```

**Before 2.3**

```surql title="API DEFINITION"
string::replace(string, $from: string, $to: string) -> string
```

The following example shows this function, and its output:

```surql
string::replace('this is a test', 'a test', 'awesome');
```

```surql title="Output"
'this is awesome'
```

As [`regexes`](/docs/reference/query-language/language-primitives/data-types/regex.md) are their own data type, the second argument can also be a regex instead of a string.

```surql
string::replace('Many languages only use consonants in their writing', <regex>'a|e|i|o|u', '');
```

```surql title="Output"
'Mny lnggs nly s cnsnnts n thr wrtng'
```

<br />

## `string::reverse`

The `string::reverse`  function reverses a string.

```surql title="API DEFINITION"
string::reverse(string) -> string
```
The following example shows this function, and its output:

```surql
string::reverse('this is a test');
```

```surql title="Output"
'tset a si siht'
```

<br />

## `string::slice`

The `string::slice` function extracts and returns a section of a string.

```surql title="API DEFINITION"
string::slice(string, $from: number, $to: number) -> string
```

The following example shows this function, and its output:

```surql
string::slice('this is a test', 10, 4);

"test"
```

<br />

## `string::slug`

The `string::slug`  function converts a string into a human and URL-friendly string.

```surql title="API DEFINITION"
string::slug(string) -> string
```
The following example shows this function, and its output:

```surql
string::slug('SurrealDB Cloud has launched!!! #ai_native_database #awesome');
```

```surql title="Output"
'surrealdb-cloud-has-launched-ai_native_database-awesome'
```

<br />

## `string::split`

The `string::split` function splits a string by a given delimiter.

```surql title="API DEFINITION"
string::split(string, $delimiter: string) -> array
```

The following example shows this function, and its output:

```surql
string::split('this, is, a, list', ', ');
```

```surql title="Output"
['this', 'is', 'a', 'list']
```

<br />

## `string::starts_with`

> [!NOTE]
> This function was known as `string::startsWith` in versions of SurrealDB before 2.0. The behaviour has not changed.

The `string::starts_with` function checks whether a string starts with another string.

```surql title="API DEFINITION"
string::starts_with(string, $predicate: string) -> bool
```

The following example shows this function, and its output:

```surql
string::starts_with('some test', 'some');
```

```surql title="Output"
true
```

<br />

## `string::trim`

The `string::trim` function removes whitespace from the start and end of a string.

```surql title="API DEFINITION"
string::trim(string) -> string
```

The following example shows this function, and its output:

```surql
string::trim('    this is a test    ');
```

```surql title="Output"
'this is a test'
```

<br />

## `string::uppercase`

The `string::uppercase` function converts a string to uppercase.

```surql title="API DEFINITION"
string::uppercase(string) -> string
```

The following example shows this function, and its output:

```surql
string::uppercase('this is a test');
```

```surql title="Output"
'THIS IS A TEST'
```

## `string::words`

The `string::words` function splits a string into an array of separate words.

```surql title="API DEFINITION"
string::words(string) -> array
```

The following example shows this function, and its output:

```surql
string::words('this is a test');
```

```surql title="Output"
['this', 'is', 'a', 'test']
```

## `string::distance::damerau_levenshtein`

The `string::distance::damerau_levenshtein` function returns the Damerau-Levenshtein distance between two strings.

```surql title="API DEFINITION"
string::distance::damerau_levenshtein(string, string) -> int
```

The following example shows this function, and its output in comparison with a number of strings.

```surql
LET $first     = "In a hole in the ground there lived a hobbit";
LET $same      = "In a hole in the ground there lived a hobbit";
LET $close     = "In a hole in the GROUND there lived a Hobbit";
LET $different = "A narrow passage holds four hidden treasures";
LET $short     = "Hi I'm Brian";

string::distance::damerau_levenshtein($first, $same);
//- 0
string::distance::damerau_levenshtein($first, $close);
//- 7
string::distance::damerau_levenshtein($first, $different);
//- 34
string::distance::damerau_levenshtein($first, $short);
//- 38
```

## `string::distance::normalized_damerau_levenshtein`

The `string::distance::normalized_damerau_levenshtein` function returns the normalised Damerau-Levenshtein distance between two strings. Normalised means that identical strings will return a score of 1, with less similar strings returning lower numbers as the distance grows.

```surql title="API DEFINITION"
string::distance::normalized_damerau_levenshtein(string, string) -> float
```

The following example shows this function, and its output in comparison with a number of strings.

```surql
LET $first     = "In a hole in the ground there lived a hobbit";
LET $same      = "In a hole in the ground there lived a hobbit";
LET $close     = "In a hole in the GROUND there lived a Hobbit";
LET $different = "A narrow passage holds four hidden treasures";
LET $short     = "Hi I'm Brian";

string::distance::normalized_damerau_levenshtein($first, $same);
//- 1f
string::distance::normalized_damerau_levenshtein($first, $close);
//- 0.8409090909090909f
string::distance::normalized_damerau_levenshtein($first, $different);
//- 0.2272727272727273f
string::distance::normalized_damerau_levenshtein($first, $short);
//- 0.13636363636363635f
```

## `string::distance::hamming`

The `string::distance::hamming` function returns the Hamming distance between two strings of equal length.

```surql title="API DEFINITION"
string::distance::hamming(string, string) -> int
```

The following example shows this function, and its output in comparison with a number of strings.

```surql
LET $first     = "In a hole in the ground there lived a hobbit";
LET $same      = "In a hole in the ground there lived a hobbit";
LET $close     = "In a hole in the GROUND there lived a Hobbit";
LET $different = "A narrow passage holds four hidden treasures";
LET $short     = "Hi I'm Brian";

string::distance::hamming($first, $same);
//- 0
string::distance::hamming($first, $close);
//- 7
string::distance::hamming($first, $different);
//- 40
string::distance::hamming($first, $short);
//- Error: strings must be of equal length
```

## `string::distance::levenshtein`

The `string::distance::levenshtein` function returns the Levenshtein distance between two strings.

```surql title="API DEFINITION"
string::distance::levenshtein(string, string) -> int
```

The following example shows this function, and its output in comparison with a number of strings.

```surql
LET $first     = "In a hole in the ground there lived a hobbit";
LET $same      = "In a hole in the ground there lived a hobbit";
LET $close     = "In a hole in the GROUND there lived a Hobbit";
LET $different = "A narrow passage holds four hidden treasures";
LET $short     = "Hi I'm Brian";

string::distance::levenshtein($first, $same);
//- 0
string::distance::levenshtein($first, $close);
//- 7
string::distance::levenshtein($first, $different);
//- 35
string::distance::levenshtein($first, $short);
//- 38
```

## `string::distance::normalized_levenshtein`

The `string::distance::normalized_levenshtein` function returns the normalised Levenshtein distance between two strings. Normalised means that identical strings will return a score of 1, with less similar strings returning lower numbers as the distance grows.

```surql title="API DEFINITION"
string::distance::normalized_levenshtein(string, string) -> float
```

The following example shows this function, and its output in comparison with a number of strings.

```surql
LET $first     = "In a hole in the ground there lived a hobbit";
LET $same      = "In a hole in the ground there lived a hobbit";
LET $close     = "In a hole in the GROUND there lived a Hobbit";
LET $different = "A narrow passage holds four hidden treasures";
LET $short     = "Hi I'm Brian";

string::distance::normalized_levenshtein($first, $same);
//- 1
string::distance::normalized_levenshtein($first, $close);
//- 0.8409090909090909f
string::distance::normalized_levenshtein($first, $different);
//- 0.20454545454545459f
string::distance::normalized_levenshtein($first, $short);
//- 0.13636363636363635f
```

## `string::distance::osa`

> [!NOTE]
> This function was known as `string::distance::osa_distance` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::distance::osa` function returns the OSA (Optimal String Alignment) distance between two strings.

```surql title="API DEFINITION"
string::distance::normalized_levenshtein(string, string) -> int
```

The following example shows this function, and its output in comparison with a number of strings.

```surql
LET $first     = "In a hole in the ground there lived a hobbit";
LET $same      = "In a hole in the ground there lived a hobbit";
LET $close     = "In a hole in the GROUND there lived a Hobbit";
LET $different = "A narrow passage holds four hidden treasures";
LET $short     = "Hi I'm Brian";

string::distance::osa($first, $same);
//- 0
string::distance::osa($first, $close);
//- 7
string::distance::osa($first, $different);
//- 34
string::distance::osa($first, $short);
//- 38
```

## `string::html::encode`

The `string::html::encode` function encodes special characters into HTML entities to prevent HTML injection. It is recommended to use this function in most cases when retrieving any untrusted content that may be rendered inside of an HTML document. You can learn more about its behaviour from the [original implementation](https://docs.rs/ammonia/latest/ammonia/fn.clean_text.html).

```surql title="API DEFINITION"
string::html::encode(string) -> string
```
The following example shows this function, and its output:

```surql
string::html::encode("<h1>Safe Title</h1><script>alert('XSS')</script><p>Safe paragraph. Not safe <span onload='logout()'>event</span>.</p>");
```

```surql title="Output"
'&lt;h1&gt;Safe&#32;Title&lt;&#47;h1&gt;&lt;script&gt;alert(&apos;XSS&apos;)&lt;&#47;script&gt;&lt;p&gt;Safe&#32;paragraph.&#32;Not&#32;safe&#32;&lt;span&#32;onload&#61;&apos;logout()&apos;&gt;event&lt;&#47;span&gt;.&lt;&#47;p&gt;'
```

<br />

## `string::html::sanitize`

The `string::html::sanitize` function sanitizes HTML code to prevent the most dangerous subset of HTML injection that can lead to attacks like cross-site scripting, layout breaking or clickjacking. This function will keep any other HTML syntax intact in order to support user-generated content that needs to contain HTML styling. It is only recommended to rely on this function if you want to allow the creators of the content to have some control over its HTML styling. You can learn more about its behaviour from the [original implementation](https://docs.rs/ammonia/latest/ammonia/fn.clean.html).

```surql title="API DEFINITION"
string::html::sanitize(string) -> string
```
The following example shows this function, and its output:

```surql
string::html::sanitize("<h1>Safe Title</h1><script>alert('XSS')</script><p>Safe paragraph. Not safe <span onload='logout()'>event</span>.</p>");
```

```surql title="Output"
'<h1>Safe Title</h1><p>Safe paragraph. Not safe <span>event</span>.</p>'
```
<br />

## `string::is_alphanum`

> [!NOTE]
> This function was known as `string::is::alphanum` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_alphanum` function checks whether a value has only alphanumeric characters.

```surql title="API DEFINITION"
string::is_alphanum(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_alphanum("ABC123");
```

```surql title="Output"
true
```

<br />

## `string::is_alpha`

> [!NOTE]
> This function was known as `string::is::alpha` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_alpha` function checks whether a value has only alpha characters.

```surql title="API DEFINITION"
string::is_alpha(string) -> bool
```
The following example shows this function, and its output:

```surql
string::is_alpha("ABCDEF");
```

```surql title="Output"
true
```

<br />

## `string::is_ascii`

> [!NOTE]
> This function was known as `string::is::ascii` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_ascii` function checks whether a value has only ascii characters.

```surql title="API DEFINITION"
string::is_ascii(string) -> bool
```
The following example shows this function, and its output:

```surql
string::is_ascii("ABC123"); -- true
'𓀀'.is_ascii(); -- false
```

<br />

## `string::is_datetime`

> [!NOTE]
> This function was known as `string::is::datetime` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_datetime` function checks whether a string representation of a date and time matches either the [datetime](/docs/reference/query-language/language-primitives/data-types/datetimes.md) format or a user-specified format.

```surql title="API DEFINITION"
string::is_datetime(string, $format: option<string>) -> bool
```

If no second argument is specified, this function will check if a string is a datetime or a format that can be cast into a datetime.

```surql
'1970-01-01'.is_datetime();  -- true
'1970-Jan-01'.is_datetime(); -- false
```

With a second argument, this function will check if a string matches the user-specified format. The output `false` may be returned in this case even if the input string is a valid datetime.

```surql
string::is_datetime("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S");
//- true

string::is_datetime("1970-01-01", "%Y-%m-%d %H:%M:%S");
//- false
```

This can be useful when validating datetimes obtained from other sources that do not use the [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) format.

```surql
string::is_datetime("5sep2024pm012345.6789", "%d%b%Y%p%I%M%S%.f");
```

```surql title="Output"
true
```

```surql
string::is_datetime("23:56:00 2015-09-05", "%Y-%m-%d %H:%M");
```

```surql title="Output"
false
```

[View all format options](/docs/reference/query-language/language-primitives/formatters.md)

<br />

## `string::is_domain`

> [!NOTE]
> This function was known as `string::is::domain` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_domain` function checks whether a value is a domain.

```surql title="API DEFINITION"
string::is_domain(string) -> bool
```
The following example shows this function, and its output:

```surql
string::is_domain("surrealdb.com");
```

```surql title="Output"
true
```

<br />

## `string::is_email`

> [!NOTE]
> This function was known as `string::is::email` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_email` function checks whether a value is an email.

```surql title="API DEFINITION"
string::is_email(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_email("info@surrealdb.com");
```

```surql title="Output"
true
```

<br />

## `string::is_hexadecimal`

> [!NOTE]
> This function was known as `string::is::hexadecimal` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_hexadecimal` function checks whether a value is hexadecimal.

```surql title="API DEFINITION"
string::is_hexadecimal(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_hexadecimal("ff009e");
```

```surql title="Output"
true
```

<br />

## `string::is_ip`

> [!NOTE]
> This function was known as `string::is::ip` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_ip` function checks whether a value is an IP address.

```surql title="API DEFINITION"
string::is_ip(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_ip("192.168.0.1");
```

```surql title="Output"
true
```

<br />

## `string::is_ipv4`

> [!NOTE]
> This function was known as `string::is::ipv4` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_ipv4` function checks whether a value is an IP v4 address.

```surql title="API DEFINITION"
string::is_ipv4(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_ipv4("192.168.0.1");
```

```surql title="Output"
true
```

<br />

## `string::is_ipv6`

> [!NOTE]
> This function was known as `string::is::ipv6` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_ipv6` function checks whether a value is an IP v6 address.

```surql title="API DEFINITION"
string::is_ipv6(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_ipv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
```

```surql title="Output"
true
```

<br />

## `string::is_latitude`

> [!NOTE]
> This function was known as `string::is::latitude` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_latitude` function checks whether a value is a latitude value.

```surql title="API DEFINITION"
string::is_latitude(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_latitude("-0.118092");
```

```surql title="Output"
true
```

<br />

## `string::is_longitude`

> [!NOTE]
> This function was known as `string::is::longitude` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_longitude` function checks whether a value is a longitude value.

```surql title="API DEFINITION"
string::is_longitude(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_longitude("51.509865");
```

```surql title="Output"
true
```

<br />

## `string::is_numeric`

> [!NOTE]
> This function was known as `string::is::numeric` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_numeric` function checks whether a value has only numeric characters.

```surql title="API DEFINITION"
string::is_numeric(string) -> bool
```
The following example shows this function, and its output:

```surql
string::is_numeric("1484091748");
```

```surql title="Output"
true
```

<br />

## `string::is_semver`

> [!NOTE]
> This function was known as `string::is::semver` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_semver` function checks whether a value matches a semver version.

```surql title="API DEFINITION"
string::is_semver(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_semver("1.0.0");
```

```surql title="Output"
true
```

<br />

## `string::is_ulid`

> [!NOTE]
> This function was known as `string::is::ulid` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_ulid` function checks whether a string is a ULID.

```surql title="API DEFINITION"
string::is_ulid(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_ulid("01JCJB3TPQ50XTG32WM088NKJD");
```

```surql title="Output"
true
```

<br />

## `string::is_url`

> [!NOTE]
> This function was known as `string::is::url` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_url` function checks whether a value is a valid URL.

```surql title="API DEFINITION"
string::is_url(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_url("https://surrealdb.com");
```

```surql title="Output"
true
```

<br />

## `string::is_record`

> [!NOTE]
> This function was known as `string::is::record` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_record` function checks whether a string is a Record ID.

```surql title="API DEFINITION"
string::is_record(string, $table_name: option<string|table>) -> bool
```

The second argument is optional and can be used to specify the table name that the record ID should belong to. If the table name is provided, the function will check if the record ID belongs to that table only.

```surql
string::is_record("person:test");           -- true
string::is_record("person:test", "person"); -- true
string::is_record("person:test", type::table("personn")); -- false
string::is_record("person:test", "other");  -- false
string::is_record("not a record id");       -- false
```

<br />

## `string::is_uuid`

> [!NOTE]
> This function was known as `string::is::uuid` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `string::is_uuid` function checks whether a string is a UUID.

```surql title="API DEFINITION"
string::is_uuid(string) -> bool
```

The following example shows this function, and its output:

```surql
string::is_uuid("018a6680-bef9-701b-9025-e1754f296a0f");
```

```surql title="Output"
true
```

<br />

## `string::semver::compare`

The `string::semver::compare` function performs a comparison on two semver strings and returns a number.

A value of `-1` indicates the first version is lower than the second, `0` indicates both versions are equal, and `1` indicates the first version is higher than the second.

```surql title="API DEFINITION"
string::semver::compare(string, $other: string) -> 1|0|-1
```

The following example shows this function, and its output:

```surql
string::semver::compare("1.0.0", "1.3.5");
//- -1
string::semver::compare("1.0.0", "1.0.0");
//- 0
string::semver::compare("3.0.0-beta.4", "2.6.0");
//- 1
```

<br />

## `string::semver::major`

The `string::semver::major` function extracts the major number out of a semver string.

```surql title="API DEFINITION"
string::semver::major(string) -> number
```

The following example shows this function, and its output:

```surql
string::semver::major("3.2.6");
```

```surql title="Output"
3
```

<br />

## `string::semver::minor`

The `string::semver::minor` function extracts the minor number out of a semver string.

```surql title="API DEFINITION"
string::semver::minor(string) -> number
```

The following example shows this function, and its output:

```surql
string::semver::minor("3.2.6");
```

```surql title="Output"
2
```

<br />

## `string::semver::patch`

The `string::semver::patch` function extracts the patch number out of a semver string.

```surql title="API DEFINITION"
string::semver::patch(string) -> number
```

The following example shows this function, and its output:

```surql
string::semver::patch("3.2.6");
```

```surql title="Output"
6
```

<br />

## `string::semver::inc::major`

The `string::semver::inc::major` function increments the major number of a semver string. As a result, the minor and patch numbers are reset to zero.

```surql title="API DEFINITION"
string::semver::inc::major(string) -> string
```

The following example shows this function, and its output:

```surql
string::semver::inc::major("1.2.3");
```

```surql title="Output"
'2.0.0'
```

<br />

## `string::semver::inc::minor`

The `string::semver::inc::minor` function increments the minor number of a semver string. As a result, the patch number is reset to zero.

```surql title="API DEFINITION"
string::semver::inc::minor(string) -> string
```

The following example shows this function, and its output:

```surql
string::semver::inc::minor("1.2.3");
```

```surql title="Output"
'1.3.0'
```

<br />

## `string::semver::inc::patch`

The `string::semver::inc::patch` function increments the patch number of a semver string.

```surql title="API DEFINITION"
string::semver::inc::patch(string) -> string
```

The following example shows this function, and its output:

```surql
string::semver::inc::patch("1.2.3");
```

```surql title="Output"
'1.2.4'
```

<br />

## `string::semver::set::major`

The `string::semver::set::major` function sets the major number of a semver string without changing the minor and patch numbers. The numeric argument must be non-negative.

```surql title="API DEFINITION"
string::semver::set::major(string, $major: number) -> string
```

The following example shows this function, and its output:

```surql
string::semver::set::major("1.2.3", 9);
```

```surql title="Output"
'9.2.3'
```

<br />

## `string::semver::set::minor`

The `string::semver::set::minor` function sets the minor number of a semver string without changing the major and patch numbers. The numeric argument must be non-negative.

```surql title="API DEFINITION"
string::semver::set::minor(string, $minor: number) -> string
```
The following example shows this function, and its output:

```surql
string::semver::set::minor("1.2.3", 9);
```

```surql title="Output"
'1.9.3'
```

<br />

## `string::semver::set::patch`

The `string::semver::set::patch` function sets the patch number of a semver string without changing the major and minor numbers. The numeric argument must be non-negative.

```surql title="API DEFINITION"
string::semver::set::patch(string, $patch: number) -> string
```

The following example shows this function, and its output:

```surql
string::semver::set::patch("1.2.3", 9);
```

```surql title="Output"
'1.2.9'
```

<br />

## `string::similarity::fuzzy`

```surql title="API DEFINITION"
string::similarity::fuzzy(string, string) -> int
```

The `string::similarity::fuzzy` function allows a comparison of similarity to be made. Any value that is greater than 0 is considered a fuzzy match.

```surql
-- returns 51
string::similarity::fuzzy("DB", "DB");
-- returns 47
string::similarity::fuzzy("DB", "db");
```

The similarity score is not based on a single score such as 1 to 100, but is built up over the course of the algorithm used to compare one string to another and will be higher for longer strings. As a result, similarity can only be compared from a single string to a number of possible matches, but not multiple strings to a number of possible matches.

While the first two uses of the function in the following example compare identical strings, the longer string returns a much higher fuzzy score.

```surql
-- returns 51
string::similarity::fuzzy("DB", "DB");
-- returns 2935
string::similarity::fuzzy(
  "SurrealDB Cloud is now live! We are excited to announce that we are inviting users from the waitlist to join. Stay tuned for your invitation!", "SurrealDB Cloud is now live! We are excited to announce that we are inviting users from the waitlist to join. Stay tuned for your invitation!"
);
-- returns 151 despite nowhere close to exact match
string::similarity::fuzzy(
  "SurrealDB Cloud is now live! We are excited to announce that we are inviting users from the waitlist to join. Stay tuned for your invitation!", "Surreal"
);
```

A longer example showing a comparison of similarity scores to one another:

```surql
LET $original = "SurrealDB";
LET $strings = ["SurralDB", "surrealdb", "DB", "Surreal", "real", "basebase", "eel", "eal"];

FOR $string IN $strings {
    LET $score = string::similarity::fuzzy($original, $string);
    IF $score > 0 {
        CREATE comparison SET of = $original + '\t' + $string,
        score = $score
    };
};

SELECT of, score FROM comparison ORDER BY score DESC;
```

```surql title="Output"
[
	{
		of: 'SurrealDB	surrealdb',
		score: 187
	},
	{
		of: 'SurrealDB	SurralDB',
		score: 165
	},
	{
		of: 'SurrealDB	Surreal',
		score: 151
	},
	{
		of: 'SurrealDB	real',
		score: 75
	},
	{
		of: 'SurrealDB	eal',
		score: 55
	},
	{
		of: 'SurrealDB	DB',
		score: 41
	}
]
```

## `string::similarity::jaro`

The `string::similarity::jaro` function returns the Jaro similarity between two strings. Two strings that are identical have a score of 1, while less similar strings will have lower scores as the distance between them increases.

```surql title="API DEFINITION"
string::similarity::jaro(string, string) -> float
```

The following example shows this function, and its output in comparison with a number of strings.

```surql
LET $first     = "In a hole in the ground there lived a hobbit";
LET $same      = "In a hole in the ground there lived a hobbit";
LET $close     = "In a hole in the GROUND there lived a Hobbit";
LET $different = "A narrow passage holds four hidden treasures";
LET $short     = "Hi I'm Brian";

string::similarity::jaro($first, $same);
//- 1
string::similarity::jaro($first, $close);
//- 0.8218673218673219f
string::similarity::jaro($first, $different);
//- 0.6266233766233765f
string::similarity::jaro($first, $short);
//- 0.4379509379509379f
```

## `string::similarity::jaro_winkler`

The `string::similarity::jaro_winkler` function returns the Jaro-Winkler similarity between two strings. Two strings that are identical have a score of 1, while less similar strings will have lower scores as the distance between them increases.

```surql title="API DEFINITION"
string::similarity::jaro_winkler(string, string) -> float
```

The following example shows this function, and its output in comparison with a number of strings.

```surql
LET $first     = "In a hole in the ground there lived a hobbit";
LET $same      = "In a hole in the ground there lived a hobbit";
LET $close     = "In a hole in the GROUND there lived a Hobbit";
LET $different = "A narrow passage holds four hidden treasures";
LET $short     = "Hi I'm Brian";

string::similarity::jaro_winkler($first, $same);
//- 1f
string::similarity::jaro_winkler($first, $close);
//- 0.8931203931203932f
string::similarity::jaro_winkler($first, $different);
//- 0.6266233766233765f
string::similarity::jaro_winkler($first, $short);
//- 0.4379509379509379f
```

## Method chaining

Method chaining allows functions to be called using the `.` dot operator on a value of a certain type instead of the full path of the function followed by the value.

```surql
-- Traditional syntax
string::is_alphanum("MyStrongPassword123");

-- Method chaining syntax
"MyStrongPassword123".is_alphanum();
```

```surql title="Output"
true
```

This is particularly useful for readability when a function is called multiple times.

```surql
-- Traditional syntax
string::concat(
  string::uppercase(
    string::replace(
      string::replace("I'll send you a check for the catalog", "ck", "que")
    , "og", "ogue")
  )
, "!!!!");

-- Method chaining syntax
"I'll send you a check for the catalog"
  .replace("ck", "que")
  .replace("og", "ogue")
  .uppercase()
  .concat("!!!!");
```

```surql title="Output"
"I'LL SEND YOU A CHEQUE FOR THE CATALOGUE!!!!"
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/time

# Time

Datetime functions and constants for working with and manipulating datetime values.

This page contains built-in functions and constants for working with and manipulating [datetime](/docs/reference/query-language/language-primitives/data-types/datetimes.md) values.

> [!NOTE]
> Since version 3.0.0-beta, the `::from::` functions (e.g. `time::from::millis()`) now use underscores (e.g. `time::from_millis()`) to better match the intent of the function and method syntax.

Many time functions take an `option<datetime>` in order to return certain values from a datetime such as its hours, minutes, day of the year, and so on. If no argument is present, the current datetime will be extracted and used. As such, all of the following function calls are valid and will not return an error.

```surql
time::hour(d'2024-09-04T00:32:44.107Z');
time::hour();

time::minute(d'2024-09-04T00:32:44.107Z');
time::minute();

time::yday(d'2024-09-04T00:32:44.107Z');
time::yday();
```

## Time functions

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeceil"><code>time::ceil()</code></a></td>
      <td scope="row" data-label="Description">Raises a datetime to the nearest multiple of duration from the Unix epoch</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeday"><code>time::day()</code></a></td>
      <td scope="row" data-label="Description">Extracts the day as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timefloor"><code>time::floor()</code></a></td>
      <td scope="row" data-label="Description">Truncates a datetime to the nearest lower multiple of duration from the Unix epoch</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeformat"><code>time::format()</code></a></td>
      <td scope="row" data-label="Description">Outputs a datetime according to a specific format</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timegroup"><code>time::group()</code></a></td>
      <td scope="row" data-label="Description">Groups a datetime by a particular time interval</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timehour"><code>time::hour()</code></a></td>
      <td scope="row" data-label="Description">Extracts the hour as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timemax"><code>time::max()</code></a></td>
      <td scope="row" data-label="Description">Returns the greatest datetime from an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timemicros"><code>time::micros()</code></a></td>
      <td scope="row" data-label="Description">Extracts the microseconds as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timemillis"><code>time::millis()</code></a></td>
      <td scope="row" data-label="Description">Extracts the milliseconds as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timemin"><code>time::min()</code></a></td>
      <td scope="row" data-label="Description">Returns the least datetime from an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeminute"><code>time::minute()</code></a></td>
      <td scope="row" data-label="Description">Extracts the minutes as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timemonth"><code>time::month()</code></a></td>
      <td scope="row" data-label="Description">Extracts the month as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timenano"><code>time::nano()</code></a></td>
      <td scope="row" data-label="Description">Returns the number of nanoseconds since the UNIX epoch until a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timenow"><code>time::now()</code></a></td>
      <td scope="row" data-label="Description">Returns the current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeround"><code>time::round()</code></a></td>
      <td scope="row" data-label="Description">Rounds a datetime to the nearest multiple of a specific duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timesecond"><code>time::second()</code></a></td>
      <td scope="row" data-label="Description">Extracts the second as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timetimezone"><code>time::timezone()</code></a></td>
      <td scope="row" data-label="Description">Returns the current local timezone offset in hours</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeunix"><code>time::unix()</code></a></td>
      <td scope="row" data-label="Description">Returns the number of seconds since the UNIX epoch</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timewday"><code>time::wday()</code></a></td>
      <td scope="row" data-label="Description">Extracts the week day as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeweek"><code>time::week()</code></a></td>
      <td scope="row" data-label="Description">Extracts the week as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeyday"><code>time::yday()</code></a></td>
      <td scope="row" data-label="Description">Extracts the yday as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeyear"><code>time::year()</code></a></td>
      <td scope="row" data-label="Description">Extracts the year as a number from a datetime or current datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeis_leap_year"><code>time::is_leap_year()</code></a></td>
      <td scope="row" data-label="Description">Checks if given datetime is a leap year</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timefrom_micros"><code>time::from_micros()</code></a></td>
      <td scope="row" data-label="Description">Calculates a datetime based on the microseconds since 1 January 1970 0:00:00 UTC.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timefrom_millis"><code>time::from_millis()</code></a></td>
      <td scope="row" data-label="Description">Calculates a datetime based on the milliseconds since 1 January 1970 0:00:00 UTC.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timefrom_nanos"><code>time::from_nanos()</code></a></td>
      <td scope="row" data-label="Description">Calculates a datetime based on the nanoseconds since 1 January 1970 0:00:00 UTC.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timefrom_secs"><code>time::from_secs()</code></a></td>
      <td scope="row" data-label="Description">Calculates a datetime based on the seconds since 1 January 1970 0:00:00 UTC.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timefrom_unix"><code>time::from_unix()</code></a></td>
      <td scope="row" data-label="Description">Calculates a datetime based on the seconds since 1 January 1970 0:00:00 UTC.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timefrom_ulid"><code>time::from_ulid()</code></a></td>
      <td scope="row" data-label="Description">Calculates a datetime based on the ULID.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timefrom_uuid"><code>time::from_uuid()</code></a></td>
      <td scope="row" data-label="Description">Calculates a datetime based on the UUID.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeset_year"><code>time::set_year()</code></a></td>
      <td scope="row" data-label="Description">Sets the year value of a datetime.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeset_month"><code>time::set_month()</code></a></td>
      <td scope="row" data-label="Description">Sets the year value of a datetime.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeset_day"><code>time::set_day()</code></a></td>
      <td scope="row" data-label="Description">Sets the year value of a datetime.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeset_hour"><code>time::set_hour()</code></a></td>
      <td scope="row" data-label="Description">Sets the year value of a datetime.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeset_minute"><code>time::set_minute()</code></a></td>
      <td scope="row" data-label="Description">Sets the year value of a datetime.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeset_second"><code>time::set_second()</code></a></td>
      <td scope="row" data-label="Description">Sets the year value of a datetime.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#timeset_nanosecond"><code>time::set_nanosecond()</code></a></td>
      <td scope="row" data-label="Description">Sets the year value of a datetime.</td>
    </tr>
  </tbody>
</table>

## Time constants

<table>
  <thead>
    <tr>
      <th scope="col">Constant</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Constant"><a href="#timeepoch"><code>time::epoch</code></a></td>
      <td scope="row" data-label="Description">Constant datetime representing the UNIX epoch</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#timemaximum"><code>time::maximum</code></a></td>
      <td scope="row" data-label="Description">Constant representing the greatest possible datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Constant"><a href="#timeminimum"><code>time::minimum</code></a></td>
      <td scope="row" data-label="Description">Constant representing the least possible datetime</td>
    </tr>
  </tbody>
</table>

## `time::ceil`

The `time::floor` function raises a datetime to the nearest multiple of duration from the Unix epoch (January 1, 1970).

```surql title="API DEFINITION"
time::ceil(datetime, $ceiling: duration) -> datetime
```

The following example shows this function, and its output:

```surql
LET $now = d'2024-08-30T02:22:50.231631Z';

RETURN [
  time::ceil($now, 1h),
  time::ceil($now, 1w)
];
```

```surql title="Output"
[
	d'2024-08-30T03:00:00Z',
	d'2024-09-05T00:00:00Z'
]
```

### Implementation details

Since this function raises a datetime to the next multiple of duration from the Unix epoch, this means for example that:

* time::ceil(now, 18d) rounds up from the nearest multiple of 18 days since epoch
* time::ceil(now, 17d) rounds up from the nearest multiple of 17 days since epoch

Each call uses a different modular base, so the truncation points land at different offsets that may not be sequential. For example, for October 23, 2023 (day ~19,653 since epoch):

* 19653 / 18 = 1091.83… → 1092 × 18 = day 19,656 → Oct 26
* 19653 / 17 = 1156.05… → 1157 × 17 = day 19,669 → Nov 08
* 19653 / 16 = 1228.31… → 1229 × 16 = day 19,664 → Nov 03
* 19653 / 15 = 1310.20… → 1311 × 15 = day 19,665 → Nov 04

```surql
LET $now = d'2023-10-23T09:12:53Z';

time::ceil($now, 18d); -- d'2023-10-26T00:00:00Z'
time::ceil($now, 17d); -- d'2023-11-08T00:00:00Z'
time::ceil($now, 16d); -- d'2023-11-03T00:00:00Z'
time::ceil($now, 15d); -- d'2023-11-04T00:00:00Z'
```

To use this function to raise to midnight of the next day, the duration values above can be subtracted from the datetime first, followed by `1d` for the ceiling.

```surql
LET $now = d'2023-10-23T09:12:53Z';

time::ceil($now - 18d, 1d); -- d'2023-10-06T00:00:00Z'
time::ceil($now - 17d, 1d); -- d'2023-10-07T00:00:00Z'
time::ceil($now - 16d, 1d); -- d'2023-10-08T00:00:00Z'
time::ceil($now - 15d, 1d); -- d'2023-10-09T00:00:00Z'
```

## `time::day`

The `time::day` function extracts the day as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::day(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::day(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
1
```

<br />

## `time::epoch`

The `time::epoch` constant returns the `datetime` for the UNIX epoch (1 January 1970).

```surql
-- Return the const
RETURN time::epoch;
//- d'1970-01-01T00:00:00Z'

-- Define field using the const
DEFINE FIELD since_epoch ON event COMPUTED time::now().floor(1d) - time::epoch;
CREATE ONLY event:one SET information = "Something happened";
//- { id: event:one, information: 'Something happened', since_epoch: 55y42w6d }
```

<br />

## `time::floor`

The `time::floor` function truncates a datetime to the nearest lower multiple of duration from the Unix epoch (January 1, 1970).

```surql title="API DEFINITION"
time::floor(datetime, $floor: duration) -> datetime
```

The following example shows this function, and its output:

```surql
time::floor(d"2021-11-01T08:30:17+00:00", 1w);
```

```surql title="Output"
d'2021-10-28T00:00:00Z'
```

### Implementation details

Since this function truncates a datetime to the nearest lower multiple of duration from the Unix epoch, this means for example that:

* time::floor(now, 18d) rounds down to the nearest multiple of 18 days since epoch
* time::floor(now, 17d) rounds down to the nearest multiple of 17 days since epoch

Each call uses a different modular base, so the truncation points land at different offsets that may not be sequential. For example, for October 23, 2023 (day ~19,653 since epoch):

* 19653 / 18 = 1091.83… → 1091 × 18 = day 19,638 → Oct 8
* 19653 / 17 = 1156.05… → 1156 × 17 = day 19,652 → Oct 22
* 19653 / 16 = 1228.31… → 1228 × 16 = day 19,648 → Oct 18
* 19653 / 15 = 1310.20… → 1310 × 15 = day 19,650 → Oct 20

```surql
LET $now = d'2023-10-23T09:12:53Z';

time::floor($now, 18d); -- d'2023-10-08T00:00:00Z'
time::floor($now, 17d); -- d'2023-10-22T00:00:00Z'
time::floor($now, 16d); -- d'2023-10-18T00:00:00Z'
time::floor($now, 15d); -- d'2023-10-20T00:00:00Z'
```

To use this function to truncate to the day, the duration values above can be subtracted from the datetime first, followed by `1d` for the floor.

```surql
LET $now = d'2023-10-23T09:12:53Z';

time::floor($now - 18d, 1d); -- d'2023-10-05T00:00:00Z'
time::floor($now - 17d, 1d); -- d'2023-10-06T00:00:00Z'
time::floor($now - 16d, 1d); -- d'2023-10-07T00:00:00Z'
time::floor($now - 15d, 1d); -- d'2023-10-08T00:00:00Z'
```

<br />

## `time::format`

The `time::format` function outputs a datetime as a string according to a specific format.

```surql title="API DEFINITION"
time::format(datetime, $format: string) -> string
```

The following example shows this function, and its output:

```surql
time::format(d"2021-11-01T08:30:17+00:00", "%Y-%m-%d");
```

```surql output="Response"
'2021-11-01'
```

[View all format options](/docs/reference/query-language/language-primitives/formatters.md)

<br />

## `time::group`

The `time::group` function reduces and rounds a datetime down to a particular time interval.

```surql title="API DEFINITION"
time::group(datetime, $group_by: 'year'|'month'|'day'|'hour'|'minute'|'second') -> datetime
```
The following example shows this function, and its output:

```surql
time::group(d"2021-11-01T08:30:17+00:00", "year");

d'2021-01-01T00:00:00Z'
```

<br />

## `time::hour`

The `time::hour` function extracts the hour as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::hour(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::hour(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
8
```

<br />

## `time::max`

The `time::max` function returns the greatest datetime from an array of datetimes.

```surql title="API DEFINITION"
time::max(array<datetime>) -> datetime
```

The following example shows this function, and its output:

```surql
time::max([ d"1987-06-22T08:30:45Z", d"1988-06-22T08:30:45Z" ])
```

```surql title="Output"
d'1988-06-22T08:30:45Z'
```

See also:

* [`array::max`](/docs/reference/query-language/functions/database-functions/array.md#arraymax), which extracts the greatest value from an array of values
* [`math::max`](/docs/reference/query-language/functions/database-functions/math.md#mathmax), which extracts the greatest number from an array of numbers

<br />

## `time::maximum`

_(since v2.3.0)_

The `time::maximum` constant returns the greatest possible datetime that can be used.

```surql title="API DEFINITION"
time::maximum -> datetime
```

Some examples of the constant in use:

```surql
time::maximum;

time::maximum + 1ns;

time::now() IN time::minimum..time::maximum;
```

```surql title="Output"
-------- Query 1 --------

d'+262142-12-31T23:59:59.999Z'

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

"Failed to compute: \"1ns + d'+262142-12-31T23:59:59.999999999Z'\", as the operation results in an arithmetic overflow."

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

true
```

<br />

## `time::micros`

The `time::micros` function extracts the microseconds as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::micros(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::micros(d"1987-06-22T08:30:45Z");
```

```surql title="Output"
551349045000000
```

<br />

## `time::millis`

The `time::millis` function extracts the milliseconds as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::millis(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::millis(d"1987-06-22T08:30:45Z");
```

```surql title="Output"
551349045000
```

<br />

## `time::min`

The `time::min` function returns the least datetime from an array of datetimes.

```surql title="API DEFINITION"
time::min(array<datetime>) -> datetime
```

The following example shows this function, and its output:

```surql
time::min([ d"1987-06-22T08:30:45Z", d"1988-06-22T08:30:45Z" ]);
```

```surql title="Output"
d'1987-06-22T08:30:45Z'
```

See also:

* [`array::min`](/docs/reference/query-language/functions/database-functions/array.md#arraymin), which extracts the least value from an array of values
* [`math::min`](/docs/reference/query-language/functions/database-functions/math.md#mathmin), which extracts the least number from an array of numbers

<br />

## `time::minimum`

_(since v2.3.0)_

The `time::minimum` constant returns the least possible datetime that can be used.

```surql title="API DEFINITION"
time::minimum -> datetime
```

Some examples of the constant in use:

```surql
time::minimum;

time::now() IN time::minimum..time::maximum;
```

```surql title="Output"
-------- Query 1 --------

d'-262143-01-01T00:00:00Z'

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

true
```

<br />

## `time::minute`

The `time::minute` function extracts the minutes as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::minute(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::minute(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
30
```

<br />

## `time::month`

The `time::month` function extracts the month as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::month(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::month(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
11
```

<br />

## `time::nano`

The `time::nano` function returns a datetime as an integer representing the number of nanoseconds since the UNIX epoch until a datetime, or the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::nano(option<datetime>) -> number
```

The result must fit in a signed 64-bit integer (roughly spanning datetimes from 1677 through 2262). Values outside that range return an arithmetic overflow error instead of `0`.

The following example shows this function, and its output:

```surql
time::nano(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
1635755417000000000
```

<br />

## `time::now`

The `time::now` function returns the current datetime as an ISO8601 timestamp.

```surql title="API DEFINITION"
time::now() -> datetime
```

<br />

## `time::round`

The `time::round` function rounds a datetime up by a specific duration.

```surql title="API DEFINITION"
time::round(datetime, $round_to: duration) -> datetime
```

The following example shows this function, and its output:

```surql
time::round(d"2021-11-01T08:30:17+00:00", 1w);
```

```surql title="Output"
d'2021-11-04T00:00:00Z'
```

<br />

## `time::second`

The `time::second` function extracts the second as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::second(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::second(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
17
```

<br />

## `time::timezone`

The `time::timezone` function returns the current local timezone offset in hours.

```surql title="API DEFINITION"
time::timezone() -> string
```

<br />

## `time::unix`

The `time::unix` function returns a datetime as an integer representing the number of seconds since the UNIX epoch until a certain datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::unix(option<datetime>) -> number
```
The following example shows this function, and its output:

```surql
time::unix(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
1635755417
```

<br />

## `time::wday`

The `time::wday` function extracts the week day as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::wday(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::wday(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
1
```

<br />

## `time::week`

The `time::week` function extracts the week as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::week(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::week(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
44
```

<br />

## `time::yday`

The `time::yday` function extracts the day of the year as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::yday(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::yday(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
305
```

<br />

## `time::year`

The `time::year` function extracts the year as a number from a datetime, or from the current date if no datetime argument is present.

```surql title="API DEFINITION"
time::year(option<datetime>) -> number
```

The following example shows this function, and its output:

```surql
time::year(d"2021-11-01T08:30:17+00:00");
```

```surql title="Output"
2021
```

<br />

## `time::is_leap_year()`

The `time::is_leap_year()` function checks if a given datetime is a leap year.

```surql title="API DEFINITION"
time::is_leap_year(datetime) -> bool
```

The following example shows this function, and its output:

```surql
-- Checks with current datetime if none is passed
time::is_leap_year();

time::is_leap_year(d"1987-06-22T08:30:45Z");
//- false

time::is_leap_year(d"1988-06-22T08:30:45Z");
//- true

-- Using function via method chaining
d'2024-09-03T02:33:15.349397Z'.is_leap_year();
//- true
```

## `time::from_micros`

The `time::from_micros` function calculates a datetime based on the microseconds since 1 January 1970 0:00:00 UTC.

```surql title="API DEFINITION"
time::from_micros(number) -> datetime
```
The following example shows this function, and its output:

```surql
time::from_micros(1000000);
```

```surql title="Output"
d'1970-01-01T00:00:01Z'
```

<br />

## `time::from_millis`

The `time::from_millis` function calculates a datetime based on the milliseconds since 1 January 1970 0:00:00 UTC.

```surql title="API DEFINITION"
time::from_millis(number) -> datetime
```

The following example shows this function, and its output:

```surql
time::from_millis(1000);
```

```surql title="Output"
d'1970-01-01T00:00:01Z'
```

<br />

## `time::from_nanos`

The `time::from_nanos` function calculates a datetime based on the nanoseconds since 1 January 1970 0:00:00 UTC.

```surql title="API DEFINITION"
time::from_nanos(number) -> datetime
```

The following example shows this function, and its output:

```surql
time::from_nanos(1000000);
```

```surql title="Output"
d'1970-01-01T00:00:00.001Z'
```

<br />

## `time::from_secs`

The `time::from_secs` function calculates a datetime based on the seconds since 1 January 1970 0:00:00 UTC.

```surql title="API DEFINITION"
time::from_secs(number) -> datetime
```
The following example shows this function, and its output:

```surql
time::from_secs(1000);
```

```surql title="Output"
d'1970-01-01T00:16:40Z'
```

<br />

## `time::from_unix`

The `time::from_unix` function calculates a datetime based on the seconds since 1 January 1970 0:00:00 UTC.

```surql title="API DEFINITION"
time::from_unix(number) -> datetime
```

The following example shows this function, and its output:

```surql
time::from_unix(1000);
```

```surql title="Output"
d'1970-01-01T00:16:40Z'
```

<br />

## `time::from_ulid`

The `time::from_ulid` function calculates a datetime based on the ULID.

```surql title="API DEFINITION"
time::from_ulid(ulid) -> datetime
```

The following example shows this function, and its output:

```surql
time::from_ulid("01JH5BBTK9FKTGSDXHWP5YP9TQ");
```

```surql title="Output"
d'2025-01-09T10:57:03.593Z'
```

As a ULID is only precise up to the millisecond, a conversion from a ULID to a timestamp will truncate nanosecond precision.

```surql
LET $now = time::now();
[$now, time::from_ulid(rand::ulid($now))];

-- Output:
[
	d'2026-01-29T02:07:06.494218Z',
	d'2026-01-29T02:07:06.494Z'
]
```

<br />

## `time::from_uuid`

The `time::from_uuid` function calculates a datetime based on the UUID.

```surql title="API DEFINITION"
time::from_uuid(uuid) -> datetime
```

The following example shows this function, and its output:

```surql
time::from_uuid(u'01944ab6-c1e5-7760-ab6a-127d37eb1b94');
```

```surql title="Output"
d'2025-01-09T10:57:58.757Z'
```

As a UUID is only precise up to the millisecond, a conversion from a UUID to a timestamp will truncate nanosecond precision.

```surql
LET $now = time::now();
[$now, time::from_uuid(rand::uuid($now))];

-- Output:
[
	d'2026-01-29T02:12:13.848476Z',
	d'2026-01-29T02:12:13.848Z'
]
```

<br />

## `time::set_year`

_(since v3.0.2)_

The `time::set_year` function sets the year value of a datetime.

```surql title="API DEFINITION"
time::set_year(datetime, $year: integer) -> datetime
```

Example:

```surql
d'1970-01-01T00:00:00.500000005Z'.set_year(2026);
-- Output
d'2026-01-01T00:00:00.500000000Z'
```

## `time::set_month`

_(since v3.0.2)_

The `time::set_month` function sets the month value of a datetime.

```surql title="API DEFINITION"
time::set_month(datetime, $month: integer) -> datetime
```

Example:

```surql
d'1970-01-01T00:00:00.500000005Z'.set_month(9);
-- Output
d'1970-09-01T00:00:00.500000005Z'
```

## `time::set_day`

_(since v3.0.2)_

The `time::set_day` function sets the day value of a datetime.

```surql title="API DEFINITION"
time::set_day(datetime, $day: integer) -> datetime
```

Example:

```surql
d'1970-01-01T00:00:00.500000005Z'.set_day(10);
-- Output
d'1970-01-10T00:00:00.500000005Z'
```

## `time::set_hour`

_(since v3.0.2)_

The `time::set_hour` function sets the hour value of a datetime.

```surql title="API DEFINITION"
time::set_hour(datetime, $hour: integer) -> datetime
```

Example:

```surql
d'1970-01-01T00:00:00.500000005Z'.set_hour(10);
-- Output
d'1970-01-01T10:00:00.500000005Z'
```

## `time::set_minute`

_(since v3.0.2)_

The `time::set_minute` function sets the minute value of a datetime.

```surql title="API DEFINITION"
time::set_minute(datetime, $minute: integer) -> datetime
```

Example:

```surql
d'1970-01-01T10:00:00.500000005Z'.set_minute(55);
-- Output
d'1970-01-01T10:55:00.500000005Z'
```

## `time::set_second`

_(since v3.0.2)_

The `time::set_second` function sets the second value of a datetime.

```surql title="API DEFINITION"
time::set_second(datetime, $second: integer) -> datetime
```

Example:

```surql
d'1970-01-01T10:00:00.500000005Z'.set_second(30);
-- Output
d'1970-01-01T10:00:30.500000005Z'
```

## `time::set_nanosecond`

_(since v3.0.2)_

The `time::set_nanosecond` function sets the nanosecond value of a datetime.

```surql title="API DEFINITION"
time::set_nanosecond(datetime, $nanosecond: integer) -> datetime
```

Example:

```surql
d'1970-01-01T10:00:00.500000005Z'.set_nanosecond(3535);
-- Output
d'1970-01-01T10:00:00.000003535Z'
```

Since nanoseconds are not needed in a datetime, setting the nanoseconds of a datetime to 0 can be used to make a datetime look cleaner.

```surql
d'1970-01-01T00:00:00.500000000Z'.set_nanosecond(0);
d'1970-01-01T00:00:00Z' -- output
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/type

# Type

These functions can be used for generating and coercing data to specific data types.

> [!NOTE]
> Since version 3.0.0-beta, the `::is::` functions (e.g. `type::is::record()`) now use underscores (e.g. `type::is_record()`) to better match the intent of the function and method syntax.

These functions can be used for generating and coercing data to specific data types. These functions are useful when accepting input values in client libraries, and ensuring that they are the desired type within SQL statements.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#typearray"><code>type::array()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into an array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typebool"><code>type::bool()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a boolean</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typebytes"><code>type::bytes()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into bytes</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typedatetime"><code>type::datetime()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typedecimal"><code>type::decimal()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a decimal</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeduration"><code>type::duration()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typefield"><code>type::field()</code></a></td>
      <td scope="row" data-label="Description">Projects a single field within a SELECT statement</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typefields"><code>type::fields()</code></a></td>
      <td scope="row" data-label="Description">Projects a multiple fields within a SELECT statement</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typefile"><code>type::file()</code></a></td>
      <td scope="row" data-label="Description">Converts two strings into a file pointer</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typefloat"><code>type::float()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a floating point number</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeint"><code>type::int()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into an integer</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typenumber"><code>type::number()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a number</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeof"><code>type::of()</code></a></td>
      <td scope="row" data-label="Description">Returns the type of a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typepoint"><code>type::point()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a geometry point</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typerecord"><code>type::record()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a record pointer</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typestring"><code>type::string()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a string</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typetable"><code>type::table()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a table</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typerange"><code>type::range()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a range</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeuuid"><code>type::uuid()</code></a></td>
      <td scope="row" data-label="Description">Converts a value into a UUID</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_array"><code>type::is_array()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type array</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_bool"><code>type::is_bool()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type bool</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_bytes"><code>type::is_bytes()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type bytes</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_collection"><code>type::is_collection()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type collection</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_datetime"><code>type::is_datetime()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type datetime</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_decimal"><code>type::is_decimal()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type decimal</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_duration"><code>type::is_duration()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type duration</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_float"><code>type::is_float()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type float</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_geometry"><code>type::is_geometry()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type geometry</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_int"><code>type::is_int()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type int</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_line"><code>type::is_line()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type line</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_none"><code>type::is_none()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type none</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_null"><code>type::is_null()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type null</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_multiline"><code>type::is_multiline()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type multiline</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_multipoint"><code>type::is_multipoint()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type multipoint</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_multipolygon"><code>type::is_multipolygon()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type multipolygon</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_number"><code>type::is_number()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type number</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_object"><code>type::is_object()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type object</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_point"><code>type::is_point()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type point</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_polygon"><code>type::is_polygon()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type polygon</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_polygon"><code>type::is_range()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type range</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_record"><code>type::is_record()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type record</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_string"><code>type::is_string()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type string</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#typeis_uuid"><code>type::is_uuid()</code></a></td>
      <td scope="row" data-label="Description">Checks if given value is of type uuid</td>
    </tr>
  </tbody>
</table>

## `type::array`

The `type::array` function converts a value into an array.

```surql title="API DEFINITION"
type::array(array|range) -> array
```

The following example shows this function, and its output:

```surql
type::array(1..=3);
```

```surql title="Output"
[1, 2, 3]
```

This is the equivalent of using [`<array>`](/docs/reference/query-language/language-primitives/casting.md#array) to cast a value to an array.

## `type::bool`

The `type::bool` function converts a value into a boolean.

```surql title="API DEFINITION"
type::bool(bool|string) -> bool
```

The following example shows this function, and its output:

```surql
type::bool("true");
```

```surql title="Output"
true
```

This is the equivalent of using [`<bool>`](/docs/reference/query-language/language-primitives/casting.md#bool) to cast a value to a boolean.

<br />

## `type::bytes`

The `type::bytes` function converts a value into bytes.

```surql title="API DEFINITION"
type::bytes(bytes|string) -> bool
```

The following example shows this function, and its output:

```surql
type::bytes("A few bytes");
```

```surql title="Output"
b"4120666577206279746573"
```

This is the equivalent of using [`<bytes>`](/docs/reference/query-language/language-primitives/casting.md) to cast a value to bytes.

<br />

## `type::datetime`

The `type::datetime` function converts a value into a datetime.

```surql title="API DEFINITION"
type::datetime(datetime|string) -> datetime
```
The following example shows this function, and its output:

```surql
type::datetime("2022-04-27T18:12:27+00:00");
```

```surql title="Output"
d'2022-04-27T18:12:27Z'
```

This is the equivalent of using [`<datetime>`](/docs/reference/query-language/language-primitives/casting.md#datetime) to cast a value to a datetime.

<br />

## `type::decimal`

The `type::decimal` function converts a value into a decimal.

```surql title="API DEFINITION"
type::decimal(decimal|float|int|number|string) -> decimal
```

The following example shows this function, and its output:

```surql
type::decimal("12345");
```

```surql title="Output"
12345dec
```

This is the equivalent of using [`<decimal>`](/docs/reference/query-language/language-primitives/casting.md#decimal) to cast a value to a decimal.

<br />

## `type::duration`

The `type::duration` function converts a value into a duration.

```surql title="API DEFINITION"
type::duration(duration|string) -> duration
```
The following example shows this function, and its output:

```surql
type::duration("4h");
```

```surql title="Output"
4h
```

This is the equivalent of using [`<duration>`](/docs/reference/query-language/language-primitives/casting.md#duration) to cast a value to a duration.

<br />

## `type::field`

The `type::field` function projects a single field within a SELECT statement.

```surql title="API DEFINITION"
type::field(string)
```
The following example shows this function, and its output:

```surql
CREATE person:test SET title = 'Mr',
  name.first = 'Tobie',
  name.last = 'Morgan Hitchcock';

LET $param = 'name.first';

SELECT type::field($param), type::field('name.last') FROM person;

SELECT VALUE { 'firstname': type::field($param),
  lastname: type::field('name.last') } FROM person;

SELECT VALUE [type::field($param),
  type::field('name.last')] FROM person;
```

```surql title="Output"
[
	{
		id: person:test,
		title: 'Mr',
		name: {
			first: 'Tobie',
			last: 'Morgan Hitchcock',
	    }
	}
]
```

<br/>

From SurrealDB 3.0, this function can be used after the `OMIT` clause of a `SELECT` statement.

```surql
LET $omit = "id";
CREATE person SET name = "Galen", surname = "Pathwarden", age = 19;
SELECT * OMIT type::field($omit) FROM person;
```

```surql title="Output"
[
	{
		age: 19,
		name: 'Galen',
		surname: 'Pathwarden'
	}
]
```

<br />

## `type::fields`

The `type::fields` function projects one or more fields within a SELECT statement.

```surql title="API DEFINITION"
type::fields(array<string>)
```
The following example shows this function, and its output:

```surql
CREATE person:test SET title = 'Mr',
  name.first = 'Tobie',
  name.last = 'Morgan Hitchcock';

LET $param = ['name.first', 'name.last'];

SELECT type::fields($param), type::fields(['title']) FROM person;

SELECT VALUE { 'names': type::fields($param) } FROM person;

SELECT VALUE type::fields($param) FROM person;
```

```surql title="Output"
[
	{
		id: person:test,
		title: 'Mr',
		name: {
			first: 'Tobie',
			last: 'Morgan Hitchcock',
		}
	}
]
```

<br/>

From SurrealDB 3.0, this function can be used after the `OMIT` clause of a `SELECT` statement.

```surql
LET $omit = ["id", "age"];
CREATE person SET name = "Galen", surname = "Pathwarden", age = 19;
SELECT * OMIT type::fields($omit) FROM person;
```

```surql title="Output"
[
	{
		name: 'Galen',
		surname: 'Pathwarden'
	}
]
```

<br />

## `type::file`

_(since v3.0.0)_

The `type::file` function converts two strings representing a bucket name and a key into a [file pointer](/docs/reference/query-language/language-primitives/data-types/files.md).

```surql title="API DEFINITION"
type::file($bucket: string, $key: string) -> file
```

An example of a file pointer created using this function:

```surql
type::file("my_bucket", "file_name")
```

```surql title="Output"
f"my_bucket:/file_name"
```

The following query shows the equivalent file pointer when created using the `f` prefix:

```surql
type::file("my_bucket", "file_name") == f"my_bucket:/file_name";
```

```surql title="Output"
true
```

Once a [bucket has been defined](/docs/reference/query-language/statements/define/indexes.md), operations using one of the [file functions](/docs/reference/query-language/functions/database-functions/file.md) can be performed on the file pointer.

```surql
DEFINE BUCKET my_bucket BACKEND "memory";

type::file("my_bucket", "file_name").put("Some data inside");
type::file("my_bucket", "file_name").get();
```

```surql title="Output"
b"536F6D65206461746120696E73696465"
```

<br />

## `type::float`

The `type::float` function converts a value into a float.

```surql title="API DEFINITION"
type::float(decimal|float|int|number|string) -> float
```

The following example shows this function, and its output:

```surql
type::float("12345");
```

```surql title="Output"
12345f
```
This is the equivalent of using [`<float>`](/docs/reference/query-language/language-primitives/casting.md#float) to cast a value to a float.

<br />

## `type::int`

The `type::int` function converts a value into an integer.

```surql title="API DEFINITION"
type::int(decimal|float|int|number|string) -> int
```
The following example shows this function, and its output:

```surql
type::int("12345");
```

```surql title="Output"
12345
```
This is the equivalent of using [`<int>`](/docs/reference/query-language/language-primitives/casting.md#int) to cast a value to an int.

<br />

## `type::number`

The `type::number` function converts a value into a number.

```surql title="API DEFINITION"
type::number(decimal|float|int|number|string) -> number
```
The following example shows this function, and its output:

```surql
type::number("12345");
```

```surql title="Output"
12345
```

This is the equivalent of using [`<number>`](/docs/reference/query-language/language-primitives/casting.md#number) to cast a value to a number.

<br />

## `type:of`

_(since v3.0.0)_

The `type::of` function returns a string denoting the type of a value.

```surql title="API DEFINITION"
type::of(value) -> string
```

```surql
type::of(2022dec);        -- 'decimal';
type::of(["some", 9]);    -- 'array';
type::of((50.0, 9.9));    -- 'geometry<point>'
```

## `type::point`

The `type::point` function converts a value into a geometry point.

```surql title="API DEFINITION"
type::point(array|point) -> point
```

The following example shows this function, and its output:

```surql
type::point([ 51.509865, -0.118092 ]);
```

```surql title="Output"
(51.509865, -0.118092)
```

<br />

## `type::range`

The `type::range` function converts a value into a [range](/docs/reference/query-language/language-primitives/data-types/ranges.md). It accepts a single argument, either a range or an array with two values. If the argument is an array, it will be converted into a range, similar to [casting](/docs/reference/query-language/language-primitives/casting.md).

```surql title="API DEFINITION"
type::range(range|array) -> range<record>
```

The following example shows this function, and its output:

```surql
type::range([1, 2]);
//- 1..2

type::range(1..10);
//- 1..10

type::range([1,9,4]);
//- 'Expected a range but cannot convert [1, 9, 4] into a range'
```

<br />

## `type::record`

**3.x**

> [!NOTE]
> This function was known as `type::thing` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::record` function converts a value into a record pointer definition.

```surql title="API DEFINITION"
type::record($table: any, $key: any) -> record
```

The following example shows this function, and its output:

```surql
LET $tb = "person";
LET $id = "tobie";
RETURN type::record($tb, $id);
```

An example of this function being used to turn an array of objects into records to be created or upserted:

```surql
FOR $data IN [
	{
		id: 9,
		name: 'Billy'
	},
	{
		id: 10,
		name: 'Bobby'
	}
] {
	UPSERT type::record('person', $data.id) CONTENT $data;
};
```

An example of the same except in which the `num` field is to be used as the record's ID. In this case, it can be mapped with the [`array::map()`](/docs/reference/query-language/functions/database-functions/array.md#arraymap) function to rename `num` as `id` so that the following `CONTENT` clause does not create both a `num` and an `id` with the same value.

```surql
FOR $data IN [
	{
		name: 'Billy',
		num: 9
	},
    {
		name: 'Bobby',
		num: 10
	},
].map(|$o| {
    id: $o.num,
    name: $o.name
}) {
    UPSERT type::record('person', $data.id) CONTENT $data;
};
```

If the second argument passed into `type::record` is a record ID, the latter part of the ID (the record identifier) will be extracted and used.

```surql
type::record("person", person:mat);
```

```surql title="Output"
person:mat
```

The output of the above function call will thus be `person:mat`, not `person:person:mat`.

When the first argument is already a record ID and the second is a string, the string is treated as a **table constraint**: the function returns the record only if it belongs to that table, and errors otherwise. This form is common in access-control clauses such as `SELECT * FROM type::record($id, "user")`, where `$id` must be a `user` record and not an ID from another table.

```surql
type::record(person:tobie, 'person'); -- person:tobie
type::record(person:tobie, 'cat');
```

```surql title="Output"
error: record is not in table `cat`
```

**2.x**

The `type::record` function returns a record from a record or a string, with an optional argument to confirm the table name.

```surql title="API DEFINITION"
type::record($record: record|string,
  $table_name: option<string>) -> record
```

The function will return a record as long as the argument passed in is already a record, or a string that can be parsed into one.

```surql
-- Both return person:tobie
type::record(person:tobie);
type::record('person:tobie');
```

The optional second argument allows an assertion that the record passed in is of this table name.

```surql
type::record('person:tobie', 'person'); -- person:tobie
type::record('person:tobie', 'cat');
//- "Expected a record<cat> but cannot convert 'person:tobie' into a
  record<cat>"
```

This second argument is mostly useful when involving a parameter that may or may not be a certain value. In the code below, the function may or may not err depending on whether the `$record` parameter is a `person` or a `cat` record.

```surql
LET $record = rand::enum(person:tobie, cat:tobie);
type::record($record, 'person');
```

<br/>

## `type::string`

The `type::string` function converts any value except `NONE`, `NULL`, and `bytes` into a string.

```surql title="API DEFINITION"
type::string(any) -> string
```

The following example shows this function, and its output:

```surql
type::string(12345);
```

```surql title="Output"
'12345'
```

This is the equivalent of using [`<string>`](/docs/reference/query-language/language-primitives/casting.md#string) to cast a value to a string.

<br />

## `type::string_lossy`

_(since v3.0.0)_

The `type::string_lossy` function converts any value except `NONE`, `NULL`, and `bytes` into a string. In the case of bytes, it will not return an error if the bytes are not valid UTF-8. Instead, invalid bytes will be replaced with the character `�` (`U+FFFD REPLACEMENT CHARACTER`, used in Unicode to represent a decoding error).

```surql title="API DEFINITION"
type::string(any) -> string
```

The following example shows this function, and its output:

```surql
-- Contains some invalid bytes
type::string_lossy(<bytes>[83,
  117,
  114,
  255,
  114,
  101,
  97,
  254,
  108,
  68,
  66]);
-- valid bytes
type::string_lossy(<bytes>[ 83,
  117,
  114,
  114,
  101,
  97,
  108,
  68,
  66 ]);
```

```surql title="Output"
-------- Query --------

'Sur�rea�lDB'

-------- Query --------

'SurrealDB'
```

This is similar to using [`<string>`](/docs/reference/query-language/language-primitives/casting.md#string) to cast a value to a string, except that an input of bytes will not fail.

<br />

## `type::table`

The `type::table` function converts a value into a table name.

```surql title="API DEFINITION"
type::table(record|string) -> string
```
The following example shows this function, and its output:

```surql
[
  type::table("person"),
  type::table(cat:one)
];
```

```surql title="Output"
[person, cat]
```

As of version 2.0, SurrealDB no longer eagerly parses strings into record IDs. As such, the output of the last item ("dog:two") in the following example will differ. In version 1.x, it will be eagerly parsed into a record ID after which the `dog` table name will be returned, while in later editions it will be treated as a string and converted into the table name `dog:two`. As of version 3.0, a number is no longer accepted as input, because a number on its own is not a valid table name.

```surql
[
  type::table(cat:one),
  type::table("dog"),
  type::table("dog:two"),
];
```

```surql title="Output"
[
	cat,
	dog,
	`dog:two`
]
```

<br />

## `type::uuid`

The `type::uuid` function converts a value into a UUID.

```surql title="API DEFINITION"
type::uuid(string|uuid) -> uuid
```

The following example shows this function, and its output:

```surql
type::uuid("0191f946-936f-7223-bef5-aebbc527ad80");
```

```surql title="Output"
u'0191f946-936f-7223-bef5-aebbc527ad80'
```
<br />

## `type::is_array`

> [!NOTE]
> This function was known as `type::is::array` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_array` function checks if the passed value is of type `array`.

```surql title="API DEFINITION"
type::is_array(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_array([ 'a', 'b', 'c' ]);
```

```surql title="Output"
true
```

<br />

## `type::is_bool`

> [!NOTE]
> This function was known as `type::is::bool` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_bool` function checks if the passed value is of type `bool`.

```surql title="API DEFINITION"
type::is_bool(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_bool(true);
```

```surql title="Output"
true
```

<br />

## `type::is_bytes`

> [!NOTE]
> This function was known as `type::is::bytes` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_bytes` function checks if the passed value is of type `bytes`.

```surql title="API DEFINITION"
type::is_bytes(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_bytes("I am not bytes");
```

```surql title="Output"
false
```

<br />

## `type::is_collection`

> [!NOTE]
> This function was known as `type::is::collection` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_collection` function checks if the passed value is of type `collection`.

```surql title="API DEFINITION"
type::is_collection(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_collection("I am not a collection");
```

```surql title="Output"
false
```

<br />

## `type::is_datetime`

> [!NOTE]
> This function was known as `type::is::datetime` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_datetime` function checks if the passed value is of type `datetime`.

```surql title="API DEFINITION"
type::is_datetime(any) -> bool
```
The following example shows this function, and its output:

```surql
type::is_datetime(time::now());
```

```surql title="Output"
true
```

<br />

## `type::is_decimal`

> [!NOTE]
> This function was known as `type::is::decimal` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_decimal` function checks if the passed value is of type `decimal`.

```surql title="API DEFINITION"
type::is_decimal(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_decimal(<decimal>
  13.5719384719384719385639856394139476937756394756);
```

```surql title="Output"
true
```

<br />

## `type::is_duration`

> [!NOTE]
> This function was known as `type::is::duration` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_duration` function checks if the passed value is of type `duration`.

```surql title="API DEFINITION"
type::is_duration(any) -> bool
```
The following example shows this function, and its output:

```surql
type::is_duration('1970-01-01T00:00:00');
```

```surql title="Output"
false
```

<br />

## `type::is_float`

> [!NOTE]
> This function was known as `type::is::float` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_float` function checks if the passed value is of type ` float`.

```surql title="API DEFINITION"
type::is_float(any) -> bool
```
The following example shows this function, and its output:

```surql
type::is_float(<float> 41.5);
```

```surql title="Output"
true
```

<br />

## `type::is_geometry`

> [!NOTE]
> This function was known as `type::is::geometry` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_geometry` function checks if the passed value is of type `geometry`.

```surql title="API DEFINITION"
type::is_geometry(any) -> bool
```
The following example shows this function, and its output:

```surql
type::is_geometry((-0.118092, 51.509865));
```

```surql title="Output"
true
```

<br />

## `type::is_int`

> [!NOTE]
> This function was known as `type::is::int` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_int` function checks if the passed value is of type `int`.

```surql title="API DEFINITION"
type::is_int(any) -> bool
```
The following example shows this function, and its output:

```surql
type::is_int(<int> 123);
```

```surql title="Output"
true
```

<br />

## `type::is_line`

> [!NOTE]
> This function was known as `type::is::line` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_line` function checks if the passed value is of type `line`.

```surql title="API DEFINITION"
type::is_line(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_line("I am not a line");
```

```surql title="Output"
false
```

<br />

## `type::is_none`

> [!NOTE]
> This function was known as `type::is::none` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_none` function checks if the passed value is of type `none`.

```surql title="API DEFINITION"
type::is_none(any) -> bool
```
The following example shows this function, and its output:

```surql
type::is_none(NONE);
```

```surql title="Output"
true
```

<br />

## `type::is_null`

> [!NOTE]
> This function was known as `type::is::null` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_null` function checks if the passed value is of type `null`.

```surql title="API DEFINITION"
type::is_null(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_null(NULL);
```

```surql title="Output"
true
```

<br />

## `type::is_multiline`

> [!NOTE]
> This function was known as `type::is::multiline` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_multiline` function checks if the passed value is of type `multiline`.

```surql title="API DEFINITION"
type::is_multiline(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_multiline("I am not a multiline");
```

```surql title="Output"
false
```

<br />

## `type::is_multipoint`

> [!NOTE]
> This function was known as `type::is::multipoint` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_multipoint` function checks if the passed value is of type `multipoint`.

```surql title="API DEFINITION"
type::is_multipoint(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_multipoint("I am not a multipoint");
```

```surql title="Output"
false
```

<br />

## `type::is_multipolygon`

> [!NOTE]
> This function was known as `type::is::multipolygon` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_multipolygon` function checks if the passed value is of type `multipolygon`.

```surql title="API DEFINITION"
type::is_multipolygon(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_multipolygon("I am not a multipolygon");
```

```surql title="Output"
false
```

<br />

## `type::is_number`

> [!NOTE]
> This function was known as `type::is::number` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_number` function checks if the passed value is of type `number`.

```surql title="API DEFINITION"
type::is_number(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_number(123);
```

```surql title="Output"
true
```

<br />

## `type::is_object`

> [!NOTE]
> This function was known as `type::is::object` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_object` function checks if the passed value is of type `object`.

```surql title="API DEFINITION"
type::is_object(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_object({ hello: 'world' });
```

```surql title="Output"
true
```

<br />

## `type::is_point`

> [!NOTE]
> This function was known as `type::is::point` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_point` function checks if the passed value is of type `point`.

```surql title="API DEFINITION"
type::is_point(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_point((-0.118092, 51.509865));
```

```surql title="Output"
true
```

<br />

## `type::is_polygon`

> [!NOTE]
> This function was known as `type::is::polygon` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_polygon` function checks if the passed value is of type `polygon`.

```surql title="API DEFINITION"
type::is_polygon(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_polygon("I am not a polygon");
```

```surql title="Output"
false
```

## `type::is_range`

> [!NOTE]
> This function was known as `type::is::range` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_range` function checks if the passed value is of type `range`.

```surql title="API DEFINITION"
type::is_range(any) -> bool
```

```surql
type::is_range(0..1);
//- true

-- method syntax
(0..1).is_range();
//- true
```

## `type::is_record`

> [!NOTE]
> This function was known as `type::is::record` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_record` function checks if the passed value is of type `record`.

```surql title="API DEFINITION"
type::is_record(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_record(user:tobie);
```

```surql title="Output"
true
```

### Validate a table

```surql title="Check if user:tobie is a record on the test table"
type::is_record(user:tobie, 'test');

-- false
```

<br />

## `type::is_string`

> [!NOTE]
> This function was known as `type::is::string` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_string` function checks if the passed value is of type `string`.

```surql title="API DEFINITION"
type::is_string(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_string("abc");
```

```surql title="Output"
true
```

<br />

## `type::is_uuid`

> [!NOTE]
> This function was known as `type::is::uuid` in versions of SurrealDB before 3.0.0. The behaviour has not changed.

The `type::is_uuid` function checks if the passed value is of type `uuid`.

```surql title="API DEFINITION"
type::is_uuid(any) -> bool
```

The following example shows this function, and its output:

```surql
type::is_uuid(u"018a6680-bef9-701b-9025-e1754f296a0f");
```

```surql title="Output"
true
```

<br /><br />

## Method chaining

Method chaining allows functions to be called using the `.` dot operator on a value of a certain type instead of the full path of the function followed by the value.

```surql
-- Traditional syntax
type::is_record(r"person:aeon", "cat");

-- Method chaining syntax
r"person:aeon".is_record("cat");
```

```surql title="Output"
false
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/value

# Value

This module contains several miscellaneous functions that can be used with values of any type.

This module contains several miscellaneous functions that can be used with values of any type.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#chain"><code>.chain()</code></a></td>
      <td scope="row" data-label="Description">Allows an anonymous function to be called on a value</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#valuediff"><code>value::diff()</code></a></td>
      <td scope="row" data-label="Description">Returns the operation required for one value to equal another</td>
    </tr>
	<tr>
      <td scope="row" data-label="Function"><a href="#valueexpect"><code>value::expect()</code></a></td>
      <td scope="row" data-label="Description">Returns the current value if the closure that captures it returns a value of `true`</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#valuepatch"><code>value::patch()</code></a></td>
      <td scope="row" data-label="Description">Applies JSON Patch operations to a value</td>
    </tr>
  </tbody>
</table>

## `.chain()`

The `.chain()` method passes a value into a [closure](/docs/reference/query-language/language-primitives/data-types/closures.md) through which an operation can be performed to return any value.

```surql title="API DEFINITION"
value.chain(closure) -> value;
```

The output of this function is usually based on the value passed into the closure, but can be something else entirely.

```surql
'SurrealDB'.chain(|$n| $n + ' 3.0');
//- 'SurrealDB 3.0'

'SurrealDB'.chain(|$n| "Something else");
```

The function is only called using the `.` operator (method syntax) and, as the name implies, works well within a chain of methods.

```surql
{ company: 'SurrealDB', latest_version: '3.1' }
    .chain(|$name| <string>$name)
    .replace('SurrealDB', 'SURREALDB!!!!!');
```

```surql title="Output"
"{ company: 'SURREALDB!!!!!', latest_version: '3.1' }"
```

For a similar function that allows using a closure on each item in an array instead of a value as a whole, see [array::map](/docs/reference/query-language/functions/database-functions/array.md#arraymap).

## `value::diff`

The `value::diff` function returns an object that shows the [JSON Patch](https://jsonpatch.com/) operation(s) required for the first value to equal the second one.

```surql title="API DEFINITION"
value::diff(value, $other: value) -> array<object>
```

The following is an example of the `value::diff` function used to display the changes required to change one string into another. Note that the JSON Patch spec requires an array of objects, and thus an array will be returned even if only one patch is needed between two values.

```surql
'tobie'.diff('tobias');
```

```surql title="Output"
[
	{
		op: 'change',
		path: '',
		value: '@@ -1,5 +1,6 @@
 tobi
-e
+as
'
	}
]
```

An example of the output when the diff output includes more than one operation:

```surql
{ company: 'SurrealDB' }.diff({ company: 'SurrealDB!!',
  latest_version: '3.1',
  location: city:london });
```

```surql title="Output"
[
	{
		op: 'change',
		path: '/company',
		value: '@@ -2,8 +2,10 @@
 urrealDB
+!!
'
	},
	{
		op: 'add',
		path: '/latest_version',
		value: '2.0'
	},
	{
		op: 'add',
		path: '/location',
		value: city:london
	}
]
```

## `value::expect`

_(since v3.1.0)_

The `value::expect` function returns the original value if the closure it is passed into matches a certain condition, and an error otherwise.

```surql title="API DEFINITION"
value.expect(closure) -> value;
value.expect(closure, $message: string) -> value;
```

This function uses the original value as the first argument. If the original closure returns `true`, the original value is returned.

```surql
{ name: "Loki" }.expect(|$obj| $obj.name = "Loki");
//- [{ name: 'Loki' }]

{ name: "Loki" }.expect(|$obj| $obj.name = "Baldr");
//- 'An error occurred: value::expect assertion failed'
```

A user-defined error string can be added after the closure if more context is desired.

```surql
{ name: "Loki" }
	.expect(
		|$obj| $obj.name = "Baldr", 
		"Norse god's name should be Loki"
	);
```

```surql title="Output"
"An error occurred: value::expect assertion failed with message: 'Norse god's name should be Loki'"
```

This method is most conveniently used when chaining methods to make assertions about the data before it reaches the end of the chain.

```surql
"My name is Billy1980"
    .lowercase()
    .split(' ')
    .expect(
        |$words| $words.all(|$word| $word.len() <= 8),
        "Found content greater than 8 characters")
    .join('');
```

```surql title="Output"
"An error occurred: value::expect assertion failed with message: 'Found content greater than 8 characters'"
```

As closures in SurrealDB take ownership of the values of their arguments, this function clones the original value in order to return it. It is thus only recommended to use when debugging or when the assertion is a simple one.

## `value::patch`

The `value::patch` function applies an array of [JSON Patch](https://jsonpatch.com/) operations to a value. Patches produced by [`value::diff()`](/docs/reference/query-language/functions/database-functions/value.md#valuediff) round-trip correctly, including when the value is a top-level scalar (the empty path `""` refers to the root value per RFC 6901).

For `copy` and `move` operations, the `from` pointer must be a **non-empty** JSON Pointer; an empty `from` is rejected at parse time.

```surql title="API DEFINITION"
value::patch(value, $patch: array<object>) -> value
```

```surql
LET $company = {
    company: 'SurrealDB',
    latest_version: '1.5.4'
};

$company.patch([{
		'op': 'replace',
		'path': 'latest_version',
		'value': '3.0'
}]);
```

```surql title="Output"
{
	company: 'SurrealDB',
	version: '3.0'
}
```

---

Source: https://surrealdb.com/docs/reference/query-language/functions/database-functions/vector

# Vector

A collection of essential vector operations that provide foundational functionality for numerical computation, machine learning, and data analysis.

These functions perform vector operations used in numerical computation, machine learning, and data analysis. They include distance measurements, similarity coefficients, and other operations on vectors.

<table>
  <thead>
    <tr>
      <th scope="col">Function</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectoradd"><code>vector::add()</code></a></td>
      <td scope="row" data-label="Description">Performs element-wise addition of two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorangle"><code>vector::angle()</code></a></td>
      <td scope="row" data-label="Description">Computes the angle between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorcross"><code>vector::cross()</code></a></td>
      <td scope="row" data-label="Description">Computes the cross product of two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectordivide"><code>vector::divide()</code></a></td>
      <td scope="row" data-label="Description">Performs element-wise division between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectordot"><code>vector::dot()</code></a></td>
      <td scope="row" data-label="Description">Computes the dot product of two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectormagnitude"><code>vector::magnitude()</code></a></td>
      <td scope="row" data-label="Description">Computes the magnitude (or length) of a vector</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectormultiply"><code>vector::multiply()</code></a></td>
      <td scope="row" data-label="Description">Performs element-wise multiplication of two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectornormalize"><code>vector::normalize()</code></a></td>
      <td scope="row" data-label="Description">Computes the normalisation of a vector</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorproject"><code>vector::project()</code></a></td>
      <td scope="row" data-label="Description">Computes the projection of one vector onto another</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorscale"><code>vector::scale()</code></a></td>
      <td scope="row" data-label="Description">Multiplies each item in a vector</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorsum"><code>vector::sum()</code></a></td>
      <td scope="row" data-label="Description">Sums vectors element-wise, as a scalar or as a grouped aggregate</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorsubtract"><code>vector::subtract()</code></a></td>
      <td scope="row" data-label="Description">Performs element-wise subtraction between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectordistancechebyshev"><code>vector::distance::chebyshev()</code></a></td>
      <td scope="row" data-label="Description">Computes the Chebyshev distance</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectordistanceeuclidean"><code>vector::distance::euclidean()</code></a></td>
      <td scope="row" data-label="Description">Computes the Euclidean distance between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectordistancehamming"><code>vector::distance::hamming()</code></a></td>
      <td scope="row" data-label="Description">Computes the Hamming distance between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectordistanceknn"><code>vector::distance::knn()</code></a></td>
      <td scope="row" data-label="Description">Returns the distance computed during the query</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectordistancemanhattan"><code>vector::distance::manhattan()</code></a></td>
      <td scope="row" data-label="Description">Computes the Manhattan distance between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectordistancemahalanobis"><code>vector::distance::mahalanobis()</code></a></td>
      <td scope="row" data-label="Description">Computes the Mahalanobis distance between two vectors given a covariance matrix</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectordistanceminkowski"><code>vector::distance::minkowski()</code></a></td>
      <td scope="row" data-label="Description">Computes the Minkowski distance between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorsimilaritycosine"><code>vector::similarity::cosine()</code></a></td>
      <td scope="row" data-label="Description">Computes the Cosine similarity between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorsimilarityjaccard"><code>vector::similarity::jaccard()</code></a></td>
      <td scope="row" data-label="Description">Computes the Jaccard similarity between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorsimilaritypearson"><code>vector::similarity::pearson()</code></a></td>
      <td scope="row" data-label="Description">Computes the Pearson correlation coefficient between two vectors</td>
    </tr>
    <tr>
      <td scope="row" data-label="Function"><a href="#vectorsimilarityspearman"><code>vector::similarity::spearman()</code></a></td>
      <td scope="row" data-label="Description">Computes the Spearman rank correlation coefficient between two vectors</td>
    </tr>
  </tbody>
</table>

## `vector::add`

The `vector::add` function performs element-wise addition of two vectors, where each element in the first vector is added to the corresponding element in the second vector.

```surql title="API DEFINITION"
vector::add(array, $other: array) -> array
```
The following example shows this function, and its output:

```surql
vector::add([1, 2, 3], [1, 2, 3]);
```

```surql title="Output"
[2, 4, 6]
```

<br />

## `vector::angle`

The `vector::angle` function computes the angle between two vectors, providing a measure of the orientation difference between them.

```surql title="API DEFINITION"
vector::angle(array, $other: array) -> number
```

Both vectors must have the same dimension and a non-zero magnitude. A zero-magnitude input (including `[]`) returns an error.

The following example shows this function, and its output:

```surql
vector::angle([5, 10, 15], [10, 5, 20]);
```

```surql title="Output"
0.36774908225917935f
```

<br />

## `vector::cross`

The `vector::cross` function computes the cross product of two vectors, which results in a vector that is orthogonal (perpendicular) to the plane containing the original vectors.

```surql title="API DEFINITION"
vector::cross(array, $other: array) -> array
```

The following example shows this function, and its output:

```surql
vector::cross([1, 2, 3], [4, 5, 6]);

[-3, 6, -3]
```

<br />

## `vector::divide`

The `vector::divide` function performs element-wise division between two vectors, where each element in the first vector is divided by the corresponding element in the second vector.

```surql title="API DEFINITION"
vector::divide(array, $other: array) -> array
```

The divisor vector must not contain a zero. Division by zero returns an error instead of `NaN`.

The following example shows this function, and its output:

```surql
vector::divide([4, 6], [2, 3]);
```

```surql title="Output"
[2, 2]
```

<br />

## `vector::dot`

The `vector::dot` function computes the dot product of two vectors, which is the sum of the products of the corresponding entries of the two sequences of numbers.

```surql title="API DEFINITION"
vector::dot(array, $other: array) -> number
```

The following example shows this function, and its output:

```surql
vector::dot([1, 2, 3], [1, 2, 3]);
```

```surql title="Output"
14
```

<br />

## `vector::magnitude`

The `vector::magnitude` function computes the magnitude (or length) of a vector, providing a measure of the size of the vector in multi-dimensional space.

```surql title="API DEFINITION"
vector::magnitude(array) -> number
```

An empty vector returns `0`.

The following example shows this function, and its output:

```surql
vector::magnitude([ 1, 2, 3, 3, 3, 4, 5 ]);
```

```surql title="Output"
8.54400374531753f
```

<br />

## `vector::multiply`

The `vector::multiply` function performs element-wise multiplication of two vectors, where each element in the first vector is multiplied by the corresponding element in the second vector.

```surql title="API DEFINITION"
vector::multiply(array, $other: array) -> array
```
The following example shows this function, and its output:

```surql
vector::multiply([1, 2, 3], [1, 2, 3]);
```

```surql title="Output"
[1, 4, 9]
```

<br />

## `vector::normalize`

The `vector::normalize` function computes the normalisation of a vector, transforming it to a unit vector (a vector of length 1) that maintains the original direction.

```surql title="API DEFINITION"
vector::normalize(array) -> array
```

The vector must have a non-zero magnitude. A zero vector (including `[]`) returns an error.

The following example shows this function, and its output:

```surql
vector::normalize([ 4, 3 ]);
```

```surql title="Output"
[0.8f, 0.6f]
```

<br />

## `vector::project`

The `vector::project` function computes the projection of one vector onto another, providing a measure of the shadow of one vector on the other. The projection is obtained by multiplying the magnitude of the given vectors with the cosecant of the angle between the two vectors.

```surql title="API DEFINITION"
vector::project(array, $other: array) -> array
```

The second vector must have a non-zero magnitude. Projecting onto a zero vector (including `[]`) returns an error.

The following example shows this function, and its output:

```surql
vector::project([1, 2, 3], [4, 5, 6]);
```

```surql title="Output"
[1.6623376623376624f, 2.077922077922078f, 2.4935064935064934f]
```

<br />

## `vector::scale`

The `vector::scale` function multiplies each item in a vector by a number.

```surql title="API DEFINITION"
vector::scale(array, $other: number) -> array
```

The following example shows this function, and its output:

```surql
vector::scale([3, 1, 5, -3, 7, 2], 5);
```

```surql title="Output"
[15,	5, 25, -15, 35, 10]
```

<br />

## `vector::sum`

_(since v3.3.0)_

The `vector::sum` function adds vectors element-wise. It is available in two forms, matching other aggregates such as [`math::sum`](/docs/reference/query-language/functions/database-functions/math.md#mathsum):

- **Scalar** - sum a collection of vectors you already hold as `array<array<number>>`
- **Aggregate** - fold a vector-valued expression across the rows of a `GROUP BY` (or `GROUP ALL`)

```surql title="API DEFINITION"
vector::sum(array<array<number>>) -> array | none
```

An empty collection returns `NONE` (there is no dimension to produce a zero vector for). Two empty vectors sum to `[]`. Vectors in the collection must share the same dimension; a mismatch returns an error in the scalar form.

The following example shows the scalar form, and its output:

```surql
vector::sum([[1, 2, 3], [4, 5, 6]]);
//- [5, 7, 9]

vector::sum([[1, 2], [3, 4], [5, 6]]);
//- [9, 12]

vector::sum([[1.5, 2.5], [1, 1]]);
//- [2.5f, 3.5f]

vector::sum([[], []]);
//- []

vector::sum([]);
//- NONE

[[1, 2], [3, 4]].vector_sum();
//- [4, 6]
```

### Aggregate form

As an aggregate, `vector::sum` keeps one running vector per group, so state stays O(dimension) rather than materialising every embedding with `array::group` then folding.

On the streaming path:

- Rows whose vector is `NONE` or `NULL` are skipped (same idea as a missing field for `math::sum`)
- A group that never sees a vector yields `NONE`
- A group whose vectors disagree on dimension yields `NULL`, not a partial sum

Together with [`vector::scale`](#vectorscale) and [`math::sum`](/docs/reference/query-language/functions/database-functions/math.md#mathsum), a weighted centroid is one query:

```surql
CREATE engagement:1 SET user = 'alice', weight = 2, embedding = [1, 0, 0] RETURN NONE;
CREATE engagement:2 SET user = 'alice', weight = 3, embedding = [0, 1, 0] RETURN NONE;
CREATE engagement:3 SET user = 'bob', weight = 1, embedding = [0, 0, 4] RETURN NONE;

SELECT
	user,
	vector::scale(
		vector::sum(vector::scale(embedding, weight)),
		1.0 / math::sum(weight)
	) AS interest
FROM engagement
GROUP BY user
ORDER BY user;

-- alice → [0.4f, 0.6000000000000001f, 0f]
-- bob   → [0f, 0f, 4f]

SELECT user, vector::sum(embedding) AS total
FROM engagement
GROUP BY user
ORDER BY user;

-- alice → [1, 1, 0]
-- bob   → [0, 0, 4]
```

> [!IMPORTANT]
> Write the reciprocal as a float (`1.0 / math::sum(weight)`). With integer weights, `1 / math::sum(weight)` is integer division and truncates to `0`, which scales the weighted total away entirely. Float weights promote the reciprocal on their own.

<br />

## `vector::subtract`

The `vector::subtract` function performs element-wise subtraction between two vectors, where each element in the second vector is subtracted from the corresponding element in the first vector.

```surql title="API DEFINITION"
vector::subtract(array, $other: array) -> array
```

The following example shows this function, and its output:

```surql
vector::subtract([4, 5, 6], [3, 2, 1]);
```

```surql title="Output"
[1, 3, 5]
```

<br />

## `vector::distance::chebyshev`

The `vector::distance::chebyshev` function computes the Chebyshev distance (also known as maximum value distance) between two vectors, which is the greatest of their differences along any coordinate dimension.

```surql title="API DEFINITION"
vector::distance::chebyshev(array, $other: array) -> number
```

Two empty vectors return `0`.

The following example shows this function, and its output:

```surql
vector::distance::chebyshev([2, 4, 5, 3, 8, 2], [3, 1, 5, -3, 7, 2]);
```

```surql title="Output"
6f
```

<br />

## `vector::distance::euclidean`

The `vector::distance::euclidean` function computes the Euclidean distance between two vectors, providing a measure of the straight-line distance between two points in a multi-dimensional space.

```surql title="API DEFINITION"
vector::distance::euclidean(array, $other: array) -> number
```

The following example shows this function, and its output:

```surql
vector::distance::euclidean([10, 50, 200], [400, 100, 20]);
```

```surql title="Output"
432.43496620879307f
```

<br />

## `vector::distance::hamming`

The `vector::distance::hamming` function computes the Hamming distance between two vectors, measuring the minimum number of substitutions required to change one vector into the other, useful for comparing strings or codes.

```surql title="API DEFINITION"
vector::distance::hamming(array, $other: array) -> number
```

The following example shows this function, and its output:

```surql
vector::distance::hamming([1, 2, 2], [1, 2, 3]);
```

```surql title="Output"
1
```

<br />

## `vector::distance::knn`

The `vector::distance::knn` function returns the distance computed during the query by the Knn operator (avoiding recomputation).

```surql title="API DEFINITION"
vector::distance::knn() -> number
```

The following example shows this function, and its output, when used in a [`SELECT`](/docs/reference/query-language/statements/select.md) statement:

```surql
CREATE pts:1 SET point = [1,2,3,4];
CREATE pts:2 SET point = [4,5,6,7];
CREATE pts:3 SET point = [8,9,10,11];
SELECT id, vector::distance::knn() AS dist FROM pts
  WHERE point <|2,EUCLIDEAN|> [2,3,4,5];
```

```surql title="Output"
[
	{
		id: pts:1,
		dist: 2f
	},
	{
		id: pts:2,
		dist: 4f
	}
]
```

<br />

## `vector::distance::manhattan`

The `vector::distance::manhattan`  function computes the Manhattan distance (also known as the L1 norm or Taxicab geometry) between two vectors, which is the sum of the absolute differences of their corresponding elements.

```surql title="API DEFINITION"
vector::distance::manhattan(array, $other: array) -> number
```

The following example shows this function, and its output:

```surql
vector::distance::manhattan([10, 20, 15, 10, 5], [12, 24, 18, 8, 7]);
```

```surql title="Output"
13
```

<br />

## `vector::distance::mahalanobis`

_(since v3.3.0)_

The `vector::distance::mahalanobis` function computes the [Mahalanobis distance](https://en.wikipedia.org/wiki/Mahalanobis_distance) between two vectors given a covariance matrix. Unlike Euclidean distance, it scales differences by the inverse of the covariance, so correlated dimensions are not double counted.

```surql title="API DEFINITION"
vector::distance::mahalanobis(array, $other: array, $covariance: array<array<number>>) -> number
```

The two vectors must share the same non-zero dimension. The covariance argument must be a square matrix of that dimension and must be symmetric positive-definite (validated via Cholesky decomposition). When the covariance is the identity matrix, the result matches [`vector::distance::euclidean`](#vectordistanceeuclidean).

The following example shows this function, and its output:

```surql
-- An identity covariance matrix gives the same result as euclidean
vector::distance::mahalanobis([1, 2], [3, 4], [[1, 0], [0, 1]]);
//- 2.8284271247461903f

vector::distance::mahalanobis([1, 2], [3, 4], [[2, 1], [1, 2]]);
//- 1.632993161855452f

vector::distance::mahalanobis([1, 2], [1, 2], [[2, 1], [1, 2]]);
//- 0f
```

A non-square matrix, a matrix that is not symmetric positive-definite, empty vectors, or a dimension mismatch returns an error.

<br />

## `vector::distance::minkowski`

The `vector::distance::minkowski` function computes the Minkowski distance between two vectors, a generalization of other distance metrics such as Euclidean and Manhattan when parameterised with different values of p.

```surql title="API DEFINITION"
vector::distance::minkowski(array, $other: array, $p_value: number) -> number
```

The following example shows this function, and its output:

```surql
vector::distance::minkowski([10, 20, 15, 10, 5], [12, 24, 18, 8, 7], 3);
```

```surql title="Output"
4.862944131094279f
```

<br />

## `vector::similarity::cosine`

The `vector::similarity::cosine` function computes the Cosine similarity between two vectors, indicating the cosine of the angle between them, which is a measure of how closely two vectors are oriented to each other.

```surql title="API DEFINITION"
vector::similarity::cosine(array, $other: array) -> number
```

The following example shows this function, and its output:

```surql
vector::similarity::cosine([10, 50, 200], [400, 100, 20]);
```

```surql title="Output"
0.15258215962441316f
```

<br />

## `vector::similarity::jaccard`

The `vector::similarity::jaccard` function computes the Jaccard similarity between two vectors, treating each vector as a set of numbers (intersection size divided by union size). Duplicate values in a vector count once.

```surql title="API DEFINITION"
vector::similarity::jaccard(array, $other: array) -> number
```

Two empty vectors return `1` (both sets are empty).

The following example shows this function, and its output:

```surql
vector::similarity::jaccard([0,1,2,5,6], [0,2,3,4,5,7,9]);
//- 0.3333333333333333f

-- The sets are {1,2} and {2}: one item in the intersection, two in the union
vector::similarity::jaccard([1, 2], [2, 2]);
//- 0.5f
```

<br />

## `vector::similarity::pearson`

The `vector::similarity::pearson` function computes the Pearson correlation coefficient between two vectors, reflecting the degree of linear relationship between them.

```surql title="API DEFINITION"
vector::similarity::pearson(array, array) -> number
```

Both vectors must have the same dimension of at least 2, and neither may have zero variance.

The following example shows this function, and its output:

```surql
vector::similarity::pearson([1,2,3], [1,5,7]);
```

```surql title="Output"
0.9819805060619659f
```

<br />

## `vector::similarity::spearman`

_(since v3.3.0)_

The `vector::similarity::spearman` function computes the [Spearman rank correlation](https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient) between two vectors: each vector is converted to average ranks (ties share a rank), then Pearson correlation is applied to those ranks.

```surql title="API DEFINITION"
vector::similarity::spearman(array, $other: array) -> number
```

Both vectors must have the same dimension of at least 2, and neither may have zero variance after ranking (a constant vector returns an error).

The following example shows this function, and its output:

```surql
vector::similarity::spearman([1, 2, 3], [1, 10, 100]);
//- 1f

vector::similarity::spearman([1, 2, 3], [3, 2, 1]);
//- -1f

-- Ties use average ranks
vector::similarity::spearman([1, 2, 2, 3], [1, 2, 3, 4]);
//- 0.9486832980505138f
```

<br />

<br /><br />

---

Source: https://surrealdb.com/docs/reference/query-language/functions/ml-functions/functions

# ML functions

These functions can be used when calculating outputs from a trained machine learning model that has been uploaded to the database.

These functions can be used when calculating outputs from a trained machine learning model that has been uploaded to the database.

<table>
	<thead>
		<tr>
			<th scope="col">Function</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Function">
				<a href="#mlname-of-modelversion">
					<code>
						ml::name-of-model&lt;version&gt;()
					</code>
				</a>
			</td>
			<td scope="row" data-label="Description">Computes a value from a trained machine learning model</td>
		</tr>
	</tbody>
</table>

## `ml::name-of-model<version>()`

Once a model has been uploaded to the database, it can be called with inputs to produce a calculation from the trained ML model. A basic raw computation uses the following call:

```surql title="API DEFINITION"
ml::house-price-prediction<0.0.1>(500.0, 1.0);
```
In the above example, the model being called is `house-price-prediction` with the version `0.0.1`, and it is passed a raw vector of `[ [500.0, 1.0] ]`. The name, version and inputs vary from model to model. The name and version of the model are defined in the `.surml` file that is uploaded to the database. A "buffered compute" can also be performed with the code below:

```surql title="API DEFINITION"
ml::house-price-prediction<0.0.1>({squarefoot: 500.0, num_floors: 1.0});
```
Here, the key mappings in the header of the uploaded `.surml` file are used to map the fields defined in the object passed into the `ml::` function in the correct order. If there are any normalisation parameters in the header of the `.surml` file, these will also be applied.

The following example shows this function, and its output:

```surql 
ml::house-price-prediction<0.0.1>({squarefoot: 500.0, num_floors: 1.0});

250000
```

As ML is integrated into SurrealQL, the `ml::` function can be used to infer entire fields. The following example of house prices begins by defining a basic table:

```surql
CREATE house_listing SET squarefoot_col = 500.0, num_floors_col = 1.0;
CREATE house_listing SET squarefoot_col = 1000.0, num_floors_col = 2.0;
CREATE house_listing SET squarefoot_col = 1500.0, num_floors_col = 3.0;
```

The following query then returns all the records with the imputed price prediction:

```surql
SELECT 
	*, 
	ml::house-price-prediction<0.0.1>({ squarefoot: squarefoot_col, num_floors: num_floors_col }) AS price_prediction 
FROM house_listing;
```

This statement gives the following result:

```json
[
	{
		"id": "house_listing:7bo0f35tl4hpx5bymq5d",
		"num_floors_col": 3,
		"price_prediction": 406534.75,
		"squarefoot_col": 1500
	},
	{
		"id": "house_listing:8k2ttvhp2vh8v7skwyie",
		"num_floors_col": 2,
		"price_prediction": 291870.5,
		"squarefoot_col": 1000
	},
	{
		"id": "house_listing:vnlv3nzr21oi5o23kydw",
		"num_floors_col": 1,
		"price_prediction": 177206.21875,
		"squarefoot_col": 500
	}
]
```

We can see that our price prediction is calculated in the query. We can build on the previous surql to filter based on the computed
price prediction with the surql below:

```surql
SELECT * FROM (
		SELECT 
			*, 
			ml::house-price-prediction<0.0.1>({ squarefoot: squarefoot_col, num_floors: num_floors_col }) AS price_prediction 
		FROM house_listing
	) 
	WHERE price_prediction > 177206.21875;
```

This gives us the following result:

```json
[
	{
		"id": "house_listing:7bo0f35tl4hpx5bymq5d",
		"num_floors_col": 3,
		"price_prediction": 406534.75,
		"squarefoot_col": 1500
	},
	{
		"id": "house_listing:8k2ttvhp2vh8v7skwyie",
		"num_floors_col": 2,
		"price_prediction": 291870.5,
		"squarefoot_col": 1000
	}
]
```

<br /><br />

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/casting

# Casting

In the SurrealDB type system, values can be converted to other values efficiently.

In the SurrealDB type system, values can be converted to other values efficiently. This is useful if input is specified in a query which must be of a certain type, or if a user may have provided a parameter with an incorrect type.

<table>
    <thead>
        <tr>
            <th scope="col">Type</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#array"><code>&lt;array&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into an array
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#arrayt"><code>&lt;array&lt;T&gt;&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into an array of <code>T</code> (some indicated type)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#bool"><code>&lt;bool&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a boolean
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#datetime"><code>&lt;datetime&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a datetime
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#decimal"><code>&lt;decimal&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a decimal
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#duration"><code>&lt;duration&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a duration
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#float"><code>&lt;float&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a float
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#int"><code>&lt;int&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a int
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#number"><code>&lt;number&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a decimal
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#record"><code>&lt;record&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a record
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#recordt"><code>&lt;record&lt;T&gt;&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a record of <code>T</code> (some indicated type)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#set-and-sett"><code>&lt;set&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a set
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#string"><code>&lt;string&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a string
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#regex"><code>&lt;regex&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a regular expression
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="#uuid"><code>&lt;uuid&gt;</code></a>
            </td>
            <td scope="row" data-label="Description">
                Casts the subsequent value into a UUID
            </td>
        </tr>
    </tbody>
</table>

## `<array>`

The `<array>` casting function converts a range into an array.

```surql
<array>1..=3;
```

```surql title="Output"
[1, 2, 3]
```

## `<array<T>>`

The `<array<T>>` casting function converts a value into an array of the specified type.

>[!NOTE]
>When using this casting function, the value must be an array and each element in the array will be cast to the specified type.

```surql
<array<int>>["42", "314", "271", "137", "141"];
```

```surql title="Output"
[42, 314, 271, 137, 141]
```

```surql
<array<string>> [42, 314, 271, 137, 141];
```

```surql title="Output"
['42', '314', '271', '137', '141']
```

A cast into an array of more than one possible type can also be used. In this case, the cast will attempt to cast into the possible types in order. As such, the `string` in the first query below will be cast into a `datetime` but not in the second.

```surql
[
  <array<datetime|string>>["2020-09-09", "21 Jan 2020"],
  <array<string|datetime>>["2020-09-09", "21 Jan 2020"]
];
```

```surql title="Output"
[
	[
		d'2020-09-09T00:00:00Z',
		'21 Jan 2020'
	],
	[
		'2020-09-09',
		'21 Jan 2020'
	]
]
```

An example of even more complex casting which attempts to cast each item in the input array into a `record<user>`, then `record<person>`, then `array<record<user>>`, and finally `string`.

```surql
<array<record<user | person> | array<record<user>> | string>> [
	'person:one',
	'user:two',
	[
		'user:three',
		'user:four'
	],
	'not_a_person_or_user'
];
```

```surql title="Output"
[
	person:one,
	user:two,
	[
		user:three,
		user:four
	],
	'not_a_person_or_user'
]
```

## `<bool>`

The `<bool` casting function converts a value into a boolean.

```surql
<bool>"true";
```

```surql title="Output"
true
```

```surql
<bool>"false";
```

```surql title="Output"
false
```

Only a boolean, or the lowercase string `"true"` or `"false"`, converts. Any other value is an error, including the strings `"TRUE"`, `"yes"` and `"1"`, the numbers `1` and `0`, `NONE`, `NULL` and an empty array.

```surql
<bool>1;
```

```surql title="Error output"
Could not cast into `bool` using input `1`
```

To convert a value by its [truthiness](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness) instead, use the `!!` operator. Note that `!!'false'` is `true`, because a non-empty string is truthy:

```surql
!!0;
//- false

!!1;
//- true

!!'false';
//- true
```

To accept other spellings of a boolean, normalise the string before casting, for example with `<bool>string::lowercase('TRUE')`, which gives `true`.

## `<datetime>`

The `<datetime>` casting function converts a value into a datetime.

```surql
<datetime>"2025-06-07";
```

```surql title="Output"
d'2025-06-07T00:00:00Z'
```

## `<decimal>`

The `<decimal>` casting function converts a value into a decimal which allows for 128 bits of precision.

```surql
<decimal>"13.5729484672938472938410938456";
-- Output:
13.572948467293847293841093846dec
```

Decimal casting should generally not be used to convert from floats with a large number of digits after the decimal point, because the input to the right will first be turned into a less precise float before the cast is performed.

```surql
<decimal>13.572948467293847293841093845679289;
-- Output:
13.57294846729385dec

<decimal>1.193847193847193847193487E11;
-- Output:
119384719384.7194dec
```

In this case, the `dec` suffix is preferable as it will instruct the database to treat the **input** as a decimal, rather than create a float to then cast into a decimal.

```surql
13.572948467293847293841093845679289dec;
-- Output:
13.572948467293847293841093846dec

1.193847193847193847193487E11dec;
-- Output:
1.193847193847193847193487E11dec;
```

## `<duration>`

The `<duration>` casting function converts a value into a duration.

```surql
<duration>"1h30m";
```

```surql title="Output"
1h30m
```

<br />

## `<float>`

The `<float>` casting function converts a value into a floating point number. Floating point numbers by nature have a limited amount of precision.

```surql
<float>13.572948467293847293841093845679289;
```

```surql title="Output"
13.572948467293847f
```

```surql
<float>"13.572948467293847293841093845679289";
```

```surql title="Output"
13.572948467293847f
```

## `<int>`

The `<int>` casting function converts a value into an integer.

```surql
<int>53;
```

```surql title="Output"
53
```

A float or decimal converts only when it has no fractional part, and a string converts only when it is written as an integer. Anything else is an error, including `<int> 12.34`, `<int> "4.0"`, a string with spaces around the number, and a boolean:

```surql
<int> 12.0;
//- 12

<int> "42";
//- 42

<int> 12.34;
//- 'Could not cast into `int` using input `12.34f`'
```

To convert a number with a fractional part, round it first with [`math::round()`](/docs/reference/query-language/functions/database-functions/math.md#mathround), [`math::floor()`](/docs/reference/query-language/functions/database-functions/math.md#mathfloor) or [`math::ceil()`](/docs/reference/query-language/functions/database-functions/math.md#mathceil):

```surql
<int> math::round(12.34);
```

```surql title="Output"
12
```

## `<number>`

The `<number>` casting function converts a value into a `number`.

```surql
<number>13.572948467293847293841093845679289;
-- Output:
"13.572948467293847293841093845679289"
```

```surql
<number>"13.572948467293847293841093845679289";
-- Output:
"13.572948467293847293841093845679289"
```

```surql
<number>1.193847193847193847193487E11;
-- Output:
"119384719384.7193847193487"
```

## `<record>`

The `<record>` casting function converts a value into a record.

Keep in mind when using this casting function that if the equivalent record id does not exist, it will not return anything.

```surql
SELECT id FROM <record>"person:hrebrffwm4sr2yifglta";
```

```surql title="Output"
{ id: person:hrebrffwm4sr2yifglta }
```

## `<record<T>>`

The `<record<T>>` casting function converts a value into a record.

Keep in mind when using this casting function that if the equivalent record id does not exist, it will not return anything.

```surql
SELECT id FROM <record>"person:hrebrffwm4sr2yifglta";
-- Output:
{ id: person:hrebrffwm4sr2yifglta }
```

A cast into a number of possible record types can also be used.

```surql
[
  <record<user|person>>"user:one",
  <array<record<user|person>>>["person:one", "user:two"]
];
```

```surql title="Output"
[
	user:one,
	[
		person:one,
		user:two
	]
]
```

## `<set>` and `<set<T>>`

The `<set>` casting function converts a value into a set.

```surql
[
  <set<datetime|string>>["2020-09-09", "21 Jan 2020"],
  <set<string|datetime>>["2020-09-09", "21 Jan 2020"]
];
```

```surql title="Output"
[
	[
		d'2020-09-09T00:00:00Z',
		'21 Jan 2020'
	],
	[
		'2020-09-09',
		'21 Jan 2020'
	]
]
```

## `<string>`

The `<string>` casting function converts a value into a string.

```surql
<string>true;
```

```surql title="Output"
'true'
```

```surql
<string>1.3463;
```

```surql title="Output"
'1.3463f'
```

```surql
<string>false;
```

```surql title="Output"
'false'
```

## `<regex>`

The `<regex>` casting function converts a value into a regular expression.

```surql
<regex> "a|b" = "a";
```

```surql title="Output"
true
```

```surql
<regex> "a|b" = "c";
```

```surql title="Output"
false
```

## `<uuid>`

The `<uuid>` casting function converts a value into a UUID.

```surql
SELECT id FROM <uuid> "a8f30d8b-db67-47ec-8b38-ef703e05ad1b";
-- Output:
[ u'a8f30d8b-db67-47ec-8b38-ef703e05ad1b' ]
```

## General notes on casting

### Syntax and order

As the parser ignores spaces and new lines, casting syntax can include spaces or new lines as desired.

```surql
-- SurrealDB Studio formatted syntax
 <array<bool | string | float>> [
	'9.1',
	'true',
	15h
];

-- Maybe someone's preferred syntax?
<array
        <bool | string | float>
      >
[ '9.1', 'true', 15h ];
```

When more than one cast type is specified, SurrealDB will attempt to convert into the type in the order specified. In the example above, while the input `'9.1'` could have been converted to a float, the type `string` comes first in the cast syntax and thus `'9.1'` remains as a string.

```surql title="Output"
[
	'9.1',
	true,
	'15h'
]
```

### Casting vs. affixes

SurrealDB uses a number of affixes to force the parser to treat an input as a certain type instead of another. These affixes may seem at first glance to be identical to casts, as the following queries show.

```surql
-- All return a record person:one
r"person:one";
<record>"person:one";
<record<person>>"person:one";
-- Returns a string 'person:one'
'person:one';

-- Both return a decimal 98dec
98dec;
<decimal>98;

-- Returns an int 98
98;
```

However, casts and affixes work in different ways:

* A cast is a way to convert from one type into another.
* An affix is an instruction to the parser to treat an input as a certain type.

These differences become clear when working with input that is less than ideal or does not work with a certain type. For example, floats by nature become imprecise after a certain number of digits.

```surql
[
  8.888,
  8.8888888888888888
];
```

```surql title="Output"
[
	8.888f,
	8.88888888888889f
]
```

In this case, a `decimal` can be used which will allow a greater number of digits after the decimal point. However, casting the above numbers into a `decimal` will result in the same inaccurate output.

```surql
[
	<decimal>8.888,
	<decimal>8.888888888888888
];
```

```surql title="Output"
[
	8.888dec,
	8.88888888888889dec
]
```

This is because the parser will first treat the number as a float and then cast it into a `decimal`.

However, using the `dec` suffix will inform the parser that the entire input is to be treated as a `decimal` and it will never pass through a stage in which it is a float.

```surql
[
	8.888dec,
	8.888888888888888dec
];
```

```surql title="Output"
[
	8.888dec,
	8.888888888888888dec
]
```

Similarly, an attempt to cast a number that is too large for an `int` into a `decimal` will not work, as the parser will first attempt to handle the number on the right before moving on to the cast.

```surql
<decimal>9999999999999999999;
```

```surql title="Output"
'Failed to parse number: number cannot fit within a 64bit signed integer'
```

However, if the same number is followed by the `dec` suffix, the parser will be aware that the input is meant to be treated as a `decimal` from the outset and the query will succeed.

```surql
9999999999999999999dec;
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/comments

# Comments

In SurrealQL, comments can be written in a number of different ways.

In SurrealQL, comments can be written in a number of different ways.

```surql
/*
In SurrealQL, comments can be written as single-line
or multi-line comments, and comments can be used and
interspersed within statements.
*/

SELECT * FROM /* get all users */ user;

# There are a number of ways to use single-line comments
SELECT * FROM user;

// Alternatively using two forward-slash characters
SELECT * FROM user;

-- Another way is to use two dash characters
SELECT * FROM user;
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types

# Data types

SurrealQL allows you to describe data with specific data types. These data types are used to validate data and to generate the appropriate database schema.

SurrealQL allows you to describe data with specific data types. These data types are used to validate data and to generate the appropriate database schema.

## Data types

<table>
    <thead>
        <tr>
            <th scope="col">Type</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Type">
                <code>any</code>
            </td>
            <td scope="row" data-label="Description">
                Use this when you explicitly don't want to specify the field's data type. The field will allow any data type supported by SurrealDB.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`array`](/docs/reference/query-language/language-primitives/data-types/arrays.md)
            </td>
            <td scope="row" data-label="Description">
                An array of items.
                The array type also allows you to define which types can be stored in the array and the required length.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`bool`](/docs/reference/query-language/language-primitives/data-types/booleans.md)
            </td>
            <td scope="row" data-label="Description">
                A value that can be either `true` or `false`.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="/docs/reference/query-language/language-primitives/data-types/bytes.md">[`bytes`](/docs/reference/query-language/language-primitives/data-types/bytes.md)</a>
            </td>
            <td scope="row" data-label="Description">
                Stores a value in a byte array.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)
            </td>
            <td scope="row" data-label="Description">
                An [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) compliant data type that stores a date with time and time zone.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`decimal`](/docs/reference/query-language/language-primitives/data-types/numbers.md#decimal-numbers)
            </td>
            <td scope="row" data-label="Description">
               Data type for storing [decimal floating point](https://en.wikipedia.org/wiki/Decimal128_floating-point_format) numbers.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`duration`](/docs/reference/query-language/language-primitives/data-types/durations.md)
            </td>
            <td scope="row" data-label="Description">
                Store a value representing a length of time. Can be added or subtracted from datetimes or other durations.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`float`](/docs/reference/query-language/language-primitives/data-types/numbers.md#floating-point-numbers)
            </td>
            <td scope="row" data-label="Description">
                Data type for storing [floating point](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) numbers. Larger or extremely precise values should be stored as a decimal.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <a href="/docs/reference/query-language/language-primitives/data-types/geometries.md"><code>geometry</code></a>
            </td>
            <td scope="row" data-label="Description">
                <a href="https://www.rfc-editor.org/rfc/rfc7946" target="_blank" rel="noopener noreferrer" title="Link to RFC 7946">RFC 7946</a> compliant data type for storing geometry in the <a href="https://geojson.org/" target="_blank" rel="noopener noreferrer" title="Link to the GeoJson website">GeoJson format</a>.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`int`](/docs/reference/query-language/language-primitives/data-types/numbers.md#integer-numbers)
            </td>
            <td scope="row" data-label="Description">
                Store a value in a 64 bit signed integer. Values can range between `-9223372036854775808` and `9223372036854775807` (inclusive). Larger values should be stored as a float or a decimal.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`number`](/docs/reference/query-language/language-primitives/data-types/numbers.md)
            </td>
            <td scope="row" data-label="Description">
                Store numbers without specifying the type.
                SurrealDB will detect the type of number and store it using the minimal number of bytes.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`object`](/docs/reference/query-language/language-primitives/data-types/objects.md)
            </td>
            <td scope="row" data-label="Description">
                Store formatted objects containing values of any supported type including nested objects or arrays.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`range`](/docs/reference/query-language/language-primitives/data-types/ranges.md)
            </td>
            <td scope="row" data-label="Description">
                A range of possible values. Lower and upper bounds can be set, in the absence of which the range becomes open-ended. A range of integers can be used in a FOR loop.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`regex`](/docs/reference/query-language/language-primitives/data-types/regex.md)
            </td>
            <td scope="row" data-label="Description">
                A regular expression that can be used for matching strings.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`record`](/docs/reference/query-language/language-primitives/data-types/record-ids.md)
            </td>
            <td scope="row" data-label="Description">
                A record ID. Table names can be added inside angle brackets to restrict to certain table names.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                [`set`](/docs/reference/query-language/language-primitives/data-types/sets.md)
            </td>
            <td scope="row" data-label="Description">
                A set of items.
                The set type also allows you to define which types can be stored in the set and the required length.
                Items are automatically deduplicated and ordered.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
            [`string`](/docs/reference/query-language/language-primitives/data-types/strings.md)
            </td>
            <td scope="row" data-label="Description">
                A value composed of text or text-like characters such as emojis.
            </td>
        </tr>
    </tbody>
</table>

### Examples

Examples of the `geometry` type:

```surql
-- Define a field with a single type
DEFINE FIELD location ON TABLE restaurant TYPE geometry<point>;
-- Define a field with any geometric type
DEFINE FIELD area ON TABLE restaurant TYPE geometry<feature>;
-- Define a field with specific geometric types
DEFINE FIELD area ON TABLE restaurant
    TYPE geometry<polygon|multipolygon|collection>;
```

Examples of the `bytes` type:

```surql
-- Define a field with a single type
DEFINE FIELD image ON TABLE product TYPE bytes;

-- Create a record with a bytes field and set the value
CREATE foo SET value = <bytes>"bar";
```

## Type expressions

Type expressions are not standalone types, but expressions to indicate which types are permitted.

<table>
    <thead>
        <tr>
            <th scope="col">Type</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Type">
                [literal](/docs/reference/query-language/language-primitives/data-types/literals.md)
            </td>
            <td scope="row" data-label="Description">
                A value that may have multiple representations or formats, similar to an enum or a union type. Can be composed of strings, numbers, objects, arrays, or durations.
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">
                <code>option</code>
            </td>
            <td scope="row" data-label="Description">
                Makes types optional and guarantees the field to be either empty (NONE) or some other type. Syntactic sugar for `type_name | NONE`.
            </td>
        </tr>
    </tbody>
</table>

### Examples

Example of an `option` in a schema:

```surql
DEFINE FIELD friends ON TABLE person TYPE option<array<person>>;
```

Example of a literal type:

```surql
DEFINE FIELD error_msg ON TABLE log TYPE 
    { code: 200, message: string } | 
    { code: 404, message: string };
```

As an option is syntactic sugar for `type | NONE`, an option is also simply another type of literal. This field definition is identical to the `option` example above.

```surql
DEFINE FIELD friends ON TABLE person TYPE array<person> | NONE;
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/arrays

# Arrays

An array is a collection of values contained inside square brackets, each of which is stored at a certain index.

An array is a collection of values contained inside `[]` (square brackets), each of which is stored at a certain index. Individual indexes and slices of indexes can be accessed using the same square bracket syntax. The first element of an array can be accessed with `[0]`, and `[$]` for the last.

```surql
-- Return a full array
[1,2,3,4,5];
-- Return the first ("zeroeth") item
[1,2,3,4,5][0];
-- Return the last item
[1,2,3,4,5][$];
-- Return indexes 0 up to and including 2 of an array
[1,2,3,4,5][0..=2];
```

```surql title="Output"
-------- Query 1 --------

[
	1,
	2,
	3,
	4,
	5
]

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

1

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

5

-------- Query 4 --------

[
	1,
	2,
	3
]
```

Arrays are frequently encountered in SurrealDB, as [`SELECT`](/docs/reference/query-language/statements/select.md) statements return an array of values by default unless the `ONLY` keyword is used on an array that contains a single item.

```surql
-- Returns an array with 9 in it
SELECT * FROM 9;
-- Use the `ONLY` clause to return a single item
SELECT * FROM ONLY 9;
-- `ONLY` errors when the array holds more than one item
SELECT * FROM ONLY [1,9];
```

```surql title="Output"
-------- Query 1  --------

[
	9
]

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

9

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

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

Records in SurrealDB can store arrays of values, including arrays within arrays. Arrays can store any value stored within them, and can store different value types within the same array.

```surql
CREATE person SET results = [
	{ score: 76, date: "2017-06-18T08:00:00Z", name: "Algorithmics" },
	{ score: 83,
	  date: "2018-03-21T08:00:00Z",
	  name: "Concurrent Programming" },
	{ score: 69,
	  date: "2018-09-17T08:00:00Z",
	  name: "Advanced Computer Science 101" },
	{ score: 73,
	  date: "2019-04-20T08:00:00Z",
	  name: "Distributed Databases" }
];
```

A required number of items can be specified for an array.

```surql
DEFINE FIELD employees ON TABLE team TYPE array<record<employee>, 5>;
CREATE team:one SET employees = [
	employee:one, 
	employee:two, 
	employee:three, 
	employee:four, 
	employee:five, 
	employee:doesnt_belong
];
```

```surql title="Output"
"Couldn't coerce value for field `employees` of `team:one`:
Expected `array<record<employee>,5>` but found a collection of length
  `6`"
```

## Mapping and filtering on arrays

The `[]` operator after an array can also be used to filter the items inside an array. The parameter `$this` is used to refer to each individual item, while `WHERE` (or its alias `?`, a question mark) is used to set the condition for the item to pass the filter.

```surql
[true, false, true][WHERE $this = true];
```

```surql title="Output"
[true, true]
```

If a `WHERE` or `?` clause finds an item that by itself is not equal to `true` or `false`, it will check the item's [truthiness](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness) to determine whether to pass it on or not.

```surql
[1,2,NONE][? $this];
```

```surql title="Output"
[1, 2]
```

Filtering can be repeated if desired.

```surql
[
    {
        name: "Boston",
        population: NONE,
        first_mayor: "John Phillips"
    },
    {
        name: "Smurfville",
        population: 55,
        first_mayor: "Papa Smurf"
    },
    {
        name: "Harrisburg",
        population: 50183,
        first_mayor: NONE
    }
][WHERE $this.population]
 [WHERE $this.first_mayor];
```

```surql title="Output"
[
	{
		first_mayor: 'Papa Smurf',
		name: 'Smurfville',
		population: 55
	}
]
```

## Filtering and mapping with array functions

SurrealDB also includes a number of methods for arrays that make it easier to filter and map. These methods take a closure (an anonymous function) that works in a similar way to the `$this` parameter above.

Here is an example of the `array::filter()` method being used in contrast to the classic `WHERE` syntax. Note that the parameter name inside the closure is named by the user, so `$val` in the example below could be `$v` or `$some_val` or anything else.

```surql
[1,3,5].filter(|$val| $val > 2);
[1,3,5][WHERE $this > 2];
```

```surql title="Output"
[3,5]
```

Closures in general have inferior performance so `[WHERE ...]` is preferred when filtering on an array. This is because `[WHERE ...]` is evaluated directly, while a closure is invoked once per element, and on an array of tens of thousands the closure takes roughly an order of magnitude longer. That is the case for any method that takes one - `map()` as much as `filter()` - and is unchanged by whether it is written as a method or as `array::filter(...)`, because the cost is the closure rather than the call.

So `[WHERE ...]` suits a plain filter, and a closure suits work that needs one, such as a transformation, a condition that reads something other than the element itself, or a step in a longer chain.

While the [array functions](/docs/reference/query-language/functions/database-functions/array.md) section of the documentation contains the full details of each function, the following examples provide a glimpse into how they are commonly used.

The [`array::map()`](/docs/reference/query-language/functions/database-functions/array.md#arraymap) function provides access to each item in an array, allowing an operation to be performed on it before being passed on.

```surql
[1,2,3].map(|$item| $item + 1);
```

```surql title="Output"
[2,3,4]
```

If desired, a second parameter can be passed in that holds the index of the item.

```surql
[1,2,3]
    .map(|$v, $i| 
    "At index " + <string>$i + " we got a " + <string>$v + "!");
```

```surql title="Output"
[
	'At index 0 we got a 1!',
	'At index 1 we got a 2!',
	'At index 2 we got a 3!'
]
```

## Adding arrays

_(since v3.0.0)_

An array can be added to another array, resulting in a single array consisting of the items of the first followed by those of the second. This is identical to the `array::concat()` function.

```surql
[1,2] + [3,4];
[1,2].concat([3,4]);
```

```surql title="Output"
[1,2,3,4]
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/booleans

# Booleans

Boolean values in SurrealDB can be used to mark whether a field is true or false

A boolean (`bool`) is a primitive type that can be either `true` or `false`.

```surql
CREATE person SET newsletter = false, interested = true;
```

Many SurrealDB operators and functions return booleans.

```surql
SELECT
    name,
    id,
    name = "Billy" AS name_is_billy,
    name.len() > 20 AS name_is_long
FROM
    CREATE person SET name = "Billy";
```

```surql title="Output"
[
	{
		id: person:7j4t4higwb141v1v2xum,
		name: 'Billy',
		name_is_billy: true,
		name_is_long: false
	}
]
```

Boolean values can be written in anycase.

```surql
CREATE person SET 
    newsletter = FALSE,
    interested = True,
    very_interested = trUE;
```

## Booleans in `WHERE` clauses

When performing a query on the database, accessing a record's ID directly or using a [record range](/docs/reference/query-language/language-primitives/data-types/record-ids.md#record-ranges) allows performance to be significantly sped up by avoiding the table scan which a `WHERE` clause on an unindexed field needs.

However, if a `WHERE` clause is unavoidable, performance can still be improved by simplifying the portion after the clause as much as possible. A field that stores a precomputed value, such as a boolean, avoids calling a function on every record. Compare the field with a literal (`is_short = true`) rather than testing it on its own (`WHERE is_short`): a comparison with a literal lets the scan reject a record before decoding it, while a bare field is only tested after the record is decoded.

```surql
DEFINE FIELD data_length ON person VALUE random_data.len();
DEFINE FIELD is_short ON person VALUE random_data.len() < 10;

-- Fill up the database a bit with 10,000 records
CREATE |person:10000|
  SET random_data = rand::string(1000) RETURN NONE;
-- Add one outlier with short random_data
CREATE person:one SET random_data = "HI!" RETURN NONE;

-- Function call + compare operation: slowest
SELECT * FROM person WHERE random_data.len() < 10;
-- Stored number compared with a literal: much faster
SELECT * FROM person WHERE data_length < 10;
-- Stored boolean compared with a literal: also much faster
SELECT * FROM person WHERE is_short = true;
-- Direct record access: almost instantaneous
SELECT * FROM person:one;
```

## Boolean values vs. truthy values

All SurrealQL values are either truthy or not. While seemingly similar to booleans in that `true` is a truthy value and `false` is not, the truthiness of a value extends to all value types and is based on the existence of a concrete value as opposed to empty values, `NONE`, `NULL`, and so on. For more information and examples, see [this page](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/bytes

# Bytes

A value that represents the bytes used ubiquitously in computer hardware.

Bytes represent raw binary data. While most data in SurrealDB is stored as strings or structured values, bytes are useful when working with encoded data such as hashes, binary identifiers, or compact representations.

A `bytes` value is stored inside its record, so it is read and written together with the rest of the record's fields. For whole files such as images or documents, the experimental [files feature](/docs/learn/schema-management/files/working-with-files.md) stores the content in a [bucket](/docs/learn/schema-management/files/buckets.md) held in memory, on disk or in cloud object storage. A record field then holds a file pointer such as `f"images:/cat.jpg"`, and calling `.get()` on the pointer returns the file's content as bytes.

## Casting from strings

Bytes can be created by casting from a string, and are displayed using hexadecimal encoding.

```surql
<bytes>"I am some bytes";
```

```surql title="Output"
b"4920616D20736F6D65206279746573"
```

## Conversion from other types

_(since v2.3.0)_

Conversions can be performed between bytes, strings, and arrays.

```surql
-- array<int> to bytes to string
<string><bytes>[99, 101, 108, 108, 97, 114, 32, 100, 111, 111, 114];
-- string to bytes to array<int>
<array><bytes>"Hobbits";
```

```surql title="Output"
-------- Query --------

'cellar door'

-------- Query --------

[72, 111, 98, 98, 105, 116, 115]
```

## Byte strings

_(since v3.0.0)_

A string preceded by a `b` prefix can be turned into bytes as long as the string represents a hexadecimal value.

```surql
b"486F6262697473";

<string>b"486F6262697473";

<string>b"This won't work though";
```

```surql title="Output"
-------- Query --------

b"486F6262697473";

-------- Query --------

'Hobbits'

-------- Query --------

"There was a problem with the database: Parse error: Unexpected
  character `T` expected hexidecimal digit
 --> [1:11]
  |
1 | <string>b\"This won't work though\";
  |           ^ 
"
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/closures

# Closures

Closures (anonymous functions) in SurrealDB allow you to define small, reusable pieces of logic that can be used throughout your queries.

```syntax title="SurrealQL Syntax"
LET $parameter = |@parameters| @expression;
```

SurrealDB allows you to define anonymous functions. These functions can be used to encapsulate reusable logic and can be called from within your queries. Below are some examples demonstrating their capabilities:

## Basic function definitions

```surql
-- Define an anonymous function that doubles a number
LET $double = |$n: number| $n * 2;
RETURN $double(2);  -- Returns 4

-- Define a function that concatenates two strings
LET $concat = |$a: string, $b: string| $a + $b;
RETURN $concat("Hello, ", "World!");  -- Returns "Hello, World!"
```

```surql
-- Define a function that greets a person
LET $greet = |$name: string| -> string { "Hello, " + $name + "!" };
RETURN $greet("Alice");   -- Returns "Hello, Alice!"
```

## Error handling and type enforcement

You can also enforce type constraints within your functions to prevent type mismatches:

```surql
-- Define a function with a return type
LET $to_upper = |$text: string| -> string { string::uppercase($text) };
RETURN $to_upper("hello");  -- Returns "HELLO"
RETURN $to_upper(123);      -- Error: type mismatch

-- Define a function that accepts only numbers
LET $square = |$num: number| $num * $num;
RETURN $square(4);    -- Returns 16
RETURN $square("4");  -- Error: type mismatch
```

## Closures in functions

Many of SurrealDB's functions allow a closure to be passed in, making it easy to use complex logic on a value or the elements of an array.

The `chain` function which performs an operation on a value before passing it on:

```surql
"Two"
    .replace("Two", "2")
    .chain(|$num| <number>$num * 1000);
```

```surql title="Output"
2000
```

We can see that the input to the `.chain()` method is indeed a closure by creating our own that is assigned to a parameter. This closure can be passed into `.chain()`, returning the same output as above.

```surql
LET $my_func = |$num| <number>$num * 1000;

"Two"
    .replace("Two", "2")
    .chain($my_func);
```

The following example shows a chain of array functions used to remove useless data, followed by a check to see if all items in the array match a certain condition, and then a cast into another type. The [`array::filter`](/docs/reference/query-language/functions/database-functions/array.md#arrayfilter) call in the middle ensures that the [`string::len`](/docs/reference/query-language/functions/database-functions/string.md#stringlen) function that follows is being called on string values.

```surql
[NONE, NONE, "good data", "Also good", "important", NULL]
    .filter(|$v| $v.is_string())
    .all(|$s| $s.len() > 5)
    .chain(|$v| <string>$v);
```

```surql title="Output"
'true'
```

## Closures and writes

Whether a closure can modify database resources depends on your version.

**SurrealDB 3.3 and later**

A closure that writes makes the expression holding it a write, so it can modify database resources like any other statement. This applies wherever the closure is invoked, including closure-taking functions such as `map`, `filter` and `fold`, and it follows a write reached through a function the closure calls.

```surql
-- 1. Create a test table and function
DEFINE TABLE test_table SCHEMAFULL;
DEFINE FIELD name ON test_table TYPE string;

DEFINE FUNCTION fn::test_create($name: string) -> object {
    CREATE test_table CONTENT { name: $name };
    { created: true, name: $name };
};

-- 2. Call the function directly
fn::test_create("direct_call");

-- 3. Call the function inside .map()
LET $names = ["Alice", "Bob", "Charlie"];
$names.map(|$n| fn::test_create($n));
```

```surql title="Output"
[
	{ created: true, name: 'Alice' },
	{ created: true, name: 'Bob' },
	{ created: true, name: 'Charlie' }
]
```

A closure whose body only reads still resolves as a read, so `map`, `filter` and `fold` over a pure closure keep their read-only path.

A `FOR` loop or a `SELECT` over a list remains a good choice where it reads more clearly:

```surql
-- Function called once for each name
SELECT VALUE fn::test_create($this) FROM ["Alice", "Bob", "Charlie"];
```

**Before SurrealDB 3.3**

Closures work inside a read-only context, and cannot be used to modify database resources. This holds even when the write sits inside a function the closure calls.

```surql
-- 1. Create a test table and function
DEFINE TABLE test_table SCHEMAFULL;
DEFINE FIELD name ON test_table TYPE string;

DEFINE FUNCTION fn::test_create($name: string) -> object {
    CREATE test_table CONTENT { name: $name };
    { created: true, name: $name };
};

-- 2. Call the function directly - works
fn::test_create("direct_call");

-- 3. Call the function inside .map() - fails
LET $names = ["Alice", "Bob", "Charlie"];
$names.map(|$n| fn::test_create($n));
```

```surql title="Output"
Error: "Couldn't write to a read only transaction"
```

In many cases, a closure can be substituted by another operation such as a `FOR` loop or a regular `SELECT` statement.

```surql
DEFINE TABLE test_table SCHEMAFULL;
DEFINE FIELD name ON test_table TYPE string;

DEFINE FUNCTION fn::test_create($name: string) -> object {
    CREATE test_table CONTENT { name: $name };
    { created: true, name: $name };
};

-- Function to create a record called for each name
SELECT VALUE fn::test_create($this) FROM ["Alice", "Bob", "Charlie"];
```

## Capturing parameters

_(since v3.0.0)_

The original implementation of closures did not allow them to capture parameters (variables) in their scope. Strictly speaking, this made them simple anonymous functions as closures did not "enclose" anything.

```surql
LET $okay_nums = [1,2,3];

-- Returns [] because $okay_nums not present inside the closure
[1,5,6,7,0].filter(|$n| $n IN $okay_nums);
```

This has since been resolved, allowing a parameter declared outside a closure to be recognized inside it.

```surql
LET $okay_nums = [1,2,3];

[1,5,6,7,0].filter(|$n| $n IN $okay_nums);
```

## Calling a closure stored in an object

An object field that holds a closure can be called with method syntax. `$object.name(arguments)` finds the field `name` on the object and calls the closure in it with the arguments given.

```surql
LET $vat = 0.2;
LET $pricing = {
	with_vat: |$net: number| $net * (1 + $vat),
	discounted: |$net: number, $percent: number| $net - $net * $percent / 100
};

$pricing.with_vat(100);
//- 120f

$pricing.discounted(80, 25);
//- 60
```

Built-in methods are resolved first. When objects have a built-in method of the same name, that method runs and the field is not called. `len` is a built-in method that returns the number of fields in an object, so it takes precedence over a `len` field:

```surql
LET $counter = { len: || 42, total: 7 };
$counter.len();
```

```surql title="Output"
2
```

A name that is neither a built-in method for objects nor a field holding a closure is an error:

```surql
{ x: 1 }.get('x');
```

```surql title="Error output"
"There was a problem running the get() function. no such method found for the object type"
```

From 3.3.0 the closure body runs with the session's namespace and database, so it can query the database, and a body that writes makes the statement a write, so that the change persists as described in [Closures and writes](#closures-and-writes).

```surql
LET $people = {
	create: |$name: string| (CREATE ONLY person SET name = $name),
	total: || count(SELECT * FROM person)
};

$people.create('Aeon');
//- { id: person:ol0tw634oomrg4vfmwm3, name: 'Aeon' }

$people.total();
//- 1
```

The `CREATE` statement in the closure body is wrapped in parentheses, so that the comma after it ends the object field rather than continuing the `SET` clause.

Before 3.3.0, a closure field could not be called this way when its name matched a built-in method of another type, such as `add` or `push` for arrays. The call now falls back to the closure.

> [!WARNING]
> A closure reached through this fallback runs in a read-only context. It can query the database, but a write fails, including a write inside a function that the closure calls. Give a closure that writes a name that is not a built-in method of any type.

```surql
LET $people = {
	add: |$name: string| (CREATE ONLY person SET name = $name)
};

$people.add('Aeon');
```

```surql title="Error output"
"There was a problem with the key-value store: Couldn't write to a read only transaction"
```

## Conclusion

Anonymous functions define small, reusable pieces of logic that can be used throughout your queries.

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/datetimes

# Datetimes

SurrealDB has native support for datetimes with nanosecond precision. SurrealDB is able to parse datetimes from strings.

SurrealDB has native support for datetimes with nanosecond precision. A datetime is written as a string with a `d` prefix, or cast from a string with `<datetime>`, and the string must be in [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) format. A string without the prefix or the cast stays a string, even when its contents look like a datetime, so it cannot be used in datetime arithmetic.

```surql
"2022-07-03T00:00:00Z" + 18y;
//- "Cannot perform addition with 'string' and 'duration'"

d"2022-07-03T00:00:00Z" + 18y;
//- d'2040-06-28T00:00:00Z'
```

Datetimes are represented by and can be created using a `d` prefix in front of a string.

```surql
CREATE event SET time = d"2025-07-03T07:18:52Z";
```

SurrealDB handles all datetimes with nanosecond precision.

```surql
CREATE event SET time = d"2025-07-03T07:18:52.841147Z";
```

A datetime can be created with a timezone, which will be converted and stored as a UTC date.

```surql
d"2025-07-03T07:18:52.841147+02:00";
```

```surql title="Output"
d'2025-07-03T05:18:52.841147Z'
```

A `datetime` can also be created by using `<datetime>` to cast from a string.

With correct input:

```surql
CREATE event SET time = <datetime>"2025-07-03T07:18:52.841147Z";
```

```surql title="Output"
[
    { 
        id: event:jwm8ncmfi30nrxdf24ws, 
        time: d'2025-07-03T07:18:52.841147Z' 
    }
]
```

With incorrect input (missing final Z):

```surql
CREATE event SET time = <datetime>"2025-07-03T07:18:52.841147";
```

```surql title="Output"
"Expected a datetime but cannot convert '2025-07-03T07:18:52.841147' into a datetime"
```

As a convenience, a date containing a year, month and day but no time will also parse correctly as a datetime.

```surql
CREATE event SET time = <datetime>"2024-04-03";
```

```surql title="Output"
[
    { 
        id: event:4t50wjjlne9v8km2qcwq, 
        time: d'2024-04-03T00:00:00Z' 
    }
]
```

## Datetime types in `DEFINE FIELD` statements

Defining a field with a set `datetime` type will ensure that datetimes are properly formatted and not passed on as simple strings.

```surql
DEFINE FIELD time ON event TYPE datetime;
// highlight-next-line
CREATE event SET time = "2025-07-03T07:18:52.841147";
```

```surql title="Output"
"Couldn't coerce value for field `time` of `event:qv8qcjf0w9oowekl36w6`:
Expected `datetime` but found `'2025-07-03T07:18:52.841147'`"
```

The above query will fail because the datetime is not cast as a datetime type. The correct input is:

```surql
DEFINE FIELD time ON event TYPE datetime;
// highlight-next-line
CREATE event SET time = d"2025-07-03T07:18:52.84114Z";
```

```surql title="Output"
[
    { 
        id: event:w2lhv58f7c9z7xo4nqkq, 
        time: d'2025-07-03T07:18:52.841140Z' 
    }
]
```

### Datetime comparison
A datetime can be compared with another using SurrealDB operators.

```surql
d"2025-07-03T07:18:52Z" < d"2025-07-03T07:18:52.84114Z";
```

```surql title="Output"
true
```

## Durations and datetimes

A duration can be used to alter a datetime.

```surql
CREATE event SET time = d"2025-07-03T07:18:52Z" + 2w;
```

```surql title="Output"
[
    { 
        id: event:`9ey7v8r0fd46xblf9dsf`, 
        time: d'2025-07-17T07:18:52Z' 
    }
]
```

Multi-part durations can also be used to modify datetimes.

```surql
CREATE event SET 
    time = d"2025-07-03T07:18:52.841147Z" + 1h30m20s1350ms;
```

```surql title="Output"
[
    { 
        id: event:5uuzy32t48yutxyszi7p, 
        time: d'2025-07-03T08:49:14.191147Z' 
    }
]
```

## Altering datetimes

_(since v3.0.2)_

Each value in a datetime can be set by using one of seven `time::set_` functions. Each of these function names ends with the part of the datetime that is modified, such as `time::set_year()` or `time::set_hour()`.

```surql
d'1970-01-01T00:00:00.000000100Z'.set_year(1914);
-- Output
d'1914-01-01T00:00:00.000000100Z'
```

As these functions do not modify an existing datetime but return a new one, they can be chained one after another.

```surql
d'1970-01-01T00:00:00.000000100Z'
    .set_year(1914)
    .set_month(6)
    .set_day(28);

-- Output
d'1914-06-28T00:00:00.000000100Z'
```

## See also

* [time](/docs/reference/query-language/functions/database-functions/time.md) functions, which enable extracting, altering, rounding, and grouping datetimes into specific time intervals

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/durations

# Durations

SurrealDB has native support for durations with nanosecond precision. SurrealDB is able to parse durations from strings.

A `duration` represents a non-negative period of time.

## Duration units

Durations can be specified in any of the following units:

<table>
    <thead>
    <tr>
        <th scope="col">Unit</th>
        <th scope="col">Description</th>
    </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Type">ns</td>
            <td scope="row" data-label="Description">Nanoseconds</td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">us</td>
            <td scope="row" data-label="Description">Microseconds, alternative: µs</td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">ms</td>
            <td scope="row" data-label="Description">Milliseconds</td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">s</td>
            <td scope="row" data-label="Description">Seconds</td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">m</td>
            <td scope="row" data-label="Description">Minutes</td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">h</td>
            <td scope="row" data-label="Description">Hours</td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">d</td>
            <td scope="row" data-label="Description">Days</td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">w</td>
            <td scope="row" data-label="Description">Weeks</td>
        </tr>
        <tr>
            <td scope="row" data-label="Type">y</td>
            <td scope="row" data-label="Description">Years</td>
        </tr>
    </tbody>
</table>

## Creating and using durations

A duration can be composed of any number of duration units.

```surql
1y40w20h;
```

A duration that contains multiple instances of the same unit type will parse as well, combining lesser units into greater units when a maximum value is reached.

For example, a duration that includes `12h` two times will be evaluated as `1d`.

```surql
1d1d12h12h;
```

```surql title="Output"
3d
```

A duration can also be created by casting a string.

```surql
<duration>"1d1d12h12h";
```

```surql title="Output"
3d
```

A duration can be zero, but cannot be negative.

```surql
0ns;
0d; -- Evaluates to 0ns
```

The maximum possible duration can be accessed via the const [`duration::max`](/docs/reference/query-language/functions/database-functions/duration.md#durationmax), above which a duration cannot be formed.

```surql
duration::max;
//- 584942417355y3w5d7h15s999ms999µs999ns

duration::max + 1ns
//- 'Failed to compute: "584942417355y3w5d7h15s999ms999µs999ns + 1ns", as the operation results in an arithmetic overflow.'
```

Durations can be added to and subtracted from other durations as well as datetimes.

```surql
d'1970-01-01' + 1d;
//- d'1970-01-02T00:00:00Z'

1y - 6w;
46w1d;
```

## Multiplying and dividing durations

_(since v3.0.1)_

A duration can be multiplied and divided by a number.

```surql
1d / 24;
//- 1h

1d * 5.5;
//- 5d12h
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/files

# Files

SurrealDB allows a bucket to be declared locally or globally to work with files.

_(since v3.0.0)_

Files are accessed by a path, which is prefixed with an `f` to differentiate it from a regular string.

Some examples of file pointers:

```surql
f"bucket:/some/key/to/a/file.txt";
```

A file path may only contain alphanumeric characters plus `_`, `-`, `.` and `/`. There is no escape syntax: a key that needs a space or any other character cannot be written as a file pointer literal.

To work with the files that can be accessed through these pointers, use the following:

* A [`DEFINE BUCKET`](/docs/reference/query-language/statements/define/bucket.md) statement to set up the bucket to hold the files
* [Files functions](/docs/reference/query-language/functions/database-functions/file.md) such as `file::put()` and `file::get()`

```surql
DEFINE BUCKET my_bucket BACKEND "memory";
f"my_bucket:/some_file.txt".put("Some text inside");
f"my_bucket:/some_file.txt".get();
<string>f"my_bucket:/some_file.txt".get();
```

```surql title="Output"
-------- Query --------

b"536F6D65207465787420696E73696465"

-------- Query --------

'Some text inside'
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/futures

# Futures

Futures are values which are only computed when the data is selected and returned to the client.

> [!NOTE]
> The `future` type is only available up to SurrealDB 2.x. Since version 3.0.0, it has been replaced by [defined fields using the `COMPUTED` clause](/docs/reference/query-language/statements/define/field.md#restrictions-on-computed-fields). Most examples in this page include an equivalent `COMPUTED` clause to aid in migrating to the new implementation.

Futures are values which are only computed when the data is selected and returned to the client. Futures can be stored inside records, to enable dynamic values which are always calculated when queried.

## Simple futures

Any value or expression can be used inside a future. This value will be dynamically computed on every access to the record.

**Legacy future type**

```surql
CREATE person SET accessed_date = <future> { time::now() };
```

**With COMPUTED clause**

```surql
-- Only used inside a DEFINE FIELD statement
DEFINE FIELD accessed_date ON person COMPUTED time::now();
```

## Futures inside schema definitions

A future can be added to a schema definition as well.

**Legacy future type**

```surql
DEFINE FIELD accessed_at ON TABLE user VALUE <future> { time::now() };

CREATE user:one;
SELECT * FROM ONLY user:one;
-- Sleep for one second
SLEEP 1s;
-- `accessed_at` is a different value now
SELECT * FROM ONLY user:one;
```

**With COMPUTED clause**

```surql
DEFINE FIELD accessed_at ON TABLE user COMPUTED time::now();

CREATE user:one;
SELECT * FROM ONLY user:one;
-- Sleep for one second
SLEEP 1s;
-- `accessed_at` is a different value now
SELECT * FROM ONLY user:one;
```

This differs from a `VALUE` clause which is only calculated when it is modified (created or updated), but is not recalculated during a `SELECT` query which does not modify a record.

```surql
DEFINE FIELD updated ON TABLE user VALUE time::now();

CREATE user:one;
SELECT * FROM ONLY user:one;
-- Sleep for one second
SLEEP 1s;
-- `updated` is still the same
SELECT * FROM ONLY user:one;
```

## Futures depending on statements

If the value of a future is the result of a statement, it must be wrapped in parentheses.

**Legacy future type**

```surql
DEFINE FIELD random_movie
    ON app_screen
    VALUE <future> { 
        (SELECT * FROM ONLY movie ORDER BY RAND() LIMIT 1) 
    };
```

**With COMPUTED clause**

```surql
-- No need for parentheses
DEFINE FIELD random_movie
    ON app_screen
    COMPUTED SELECT * FROM ONLY movie ORDER BY RAND() LIMIT 1;
```

If your statement is wrapped in parentheses, you need to access the fields using the $parent variable.

```surql
DEFINE FIELD OVERWRITE followers
    ON user
    VALUE <future> { 
        (SELECT VALUE count FROM ONLY follower_count 
            WHERE user = $parent.id LIMIT 1) ?? 0
        };
```

Since SurrealDB 3.0.0, a `COMPUTED` field can count related records directly. The following `total_players` field counts the `joined` edges that point at each room, and the count changes as soon as an edge is added or deleted.

```surql
DEFINE TABLE joined TYPE RELATION IN player OUT room;
DEFINE FIELD total_players ON room COMPUTED count(<-joined);

CREATE room:lobby;
CREATE player:one, player:two;
RELATE player:one->joined->room:lobby;
RELATE player:two->joined->room:lobby;
SELECT * FROM room:lobby;
```

```surql title="Output"
[
	{
		id: room:lobby,
		total_players: 2
	}
]
```

Where the related records are not graph edges, a subquery gives the same count: `COMPUTED count(SELECT * FROM joined WHERE out = $parent.id)`.

## Avoiding infinite recursion

When defining a future on a field, be sure to avoid any statements that would cause infinite recursion. In the following example, the `random_friend` field is defined by a statement that uses a `SELECT` statement on all the fields of the same `person` table, one of which will also use the same `future` to compute its value.

```surql
CREATE |person:10| SET name = "Person " + <string>id.id() RETURN NONE;

DEFINE FIELD random_friend
    ON person
    VALUE <future> { 
        (SELECT * FROM ONLY person ORDER BY RAND() LIMIT 1) 
    };

CREATE person;
```

```surql title="Output"
'Reached excessive computation depth due to functions, subqueries, or futures'
```

A `SELECT` query that does not access the field defined by a future will avoid the infinite recursion.

```surql
CREATE |person:10| SET name = "Person " + <string>id.id() RETURN NONE;

DEFINE FIELD random_friend
    ON person
    VALUE <future> { 
        (SELECT VALUE name FROM ONLY person ORDER BY RAND() LIMIT 1) 
    };

CREATE person;
```

```surql title="Output"
[
	{
		id: person:4o973bouhd6xrj8l2x69,
		random_friend: 'Person imoy71qbhnsgjtczybiq'
	}
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/geometries

# Geometries

SurrealDB makes working with GeoJSON easy, with support for Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, and Collection values.

A `geometry` is a type based on the GeoJSON spec that is optimised for working with data pertaining to locations on Earth.

SurrealDB supports GeoJSON, including `Point`, `LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, `MultiPolygon`, and `Collection` values. SurrealQL automatically detects GeoJSON objects converting them into a single data type.

<table>
<thead>
  <tr>
    <th scope="col">Type</th>
    <th scope="col">Description</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td scope="row" data-label="Type"><a href="#point"><code>Point</code></a></td>
    <td scope="row" data-label="Description">A geolocation point with longitude and latitude</td>
  </tr>
  <tr>
    <td scope="row" data-label="Type"><a href="#linestring"><code>LineString</code></a></td>
    <td scope="row" data-label="Description">A GeoJSON LineString value for storing a geometric path</td>
  </tr>
  <tr>
    <td scope="row" data-label="Type"><a href="#polygon"><code>Polygon</code></a></td>
    <td scope="row" data-label="Description">A GeoJSON Polygon value for storing a geometric area</td>
  </tr>
  <tr>
    <td scope="row" data-label="Type"><a href="#multipoint"><code>MultiPoint</code></a></td>
    <td scope="row" data-label="Description">A value which contains multiple geometry points</td>
  </tr>
  <tr>
    <td scope="row" data-label="Type"><a href="#multilinestring"><code>MultiLineString</code></a></td>
    <td scope="row" data-label="Description">A value which contains multiple geometry lines</td>
  </tr>
  <tr>
    <td scope="row" data-label="Type"><a href="#multipolygon"><code>MultiPolygon</code></a></td>
    <td scope="row" data-label="Description">A value which contains multiple geometry polygons</td>
  </tr>
  <tr>
    <td scope="row" data-label="Type"><a href="#collection"><code>Collection</code></a></td>
    <td scope="row" data-label="Description">A value which contains multiple different geometry types</td>
  </tr>
</tbody>
</table>

## The GeoJSON spec

There are two main points to keep in mind when creating a `geometry` type in SurrealDB. They are:

* Points are defined according to the GeoJSON spec, which specifies longitude before latitude. Many sites - including Google Maps - provide location data in the opposite order, so be sure to confirm that any data being used to create a `Point` is in the order `(longitude, latitude)`, and not the other way around.
* A `geometry` created from an object must contain a `type` field and a `coordinates` field, no more and no less.

This can be shown by calling the [`type::is_geometry()`](/docs/reference/query-language/functions/database-functions/type.md#typeis_geometry) function on some sample objects.

```surql
-- Has both a `type` and a `coordinates` field, each with valid data
{ type: "Point", coordinates: [-0.118092, 51.509865] }.is_geometry();
//- true

-- Lacks a `type` field
{ coordinates: [-0.118092, 51.509865] }.is_geometry();
//- false

-- Carries a field beyond `type` and `coordinates`
{ type: "Point", coordinates: [-0.118092, 51.509865], unnecessary: "data" }.is_geometry();
//- false
```

## `Point`

The simplest form of GeoJSON that SurrealDB supports is a geolocation point. These can be written using two different formats. The first format is that of an object that matches the GeoJSON spec.

```surql
CREATE city:london SET centre = {
    type: "Point",
    coordinates: [-0.118092, 51.509865],
};
```

The other format is a simple 2-element tuple containing the longitude and the latitude of a location. This output for this format is no different from the above, and is simply a convenience due to the frequency of use of the `Point` type.

```surql
CREATE city:london SET centre = (-0.118092, 51.509865);
```

<br />

## `LineString`

A GeoJSON LineString value for storing a geometric path.

```surql
CREATE city:london SET distance = {
    type: "LineString",
    coordinates: [[-0.118092, 51.509865],[0.1785278, 51.37692386]],
};
```

<br />

## `Polygon`

A GeoJSON Polygon value for storing a geometric area.

```surql
CREATE city:london SET boundary = {
	type: "Polygon",
	coordinates: [[
		[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
		[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
		[-0.38314819, 51.37692386]
	]]
};
```

<br />

## `MultiPoint`

MultiPoints can be used to store multiple geometry points in a single value.

```surql
CREATE person:tobie SET locations = {
	type: "MultiPoint",
	coordinates: [
		[10.0, 11.2],
		[10.5, 11.9]
	],
};
```

<br />

## `MultiLineString`

A MultiLineString can be used to store multiple geometry lines in a single value.

```surql
CREATE travel:yellowstone SET routes = {
	type: "MultiLineString",
	coordinates: [
		[ [10.0, 11.2], [10.5, 11.9] ],
		[ [11.0, 12.2], [11.5, 12.9], [12.0, 13.0] ]
	]
}
```

<br />

## `MultiPolygon`

MultiPolygons can be used to store multiple geometry polygons in a single value.

```surql
CREATE university:oxford SET locations = {
	type: "MultiPolygon",
	coordinates: [
		[
			[ [10.0, 11.2], [10.5, 11.9], [10.8, 12.0], [10.0, 11.2] ]
		],
		[
			[ [9.0, 11.2], [10.5, 11.9], [10.3, 13.0], [9.0, 11.2] ]
		]
	]
};
```

<br />

## `Collection`

Collections can be used to store multiple different geometry types in a single value.

```surql
CREATE university:oxford SET buildings = {
	type: "GeometryCollection",
	geometries: [
		{
			type: "MultiPoint",
			coordinates: [
				[10.0, 11.2],
				[10.5, 11.9]
			],
		},
		{
			type: "Polygon",
			coordinates: [[
				[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
				[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
				[-0.38314819, 51.37692386]
			]]
		},
		{
			type: "MultiPolygon",
			coordinates: [
				[
					[ [10.0, 11.2], [10.5, 11.9], [10.8, 12.0], [10.0, 11.2] ]
				],
				[
					[ [9.0, 11.2], [10.5, 11.9], [10.3, 13.0], [9.0, 11.2] ]
				]
			]
		}
	]
};
```

<br />

## Example

The following example includes five records from [an open database](https://public.opendatasoft.com/explore/dataset/geonames-all-cities-with-a-population-1000/export/?disjunctive.cou_name_en&sort=name) with cities worldwide that have 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`](/docs/reference/query-language/functions/database-functions/geo.md#geodistance) 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 SurrealDB Studio's [graph view](/blog/visualising-your-data-with-surrealists-graph-view) to see a visual representation of the network of closely linked cities.

```surql
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;
```

## See also

* [SurrealQL operators](/docs/reference/query-language/language-primitives/operators.md)
* [geo functions](/docs/reference/query-language/functions/database-functions/geo.md)

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/literals

# Literals

A value that may have multiple representations or formats.

A literal is a value that may have multiple representations or formats, similar to an enum or a union type. A literal can be composed of strings, numbers, objects, arrays, or durations.

## Examples

A literal can be as simple as a declaration that a parameter must be a certain value.

```surql
LET $nine: 9 = 9;
LET $nine: 9 = 10;
```

```surql title="Output"
-------- Query --------

NONE

-------- Query --------

"Tried to set `$nine`, but couldn't coerce value: Expected `9` but found `10`"
```

Using `|` allows a literal to be a number of possible options.

```surql
LET $nine: 9 | "9" | "nine" = "Nein";
```

```surql title="Output"
"Tried to set `$nine`, but couldn't coerce value: Expected `9 | '9' | 'nine'` but found `'Nein'`"
```

A literal can contain possible types in addition to possible values.

```surql
LET $flexible_param: datetime | uuid | "N/A" = "N/A";
LET $flexible_param: datetime | uuid | "N/A" = <datetime>"2024-09-01";
```

Literals that include the option to be an array or an object can contain rich data.

```surql
LET $status: "Ok" | { err: string } = { err: "Forgot to plug it in" };
```

## Literals in database schema

Literals can be defined inside a database schema by using a [DEFINE FIELD](/docs/reference/query-language/statements/define/field.md) statement.

```surql
DEFINE FIELD error_info ON TABLE information TYPE
      { error: "Continue" }
    | { error: "RetryWithId", id: string }
    | { error: "Deprecated", message: string };

CREATE information SET
	error_info = { error: "Deprecated", message: "You shouldn't use this anymore" };
-- Doesn't conform to definition, will not work
CREATE information SET
	error_info = "You shouldn't use this anymore";
```

```surql title="Output"
-------- Query --------

[
	{
		error_info: {
			error: 'Deprecated',
			message: "You shouldn't use this anymore"
		},
		id: info:pkckjrri8q1pg12unyuo
	}
]

-------- Query --------

"Couldn't coerce value for field `error_info` of `information:qbohn4wu4l2t81wj2fb3`: Expected `{ error: 'Continue' } | { error: 'RetryWithId', id: string } | { error: 'Deprecated', message: string }` but found `\"You shouldn't use this anymore\"`"
```

## Matching on literals

While SurrealQL does not have a `match` or `switch` operator, `IF ELSE` statements can be used to match on a literal, particularly if each possible type is an object. The following shows a similar example to the above except that each object begins with a field containing the name of the type of error.

```surql
DEFINE FIELD error_info ON TABLE information TYPE
	{ Continue:    { message: "" }} |
	{ Retry: { error: "Retrying", after: duration }} |
	{ Deprecated:  { message: string }};
```

Next, we will [define a function](/docs/reference/query-language/statements/define/function.md) to handle this field and return a certain type of message depending on the error. Note the following:

* The `LET` statement in the first line is simply to shorten the path to the information contained inside `error_info`
* `IF ELSE` statement works here because [IF](/docs/reference/query-language/statements/throw.md) involves a check for [truthiness](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness), returning `true` as long as it finds a value that is not none, empty, or zero.

```surql
DEFINE FUNCTION fn::handle_error($data: record<information>) -> string {
	LET $err = $data.error_info;
	RETURN IF $err.Continue {
		"Continue"
	}
	ELSE IF $err.Retry {
		sleep($err.Retry.after);
		"Now retrying again"
	}
	ELSE IF $err.Deprecated {
		$err.Deprecated.message
	}
};
```

With the function set up, the `info` records can be inserted and run one at a time through the function.

```surql
LET $info = INSERT INTO information [
	{ error_info: { Continue: { message: "" } }},
	{ error_info: { Retry: { error: "Retrying", after: 1s } }},
	{ error_info: { Deprecated: { message: "Thought I said you shouldn't use this anymore" } }}
];

fn::handle_error($info[0].id);
fn::handle_error($info[1].id);
fn::handle_error($info[2].id);
```

```surql title="Output"
-------- Query --------

'Continue'

-------- Query --------

-- After waiting 1 second
'Now retrying again'

-------- Query --------

"Thought I said you shouldn't use this anymore"
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/none-and-null

# None and Null

None means a missing field; null means an empty stored value - how SurrealDB distinguishes them in SurrealQL.

SurrealDB uses two types called `None` and `Null` to represent two different ways in which data may not exist. While these may appear similar, they have different meanings and are used in different contexts.

## None values

`None` is used to denote that "something does not exist", for example, a field which is not present on a record. Because of this, values of `None` can not be stored within records, meaning uses of `None` are typically limited to SurrealQL statements where it is used to denote a value or response that does not exist.

### Example

Setting a record field to `None` is analogous to using `UNSET` to remove the field entirely. While inside the query it may appear that `None` is being written to the `children` field, what is actually happening is that the `children` field is being removed from the record.

```surql
CREATE person:two;
CREATE person:one SET children = [person:two];
UPDATE person:one SET children = NONE;
SELECT * FROM person;
```

```surql title="Output"
[
  { id: person:one },
  { id: person:two }
]
```

## Null values

`Null` values are used to denote that "something exists, but has no value". This is useful when a field is present on a record, but the value of that field is unknown or not applicable. Unlike `None`, `Null` is written into records and can be stored as a value.

### Example

Setting a record field to `Null` will create the field on the record, but denotes that the field is considered empty. In this example, the `children` field is present on the record, but the value of that field is `null`.

```surql
CREATE person SET children = null;
```

```surql title="Output"
[
  { 
    children: NULL, 
    id: person:dgwjn0ldg8ep3e8y39jw
  }
]
```

## When to use None or Null

How you use `None` or `Null` is largely dependent on the context in which you are working.

If you are writing SurrealQL and need to denote something that does not exist, such as the absence of a field, use `None`.

If you are working with data and need to represent a value which is empty, use `Null`. This is particularly useful when needing to deserialise SurrealQL output into a type in another programming language that requires a field name to be present.

## NONE as a datatype

_(since v3.0.0)_

Since SurrealDB 3.0, NONE has been usable as a datatype of its own. This allows syntax like the following to be used without returning a parsing error.

```surql
DEFINE FUNCTION fn::do_stuff() -> NONE {
  -- Code that should return nothing
};

DEFINE FIELD middle_name
  ON TABLE user TYPE string | NONE; // Equivalent to option<string>

DEFINE FIELD value ON temperature TYPE float | decimal | NONE; // Equivalent to option<float|decimal>
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/numbers

# Numbers

In SurrealDB, numbers can be one of three types - 64-bit integers, 64-bit floating point numbers, or 128-bit decimal numbers.

In SurrealDB, numbers can be one of three types: 64-bit integers, 64-bit floating point numbers, or 128-bit decimal numbers.

## Integer numbers
If a numeric value is specified without a decimal point and is within the range `-9223372036854775808` to `9223372036854775807` then the value will be parsed, stored, and treated as a 64-bit integer.

```surql
CREATE event SET year = 2022;
```

## Floating point numbers
If a number value is specified with a decimal point, or is outside of the maximum range specified above, then the number will automatically be parsed, stored, and treated as a 64-bit floating point value. This ensures efficiency when performing mathematical calculations within SurrealDB.

```surql
CREATE event SET temperature = 41.5;
```

## Decimal numbers
To opt into 128-bit decimal numbers when specifying numeric values, you can use the `dec` suffix.

```surql
CREATE product SET price = 99.99dec;
```

The `dec` suffix is an instruction to the parser and not a cast, and is thus preferred when making a decimal.

```surql
-- Creates the imprecise float 3.888888888888889 and casts it into a decimal as 3.888888888888889dec
<decimal>3.8888888888888888;
-- Uses the input 3.8888888888888888 to directly create a decimal
3.8888888888888888dec;
```

## Using a specific numeric type
To use a specific type when specifying numeric values, you can cast the value to a specific numeric type or use the appropriate suffix.

```surql
CREATE event SET
	year = <int> 2022,
	temperature = <float> 41.5 + 5f,
	horizon = <decimal> 31 + 3dec
;
```

## Numeric precision
Different numeric types can be compared and used together in calculations.

The benefits of floating point numeric values are speed and storage size, but there is a limit to the numeric precision.

```surql
13.5719384719384719385639856394139476937756394756;
```

```surql title="Output"
13.571938471938472f
```

In addition, when using floating point numbers specifically, mathematical operations can result in a loss of precision (as is normal with other databases).

```surql
0.3 + 0.3 + 0.3 + 0.1;
```

```surql title="Output"
0.9999999999999999f
```

Common rounding errors can be avoided by performing calculations using decimals.

```surql
0.3dec + 0.3dec + 0.3dec + 0.1dec;
```

```surql title="Output"
1.0dec
```

## Underscores

As a convenience, underscores are ignored when using a number. This allows input to be more readable than it would otherwise. Because underscores are ignored, they will not display in the output.

```surql
RELATE dr:evil->bribes->other:character SET dollars = 1_000_000.99;
//- [{ dollars: 1000000.99, id: bribes:4bfld2ukwnja24dzrpw9, in: dr:evil, out: other:character }]

-- Input Korean currency counted in units of 10000, not 1000
RELATE korean:purchaser->buys_house_from->korean:seller
              -- 10억 4천만 5천
    SET amount = 10_4000_5000;
//- [{ amount: 1040005000, id: buys_house_from:9070t2ctgwwg202cpw1z, in: korean:purchaser, out: korean:seller }]
```

## Mathematical constants
A set of floating point numeric constants are available in SurrealDB. Constant names are case insensitive, and can be specified with either lowercase or capital letters, or a mixture of both.

```surql
CREATE circle SET circumference = 10;
UPDATE circle SET radius = circumference / ( 2 * MATH::PI );
```

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Constant</th>
            <th colspan="2" scope="col">Description</th>
            <th colspan="2" scope="col">Value</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::E</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Euler’s number (e)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                2.718281828459045
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::FRAC_1_PI</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                1/π
            </td>
            <td colspan="2" scope="row" data-label="Value">
                0.3183098861837907
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::FRAC_1_SQRT_2</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                1/sqrt(2)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                0.7071067811865476
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::FRAC_2_PI</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                2/π
            </td>
            <td colspan="2" scope="row" data-label="Value">
                0.6366197723675814
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::FRAC_2_SQRT_PI</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                2/sqrt(π)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                1.1283791670955126
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::FRAC_PI_2</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                π/2
            </td>
            <td colspan="2" scope="row" data-label="Value">
            1.5707963267948966
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::FRAC_PI_3</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                π/3
            </td>
            <td colspan="2" scope="row" data-label="Value">
                1.0471975511965979
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::FRAC_PI_4</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                π/4
            </td>
            <td colspan="2" scope="row" data-label="Value">
                0.7853981633974483
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::FRAC_PI_6</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                π/6
            </td>
            <td colspan="2" scope="row" data-label="Value">
                0.5235987755982989
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::FRAC_PI_8</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                π/8
            </td>
            <td colspan="2" scope="row" data-label="Value">
                0.39269908169872414
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::INF</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Positive infinity
            </td>
            <td colspan="2" scope="row" data-label="Value">
                inf
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::LN_10</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                ln(10)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                2.302585092994046
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::LN_2</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                ln(2)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                0.6931471805599453
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::LOG10_2</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                log<sub>10</sub>(2)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                0.3010299956639812
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::LOG10_E</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                log<sub>10</sub>(e)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                0.4342944819032518
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::LOG2_10</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                log<sub>2</sub>(10)
            </td>
            <td colspan="2" scope="row" data-label="Value">
            3.321928094887362
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::LOG2_E</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                log<sub>2</sub>(e)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                1.4426950408889634
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::NEG_INF</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Negative infinity
            </td>
            <td colspan="2" scope="row" data-label="Value">
                -inf
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::PI</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Archimedes’ constant (π)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                3.141592653589793
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::SQRT_2</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                sqrt(2)
            </td>
            <td colspan="2" scope="row" data-label="Value">
            1.4142135623730951
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Constant">
                <code>MATH::TAU</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The full circle constant (τ)
            </td>
            <td colspan="2" scope="row" data-label="Value">
                6.283185307179586
            </td>
        </tr>
    </tbody>
</table>

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/objects

# Objects

SurrealDB records can store objects with fields that can also hold other objects or arrays.

An object is a collection of named fields and values.

As a record is essentially an object with a required [`id` field](/docs/reference/query-language/language-primitives/data-types/record-ids.md) that can be created, updated, or deleted, they can be worked with in almost exactly the same way as a standalone object.

A field of an object can be of any value type, including another object or array at multiple levels of depth. This allows objects and arrays to be stored within each other in order to model complex data scenarios.

```surql
CREATE person SET metadata = {
	interest_level: 83.67,
	information: {
		age: 23,
		gender: 'm',
	},
	marketing: true,
	activities: [
		"clicked link",
		"contact form",
		"read email",
		"viewed website",
		"viewed website",
		"viewed website",
		"read email",
	]
};
```

## Field names

### Valid field names

Similar to record IDs, field names can be constructed from ASCII characters, underscores, and numbers. To create a field name with complex characters, backticks can be used.

When you combine backticks with dotted paths for nested fields, keep any periods used to denote a nested path **outside** the backticks. Anything inside backticks is treated as a single literal field name, including a `.` inside the backticks. For definitions and concrete examples (including non-ASCII identifiers), see [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md#example-usage).

```surql
CREATE ONLY user SET my_name = 'name';
CREATE ONLY user SET `mi_nómine😊` = 'name';
```

```surql title="Output"
-------- Query --------

{
	id: user:nronupvxvdm7r1n5hlzm,
	my_name: 'name'
}

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

{
	id: user:eb5pu7u9g67dy773hsv9,
	"mi_nómine😊": 'name'
}
```

Inside a standalone object, non-ASCII field names can also be set by using a string.

```surql
SELECT * FROM ONLY {
    "mi nómine": "Edgar"
};
```

```surql title="Output"
{
	"mi nómine": 'Edgar'
}
```

A field name that contains a period, such as a version number, is accessed with backticks around the whole name or with a string inside square brackets. Writing `content.1.0` without backticks is a parse error.

```surql
CREATE project:one SET content = { "1.0": { tree: { key: "value" } } };

SELECT content.`1.0`.tree.key AS key FROM project;
//- [{ key: 'value' }]

SELECT content["1.0"].tree.key AS key FROM project;
//- [{ key: 'value' }]
```

### Automatically generated field names

A field created from an operation will have a field name that represents the operation(s) used to construct it.

```surql
SELECT
    math::mean(temps),
    [ math::min(temps), math::max(temps) ]
FROM { temps: [-5, 8, 9] };
```

```surql title="Output"
[
    {
        "[math::min(temps), math::max(temps)]": [
            -5,
            9
        ],
        "math::mean": 4f
    }
]
```

Using `AS` allows these automatically calculated field names to be replaced with custom names.

```surql
SELECT
    math::mean(temps) AS mean_temps,
    [ math::min(temps), math::max(temps) ] AS avg_temps
FROM { temps: [-5, 8, 9] };
```

```surql title="Output"
[
    {
        "avg_temps": [
            -5,
            9
        ],
        "mean_temps": 4
    }
]
```

## Extending objects and removing fields

_(since v3.0.0)_

Two objects can be merged by using either the `+` operator or the `object::extend()` function. Any fields in the second object will be added to the first object, thereby updating any existing fields and adding new fields to those that were not present.

```surql
{ name: "Venus", radius: 6000 } + { radius: 6051.8, orbital_period: 1y31w1d22h };
{ name: "Venus", radius: 6000 }.extend({ radius: 6051.8, orbital_period: 1y31w1d22h });
```

```surql title="Output"
-------- Query 1 --------

{
	name: 'Venus',
	orbital_period: 1y31w1d22h,
	radius: 6051.8f
}

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

{
	name: 'Venus',
	orbital_period: 1y31w1d22h,
	radius: 6051.8f
}
```

Fields of an object can be removed with the `object::remove()` function, which takes either a single string or an array of strings of the field names to remove.

```surql
{ name: 'Venus', orbital_period: 1y31w1d22h, radius: 6051.8 }.remove("radius");
{ name: 'Venus', orbital_period: 1y31w1d22h, radius: 6051.8 }.remove(["radius", "orbital_period"]);
```

```surql title="Output"
-------- Query 1 --------

{
	name: 'Venus',
	orbital_period: 1y31w1d22h
}

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

{
	name: 'Venus'
}
```

## See also

* [Object functions](/docs/reference/query-language/functions/database-functions/object.md)
* [Destructuring nested objects](/docs/reference/query-language/language-primitives/idioms.md#destructuring)

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/ranges

# Ranges

A range of possible values.

A range is composed of `..` and possible delimiters to set the maximum and minimum possible values. The default syntax includes the lower limit and excludes the upper limit. A `=` can be used to make the upper limit inclusive, and `>` can be used to make the lower limit exclusive.

```surql
-- From 0 up to 9
0..10;
-- From 0 up to 10
0..=10;
-- From 1 to 9
0>..10;
-- From 1 to 10
0>..=10;
```

A range becomes open ended if a delimiter is not specified.

```surql
-- Anything from 0 and up
0..;
-- Anything from 1 and up
0>..;
-- Anything up to 99
..100;
-- Anything up to 100
..=100;
-- An infinite range
..;
```

A range can be constructed from any type of value. This is most useful when comparing one value to another.

```surql
-- All true
'g' IN 'a'..'z';
d"2024-01-01" IN d"2020-01-01"..=d"2025-01-01";
['London', d"2022-02-02", 5.7] IN ['London', d"2020-01-01"]..=['London', d"2024-12-31"];

-- All false
"ㅋㅋㅋ" IN "a".."z";
d"2028-01-01" IN d"2020-01-01"..=d"2025-01-01";
['Philadelphia', d"2022-02-02", 5.7] IN ['London', d"2020-01-01"]..=['London', d"2024-12-31"];
```

## Ranges in FOR loops

Ranges of integers have the added convenience of being able to be used in a [FOR loop](/docs/reference/query-language/statements/for.md).

```surql
FOR $year IN 0..=2024 {
    CREATE historical_events SET
        for_year = $year,
        events = "To be added";
}
```

## Ranges in WHERE clauses

A range can be used in a `WHERE` clause in place of operators like `<` and `>`. This is useful when checking for a number that must be within a certain range, and produces shorter code.

```surql
SELECT * FROM person WHERE age >= 18 AND age <= 65;
SELECT * FROM person WHERE age IN 18..=65;
```

The comparison form is faster, so prefer it on large tables. A pair of comparisons on an indexed field uses the index, while `IN` with a range reads every record in the table. See [numeric ranges in a `WHERE` clause](/docs/reference/query-language/statements/select.md#numeric-ranges-in-a-where-clause) for details.

## Casting and functional usage

A range can be cast into an array.

```surql
<array> 1..3;
```

```surql title="Output"
[
	1,
	2
]
```

This opens up a range of functional programming patterns that are made possible by SurrealDB's [array functions](/docs/reference/query-language/functions/database-functions/array.md), many of which can use [anonymous functions](/docs/reference/query-language/language-primitives/data-types/closures.md) (closures) to perform an operation on each item in the array.

```surql
-- Construct an array
(<array> 1..=100)
-- Turn it into an array that increments by 10
    .map(|$v| $v * 10)
-- Turn each number into a object with original and square root value
    .map(|$v| { original: $v, square_root: math::sqrt($v) })
-- Keep only those with square roots in between 11 and 12
    .filter(|$obj| $obj.square_root IN 11..12);
```

```surql title="Output"
[
	{
		original: 130,
		square_root: 11.40175425099138f
	},
	{
		original: 140,
		square_root: 11.832159566199232f
	}
]
```

## Ranges in mock syntax for `CREATE` statements

_(since v3.0.0)_

`CREATE` statements have always been able to work on more than one record by enclosing either a single number or a range-like operator between two `||` bars.

```surql title="Before 3.0.0"
-- Create 10 person records with random IDs
CREATE |person:10|;
-- Create `person` records from person:1 to person:10
CREATE |person:1..10|;
```

Originally an internal syntax for mock testing, this syntax became known to the user community and is now commonly used. However, the original syntax differed from true ranges in always being inclusive, in that `1..10` was treated as "from 1 up to and including 10". A change has since been made to have the mock syntax take a true range with a syntax equivalent to that demonstrated in this page.

```surql title="Since 3.0.0"
-- All of these create ten records from person:1 to person:10
CREATE |person:1..=10|;
CREATE |person:1..11|;
CREATE |person:0>..11|;
CREATE |person:0>..=10|;
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/record-ids

# Record IDs

In SurrealDB, document record IDs store both the table name, and the record ID.

> [!NOTE]
> SurrealDB does not eagerly convert a string into a record. An [implicit `r` prefix or cast](/docs/reference/query-language/language-primitives/casting.md#casting-vs-affixes) is required instead.

SurrealDB record IDs are composed of a table name and a record identifier separated by a `:` in between, allowing for a simple and consistent way to reference records across the database. Record IDs are used to uniquely identify records within a table, to [query](/docs/reference/query-language/statements/select.md), [update](/docs/reference/query-language/statements/update.md), and [delete](/docs/reference/query-language/statements/delete.md) records, and serve as [links](/docs/reference/query-language/language-primitives/record-links.md) from one record to another.

Record IDs can be constructed from a number of ways, including [alphanumeric text](#text-record-ids), complex Unicode text and symbols, [numbers](#numeric-record-ids), arrays, objects, [built-in ID generation functions](#random-ids), and [a function to generate an ID from values](/docs/reference/query-language/functions/database-functions/type.md#typerecord).

All of the following are examples of valid record IDs in SurrealQL.

```surql
company:surrealdb
company:w6xb3izpgvz4n0gow6q7
reaction:`🤪`
weather:['London', d'2025-02-14T01:52:50.375Z']
```

As all record IDs are unique, trying to create a new record with an existing record ID will return an error. To create a record or modify it if the ID already exists, use an [`UPSERT`](/docs/reference/query-language/statements/upsert.md) statement or an [`INSERT`](/docs/reference/query-language/statements/insert.md#example-usage) statement with an `ON DUPLICATE KEY UPDATE` clause.

## Types of record IDs

### Random IDs

When you [create a record](/docs/reference/query-language/statements/create.md) without specifying the full ID, a random identifier is assigned after the table name. This differs from the traditional default of auto-increment or serial IDs that many developers are used to.

```surql
CREATE company;
```

```surql title="Output"
[
	{
		id: company:ezs644u19mae2p68404j
	}
]
```

Record IDs can be generated with a number of built-in ID generation functions, which are cryptographically secure and suitable for dispersion across a distributed datastore. These include a 20 digit alphanumeric ID (the default), sequentially incrementing and temporally sortable ULID Record identifiers, and UUID version 7 Record identifiers.

The default ID is 20 characters drawn at random from 36 (`a` to `z` and `0` to `9`), which gives about 1.3 × 10³¹ possible values. A collision between two IDs in the same table becomes as likely as not only after about 4 × 10¹⁵ IDs have been generated in it. If a collision does happen, the `CREATE` fails with `Database record ... already exists` and the existing record is left unchanged. Use `ulid()` when records should sort by creation time, and `uuid()` when the ID has to be a standard UUID for another system.

```surql
-- Generate a random record ID 20 characters in length
-- Charset: `abcdefghijklmnopqrstuvwxyz0123456789`
CREATE temperature:rand() SET time = time::now(), celsius = 37.5;
-- Identical to the above CREATE statement, because
-- :rand() is the default random ID format
CREATE temperature SET time = time::now(), celsius = 37.5;

-- Generate a ULID-based record ID
CREATE temperature:ulid() SET time = time::now(), celsius = 37.5;
-- Generate a UUIDv7-based record ID
CREATE temperature:uuid() SET time = time::now(), celsius = 37.5;
```

### Text record IDs

Text record IDs can contain letters, numbers and `_` characters.

```surql
CREATE company:surrealdb SET name = 'SurrealDB';
CREATE user_version_2025 SET name = 'Alucard';
```

To create a record ID with complex characters, use <code>`</code> (backticks) around the table name and/or record identifier.

```surql
CREATE article:`8424486b-85b3-4448-ac8d-5d51083391c7` SET
    time = time::now(),
    author = person:tobie;

CREATE `Artykuł`:100 SET
    author = person:`Lech_Wałęsa`;
```

The parts of record IDs with complex characters will display enclosed by <code>`</code> backticks.

SurrealDB 2.x displayed the identifier part of such a record ID between `⟨` and `⟩` instead, as in `person:⟨Lech_Wałęsa⟩`. Both forms are still accepted as input and refer to the same record, so ``person:⟨Lech_Wałęsa⟩ = person:`Lech_Wałęsa` `` is `true`.

```surql title="Output"
-------- Query --------

[
	{
		author: person:tobie,
		id: article:`8424486b-85b3-4448-ac8d-5d51083391c7`,
		time: d'2025-02-18T01:48:46.364Z'
	}
]

-------- Query --------

[
	{
		author: person:`Lech_Wałęsa`,
		id: `Artykuł`:100
	}
]
```

### Numeric record IDs

If you create a record ID with a number as a string, it will be stored with <code>`</code> backticks to differentiate it from a number.

```surql
CREATE article SET id = 10;
CREATE article SET id = "10";
CREATE article SET id = "article10";
SELECT VALUE id FROM article;
```

As the record ID `article:10` is different from ```article:`10` ```, no errors are returned when creating and both records turn up in the output of the `SELECT` statement. Meanwhile, the article with the identifier `article10` does not use backticks as there is no `article10` number to differentiate it from.

```surql title="Output"
[
	article:10,
	article:`10`,
    article:article10
]
```
If a numeric value is specified without any decimal point suffix and is within the range `-9223372036854775808` to `9223372036854775807` then the value will be parsed, stored, and treated as a 64-bit signed integer.

Any numeric numbers outside of the range of a signed 64-bit integer will be stored as a string.

```surql
/**[test]

[[test.results]]
value = "[{ celsius: 37.5f, id: temperature:17493, time: d'2025-10-03T01:09:50.155406Z' }]"

[[test.results]]
value = "[{ events: ['Galactic senate convenes', 'Mr. Bean still waits in a field'], id: year:⟨29878977097987987979232⟩ }]"

*/

CREATE temperature:17493 SET time = time::now(), celsius = 37.5;
CREATE year:29878977097987987979232 SET
    events = [
        "Galactic senate convenes",
        "Mr. Bean still waits in a field"
    ];
```

```surql title="Output"
-------- Query --------

[
	{
		celsius: 37.5f,
		id: temperature:17493,
		time: d'2025-02-17T06:21:08.911Z'
	}
]

-------- Query s--------

[
	{
		events: [
			'Galactic senate convenes',
			'Mr. Bean still waits in a field'
		],
		id: year:`29878977097987987979232`
	}
]
```
### Array-based record IDs

Record IDs can be constructed out of arrays and even objects. This sort of record ID is most used when you have a field or two that will be used to look up records inside a [record range](#record-ranges), which is extremely performant. This is in contrast to using a `WHERE` clause to filter, which involves a table scan.

Records in SurrealDB can store arrays of values, including other nested arrays or objects within them. Different types of values can be stored within the same array, unless defined otherwise.

```surql
/**[test]

[[test.results]]
value = "[{ conditions: 'cloudy', id: weather:['London', d'2025-02-13T05:00:00Z'], temperature: 5.7f }]"

*/

CREATE weather:['London', d'2025-02-13T05:00:00Z'] SET
    temperature = 5.7,
    conditions = "cloudy";
```

```surql title="Output"
[
	{
		conditions: 'cloudy',
		id: weather:[
			'London',
			d'2025-02-13T05:00:00Z'
		],
		temperature: 5.7f
	}
]
```
### Why record ranges are performant

The main reason why record ranges are so performant is simply because the database knows ahead of time in which area to look for records in a query, and therefore has a smaller "surface area" to work in.

This can be demonstrated by seeing what happens when a single record range query encompasses all of the records in a database. The example below creates 10,000 `player` records that have an array-based record ID that begins with `'mage'`, allowing them to be used in a record range query, as well as a field called `class` that is also `'mage'`, which will be used in a `WHERE` clause to compare performance.

Interestingly, in this case a record range query is only somewhat more performant. This is because both queries end up iterating over 10,000 records, with the only difference being that the query with a `WHERE` clause also checks to see if the value of the `class` field is equal to `'mage'`.

```surql
FOR $_ IN 0..10000 {
    CREATE player:['mage', rand::id()] SET class = 'mage';
};

LET $_ = SELECT * FROM player:['mage', NONE]..['mage', ..];
LET $_ = SELECT * FROM player WHERE class = 'mage';
```
If the number of `player` records is extended to a larger number of classes, however, the difference in performance will be much larger. In this case the record range query is still only iterating a relatively small surface area of 10,000 records, while the second one has ten times this number to go through in addition to the `WHERE` clause on top.

```surql
FOR $_ IN 0..10000 {
  CREATE player:['mage', rand::id()] SET class = 'mage';
  CREATE player:['barbarian', rand::id()] SET class = 'barbarian';
  CREATE player:['rogue', rand::id()]     SET class = 'rogue';
  CREATE player:['bard', rand::id()]      SET class = 'bard';
  CREATE player:['sage', rand::id()]      SET class = 'sage';
  CREATE player:['psionic', rand::id()]   SET class = 'psionic';
  CREATE player:['thief', rand::id()]     SET class = 'thief';
  CREATE player:['paladin', rand::id()]   SET class = 'paladin';
  CREATE player:['ranger', rand::id()]    SET class = 'ranger';
  CREATE player:['cleric', rand::id()]    SET class = 'cleric';
};

LET $_ = SELECT * FROM player:['mage', NONE]..['mage', ..];
LET $_ = SELECT * FROM player WHERE class = 'mage';
```
### IDs made with parameters and function calls

Parameters and function calls can be used inside array- and object-based record IDs in the same way as on standalone arrays and objects.

```surql
/**[test]

[[test.results]]
value = "NONE"

[[test.results]]
value = "[{ conditions: 'cloudy', id: weather:['Seoul', d'2025-10-03T01:11:38.204589Z'], temperature: -2.3f }]"
skip-datetime = true

[[test.results]]
value = "[{ conditions: 'cloudy', id: weather:['London', d'2025-10-03T01:11:38.204961Z'], temperature: 5.3f }]"
skip-datetime = true

*/

LET $now = time::now();

CREATE weather:['Seoul', $now] SET
    temperature = -2.3,
    conditions = "cloudy";

CREATE weather:['London', time::now()] SET
    temperature = 5.3,
    conditions = "cloudy";
```
To create a record that uses a parameter or function call as its entire record identifier, the [`type::record()`](/docs/reference/query-language/functions/database-functions/type.md#typerecord) function can be used. (Note: this function was known as `type::thing()` before SurrealDB 3.0)

```surql
/**[test]

[[test.results]]
value = "NONE"

[[test.results]]
value = "[{ city: 'London', id: weather:⟨2025-10-03T01:13:14.238633Z⟩ }]"
skip-record-id-key = true

*/

LET $now = time::now();

CREATE type::record("weather", $now) SET city = 'London';
```

```surql title="Output"
[
	{
		city: 'London',
		id: weather:`2025-02-18T02:30:08.563Z`
	}
]
```
## Sort order of record IDs

Records in a table are stored in the order of their IDs, and a query that reads a whole table returns them in that order. `ORDER BY id` gives the same order. The key of an ID sorts first by its type and then by its value:

1. Numbers, by numeric value, so `10` comes after `9`.
2. Strings, character by character, so the string `` `10` `` comes before `` `9` ``.
3. Arrays, element by element.
4. Objects.

```surql
CREATE k:1, k:2, k:10, k:`10`, k:`9`, k:abc, k:[1, 'a'], k:[0, 'z'], k:{ a: 1 };
SELECT VALUE id FROM k ORDER BY id;
```

```surql title="Output"
[k:1, k:2, k:10, k:`10`, k:`9`, k:abc, k:[0, 'z'], k:[1, 'a'], k:{ a: 1 }]
```
To make IDs sort by value, use numbers for numeric keys, rather than strings of digits. A [ULID](#random-ids) sorts by the time it was created. The same order decides which records a [record range](#record-ranges) returns.

## Record ranges

SurrealDB supports the ability to query a range of records, using the record ID. Record ID range queries retrieve records using the natural sorting order of the record IDs, making a table scan unnecessary. These range queries can be used to query a range of records in a timeseries context.

```surql
-- Select all person records with IDs between the given range
SELECT * FROM person:1..1000;

-- Select all records for a particular location, inclusive
SELECT * FROM temperature:['London', NONE]..=['London', ..];

-- Select London records before a given time
SELECT * FROM temperature:['London', NONE]..['London', '2022-08-29T08:09:31'];

-- Select London records from a given time onwards
SELECT * FROM temperature:['London', '2022-08-29T08:03:39']..=['London', ..];

-- Select all temperature records with IDs between the specified range
SELECT * FROM temperature:['London', '2022-08-29T08:03:39']..['London', '2022-08-29T08:09:31'];
```
A range compares whole IDs in their [sort order](#sort-order-of-record-ids), not each element on its own. A range with an open end therefore continues past the records for one location: `temperature:['London', '2022-08-29T08:03:39']..` also returns every record for `'Munich'`, and `temperature:..['London', '2022-08-29T08:09:31']` every record for `'Amsterdam'`, because those arrays sort after and before the London ones. To stay within one location, close the range with `NONE` or `..` in the second element, as in the examples above. `NONE` sorts before every other value and `..` after every other value.

The following example shows the difference in performance between a regular query that uses a `WHERE` clause and a record range scan.

```surql
FOR $num IN 0..=100000 {
  CREATE person SET id = $num, num = $num  
};

-- Assign the output to an unused parameter
-- to avoid excessive output
LET $_ = SELECT * FROM person WHERE num IN 0..=1000;
LET $_ = SELECT * FROM person:0..=1000;
```
## Defining record IDs in a schema

The type name of a record ID is `record`, which by default allows any sort of record. This type can be set inside a [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statement.

```surql
/**[test]

[[test.results]]
value = "NONE"

[[test.results]]
value = "NONE"

[[test.results]]
value = "[{ friends: [person:one, person:two], id: person:663uogu8gnw31irybeer, possessions: [book:one, house:one] }]"
skip-record-id-key = true

*/

DEFINE FIELD possessions ON TABLE person TYPE option<array<record>>;
DEFINE FIELD friends ON TABLE person TYPE option<array<record<person>>>;

CREATE person SET
    possessions = [ book:one, house:one],
    friends = [ person:one, person:two ];
```
Be sure to use just `record` instead of `record<any>`, as `<any>` here would imply actual records of a table called `any`.

```surql
/**[test]

[[test.results]]
value = "NONE"

[[test.results]]
match = "$error = "Couldn't coerce value for field `possessions` of `person:*`: Expected `none | array<record<any>>` but found `[book:one, house:one]`""
error = true

[[test.results]]
value = "[{ id: person:u6qd2t4ij2h45bkf2gk4, possessions: [any:one, any:two] }]"
skip-record-id-key = true

*/

DEFINE FIELD possessions ON TABLE person TYPE option<array<record<any>>>;

-- Won't work, 'book' and 'house' are not of table 'any'
CREATE person SET
    possessions = [ book:one, house:one ];

-- Actually expects this, which is probably
-- not what the DEFINE FIELD intended
CREATE person SET
    possessions = [ any:one, any:two ];
```
## Limitations

A [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statement for the `id` field cannot use the `VALUE` clause.

```surql
/**[test]

[[test.results]]
error = "Cannot use the `VALUE` keyword on the `id` field."

*/

DEFINE FIELD id ON user VALUE rand::int(1, 1000000000) READONLY;
```

```surql title="Error output"
'Cannot use the `VALUE` keyword on the `id` field.'
```
To achieve the desired behaviour, the `id` field can be set inside the statement to create the record.

```surql
/**[test]

[[test.results]]
value = "[{ id: user:639167349 }]"
skip-record-id-key = true

*/


CREATE user SET id = rand::int(1, 1000000000);
```
### Changing a record's ID

A record's ID cannot be changed after the record is created. Setting `id` in an `UPDATE`, or in the `CONTENT` of an `UPSERT`, fails with ``Found person:new for the `id` field, but a specific record has been specified``.

To move a record to a new ID, create a copy under the new ID and delete the original. Links to the record are not updated, so update the fields that point to it as well:

```surql
CREATE person:old SET name = 'Ann';
CREATE post:1 SET author = person:old;

CREATE person:new CONTENT (SELECT * OMIT id FROM ONLY person:old);
DELETE person:old;
UPDATE post SET author = person:new WHERE author = person:old;

SELECT author.name AS author_name FROM post:1;
```

```surql title="Output"
[{ author_name: 'Ann' }]
```
Such statements are best run inside a [manual transaction](/docs/reference/query-language/language-primitives/transactions.md) so that no query sees the record half moved. Graph edges that point to the record have to be recreated in the same way, because an edge's `in` and `out` cannot be changed either: an `UPDATE` that sets them leaves them as they were, without an error. To see all the incoming and outgoing edges for a record, the `?` operator can be used.

```surql
-- Destructuring operator can be used...
person:one.{
    out: ->?,
    in: <-?
}

-- Or a regular SELECT statement
SELECT 
	->? AS out, 
	<-? AS in
FROM ONLY person:one;
```

## Learn more

Learn more about record IDs [in this blogpost](/blog/the-life-changing-magic-of-surrealdb-record-ids#the-performance-at-scale) and on this [youtube video](https://www.youtube.com/watch?v=c0cqmWRYP8c).

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/regex

# Regex

A reged (regular expression) can be used to specify a match pattern in text.

_(since v3.0.0)_

A `regex` can be created by casting from a string.

The following examples all return `true`.

```surql
-- Either 'a' or 'b'
<regex> "a|b" = "a";

-- Either color or colour
<regex> "col(o|ou)r" = "colour";

-- Case-insensitive match on English color, colour, or French couleur
<regex> "((?i)col(o|ou)r|couleur)" = "COULEUR";
```

While `regex` was added as a standalone type in version 2.3.0, regex matching has always been available via the [`string::matches()`](/docs/reference/query-language/functions/database-functions/string.md#stringmatches) function.

```surql
string::matches("a", "a|b");
string::matches("colour", "col(o|ou)r");
string::matches("COULEUR", "((?i)col(o|ou)r|couleur)");
```

A regex can also be written as a literal between forward slashes, which is the shortest form inside a `WHERE` clause.

```surql
CREATE article:1 SET text = "News from Bitfinicon";
CREATE article:2 SET text = "Other news";
SELECT VALUE id FROM article WHERE text = /(?i)bitfinicon/;
```

```surql title="Output"
[article:1]
```

A regex match in a `WHERE` clause does not use an index, including a `FULLTEXT` index on the same field, so the query reads every record in the table. For word-based search over a large table, a [full-text search index](/docs/learn/data-models/full-text-search/overview.md) queried with the `@@` operator uses the index.

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/sets

# Sets

A set is a collection type of deduplicated and ordered values that can have a maximum size limit.

> [!NOTE]
> Before version 3.0.0, sets were simply arrays that deduplicated their items. To emulate the former behaviour, add the clause `VALUE $value.distinct()` to a `DEFINE FIELD` definition.

A set is similar to an array, but with two key differences:

* The values in a set are automatically deduplicated.
* The values in a set are automatically ordered.

## Set syntax and casting

A set can be created using the literal syntax `{}`.

```surql
{1, 6, 6, 2};
```

```surql title="Output"
{1, 2, 6}
```

To create a set with zero items or a single item, add a comma.

```surql
{,}.is_set();  -- true
{9,}.is_set(); -- true

{}.is_set();   -- false
{9}.is_set();  -- false
```

In addition to the `{}` literal syntax, an array can be cast into a set.

```surql
DEFINE FIELD bank_accounts ON TABLE customer TYPE array<int>;
DEFINE FIELD languages ON TABLE customer TYPE set<string>;

CREATE customer SET
    bank_accounts = [
      55555,
      55555,
      98787
    ],
    languages = <set>[
        "en",
        "ja",
        "kr",
        "kr"
    ];
```

```surql title="Output"
[
	{
		bank_accounts: [
			55555,
			98787
		],
		id: customer:uv6mn62t8td9vzvfogh4,
		languages: {
			'en',
			'ja',
			'kr'
		}
	}
]
```

Casting into a `set` and back into an array can be a convenient way to deduplicate items in the same way that the [`array::distinct()`](/docs/reference/query-language/functions/database-functions/array.md#arraydistinct) and [`array::sort()`](/docs/reference/query-language/functions/database-functions/array.md#arraysort) functions are used.

```surql
<array><set>[18,7,6,6,6,6,5,4,3,9];
[18,7,6,6,6,6,5,4,3,9].distinct().sort();
```

```surql title="Output"
[3, 4, 5, 6, 7, 9, 18]
```

## Using the index operator on sets

The `[]` index operator can be used on a set in the same manner as an array. Note however that due to a set's automatic ordering, its individual values are technically not assigned an index. This can be seen in the following example in which `[0]` for an array will return whichever item happens to be at that location, while for a set `[0]` will automatically be the item with the least value.

To return the greatest value of a set, use `[$]` (since 3.2.0) or the [`set::last()`](/docs/reference/query-language/functions/database-functions/set.md#setlast) function.

```surql
-- Array: returns 4 at index 0
[4,6,2][0];

-- Set: returns 2, the least item
(<set>{4,6,2})[0];

-- Set: returns 6, the greatest item
{4,6,2}[$];
{4,6,2}.last();
```

## Filtering and mapping with set functions

SurrealDB also includes a number of methods for sets that make it easier to filter and map. These methods take a closure (an anonymous function) that works in a similar way to the `$this` parameter above.

Here is an example of the `set::filter()` method being used. Note that the parameter name inside the closure is named by the user, so `$val` in the example below could be `$v` or `$some_val` or anything else.

```surql
{1,3,5}.filter(|$val| $val > 2);
```

```surql title="Output"
{3,5}
```

## Adding sets

A set can be added to another set or array, resulting in a single set consisting of the items of the first followed by those of the second.

```surql
{1,2} + [3,4];
{1,2} + {3,4};
```

```surql title="Output"
{1,2,3,4}
```

## Sets on schemafull fields

A field typed as a set accepts a value that can be coerced into one, so the `+=` operator adds a single item to it.

```surql
DEFINE TABLE test SCHEMAFULL;
DEFINE FIELD tags ON test TYPE set<string>;

CREATE test:one SET tags += 'admin';
UPDATE test:one SET tags += 'editor';
```

When the field has a [`VALUE`](/docs/reference/query-language/statements/define/field.md#using-the-value-clause-to-set-a-fields-value) clause on its items, that clause is applied before the set removes duplicates. Two entries that only differ in a way the clause normalises therefore collapse into one on the write that adds them.

```surql
DEFINE FIELD OVERWRITE tags.* ON test TYPE string VALUE string::trim($value);

-- tags is still { 'admin', 'editor' }: the trimmed value already exists
UPDATE test:one SET tags += ' admin ';
```

> [!NOTE]
> Before SurrealDB 3.3.0, a set field rejected `+=` because the single item could not be coerced into a set, and deduplication ran before the `VALUE` clause. A value that only became a duplicate after the clause was applied stayed in the set until the next write to that record.

## Limiting the size of a set

A set type takes an optional maximum size as its second argument, as in `set<string, 2>`. Duplicates are removed before the size is checked, so a value with three items, two of them the same, still fits.

```surql
DEFINE FIELD tags ON post TYPE set<string, 2>;

CREATE post:one SET tags = ['news', 'tech', 'news'];
//- [{ id: post:one, tags: {'news', 'tech'} }]

CREATE post:two SET tags = ['news', 'tech', 'sport'];
//- "Couldn't coerce value for field `tags` of `post:two`: Expected `set<string,2>` but found a collection of length `3`"
```

A set limits how many values a field holds, but not which values those are. For a field that holds one of a fixed list of values, such as a post status, use a [literal](/docs/reference/query-language/language-primitives/data-types/literals.md) union type instead.

```surql
DEFINE FIELD status ON post TYPE 'draft' | 'published' | 'private';

CREATE post:three SET status = 'archived';
//- "Couldn't coerce value for field `status` of `post:three`: Expected `'draft' | 'published' | 'private'` but found `'archived'`"
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/strings

# Strings

Strings can be used to store text values. All string values can include Unicode values, emojis, tab characters, and line breaks.

Strings can be used to store text values. All string values can include Unicode values, emojis, tab characters, and line breaks.

```surql
CREATE person SET text = 'Lorem ipsum dolor sit amet';
```

Strings can be created using single quotation marks, or double quotation marks.

```surql
CREATE person SET text = "Lorem ipsum dolor sit amet";
```

Any string in SurrealDB can include Unicode text.

```surql
CREATE person SET text = "I ❤️ SurrealDB";
```

Strings can also include line breaks.

```surql
CREATE person SET text = "This
is
over
multiple
lines";
```

## Specifying data type literal values using string prefixes

### Overview

In SurrealQL, there are several data types for which literal values are specified using string values, with a prefix indicating the intended type for the value to be interpreted as.

Previously, in SurrealQL version `1.0`, literal values of these types were simply specified using a string without any prefix, and SurrealDB would eagerly convert the strings into the relevant data type in any case where the string matched the format expected for that type. However, since SurrealQL version `2.0`, strings are no longer eagerly converted into other data types. Instead, if you want to specify a literal value of one of these data types, you must explicitly use a string with the appropriate prefix.

### Record ID literal values using the `r` prefix

The `r` prefix tells the parser that the contents of the string represent a [`record ID`](/docs/reference/query-language/language-primitives/data-types/record-ids.md). The parser expects record IDs to have the following format: `table_name:record ID`.

> [!NOTE]
> Strings without the `r` prefix are of type `string` and are not parsed as records unless the prefix is present.

Here is an example of a record ID literal value, specified using a string with the `r` prefix.

```surql
r"person:john";
```

```surql title="Output"
-------- Query 1 --------

person:john
```

In the example below, using the [`type::is_string()`](/docs/reference/query-language/functions/database-functions/type.md#typeis_string) and [`type::is_record()`](/docs/reference/query-language/functions/database-functions/type.md#typeis_record) functions respectively, you can check the type of the string.

```surql
type::is_string("person:john");
type::is_record("person:john");
type::is_record(r"person:john");
```
```surql title="Output"
-------- Query 1 --------

true

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

false

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

true
```

### Datetime literal values using the `d` prefix

The `d` prefix tells the parser that the contents of the string represent a [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md). The parser expects `datetime` values to have a valid [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) format. Here are a few examples:

```surql
d"2025-11-28T11:41:20.262Z";       --- Sub-second precision included, timezone defaulted to UTC
d"2025-11-28T11:41:20.262+04:00";  --- Sub-second precision included, timezone specified as UTC + 4:00
d"2025-11-28T11:41:20.262-04:00";  --- Sub-second precision included, timezone specified as UTC - 4:00
d"2025-11-28T11:41:20Z";           --- Sub-second precision excluded, timezone defaulted to UTC
d"2025-11-28T11:41:20+04:00";      --- Sub-second precision excluded, timezone specified as UTC + 4:00
```
```surql title="Output"
-------- Query 1 --------

d'2025-11-28T11:41:20.262Z'

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

d'2025-11-28T07:41:20.262Z'

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

d'2025-11-28T15:41:20.262Z'

-------- Query 4 --------

d'2025-11-28T11:41:20Z'

-------- Query 5 --------

d'2025-11-28T07:41:20Z'
```

### UUID literal values with the `u` prefix

The `u` prefix tells the parser that the contents of the string represent a [`uuid`](/docs/reference/query-language/language-primitives/data-types/uuids.md). The parser expects `uuid` values to follow the format of an UUID, `ffffffff-ffff-ffff-ffff-ffffffffffff`, where each non-hyphen character can be a digit (0-9) or a letter between `a` and `f` (representing a single hexadecimal digit).

```surql
u"8c54161f-d4fe-4a74-9409-ed1e137040c1";
```

```surql title="Output"
-------- Query 1 --------

u'8c54161f-d4fe-4a74-9409-ed1e137040c1'
```

### Byte values using the `b` prefix

```surql
b"0099aaff"
```

### File paths using the `f` prefix

```surql
f"bucket:/some/key/to/a/file.txt";
f"bucket:/some/key/with\ escaped";
f"bucket:/some/key".put(b"00aa");
f"bucket:/some/key".get();
```

### String prefixes vs. casting

String prefixes seem outwardly similar to casting, but differ in behaviour. A string prefix is an instruction to the parser to treat an input in a certain way, whereas a cast is an instruction to the database to convert one type into another.

As a result, incorrect input with a cast will generate an error:

```surql
-- Change _ to - in both examples to fix the input
<uuid>"018f0e6a_9b95-7ecc-8a38-aea7bf3627dd";
<datetime>"2024_06-06T12:00:00Z";
```
```surql title="Output"
-------- Query 1 --------

"Expected a uuid but cannot convert '018f0e6a-9b95-7ecc-8a38-aea7bf3627d' into a uuid"

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

"Expected a datetime but cannot convert '2024-06-06T12:00:00' into a datetime"
```

But the same input using a string prefix will not even parse until the input is valid.

```surql
-- Will not parse in either case until _ is changed to -
u"018f0e6a_9b95-7ecc-8a38-aea7bf3627dd";
d"2024_06-06T12:00:00Z";
```

This also allows for immediate error messages on which part of the input is incorrect. As seen in the image below, the parser is able to inform the user that an underscore at column 18 is the issue.

<img src="~/assets/img/image/light/surrealql-parse-error.png" darkSrc="~/assets/img/image/dark/surrealql-parse-error.png" alt="A screenshot showing how a string prefix allows incorrect UUID input to be identified before a query can be run. In this case, the parser is able to inform the user that an underscore at column 18 is the issue." />

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/uuids

# UUIDs

UUID values in SurrealQL represent UUID v4 and v7 values.

UUIDs represent UUID v4 and v7 values. They can be obtained via either the:
- [`rand::uuid::*` functions](/docs/reference/query-language/functions/database-functions/rand.md#randuuidv4)
- [casted from strings](/docs/reference/query-language/language-primitives/casting.md#uuid)
- or via [string prefixes](/docs/reference/query-language/language-primitives/data-types/strings.md#uuid-literal-values-with-the-u-prefix)

> [!NOTE]
> SurrealDB does not eagerly convert a string into a UUID. An implicit `u` prefix or cast using `<uuid>` is required instead.

```surql
rand::uuid::v4();
rand::uuid::v7();
<uuid> "a8f30d8b-db67-47ec-8b38-ef703e05ad1b";
u"a8f30d8b-db67-47ec-8b38-ef703e05ad1b";
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/values

# Values

Every type in SurrealDB is a value.

Each of the types mentioned in the data model is a subset of an all-encompassing type called a value.

## Comparing and ordering values

While it is unsurprising that a data type can be compared with itself, it may be surprising that different types can also be compared with each other.

```surql
9 > 1;            // Returns true
[] > time::now(); // Also returns true
```

This comparison is possible because every type in SurrealDB is a subset of value, and a comparison of any type with another is also simply a comparison of a value with another value. The order of values from least to greatest is:

* `none`
* `null`
* `bool`
* `number`
* `string`
* `duration`
* `datetime`
* `uuid`
* `array`
* `set`
* `object`
* `geometry`
* `bytes`
* `table`
* `record`
* `file`
* `regex`
* `range`

As a result, all of the following return `true`.

```surql
[
    null > none,
    true > null,
    1 > true,
    'a' > 999999999,
    1m > 'a',
    time::now() > 1m,
    rand::uuid() > time::now(),
    [ 9, 10 ] > rand::uuid(),
    { 9, 10 } > [ 9, 10 ],
    {} > { 9, 10 },
    (9.9, 9.9) > {},
    <bytes>"Aeon" > (9.9, 9.9),
    type::table("person") > <bytes>"Aeon",
    person:one > type::table("person"),
    f"file://myfile.txt" > person:one,
    <regex>"a|b" > f"file://myfile.txt",
    0..10 > <regex>"a|b",
    (|| 1) > 0..10
];
```

Being able to compare a value with any other value is what makes SurrealDB's record range syntax possible.

```surql
CREATE time_data:[d'2024-07-23T00:00:00.000Z'];
CREATE time_data:[d'2024-07-24T00:00:00.000Z'];
CREATE time_data:[d'2024-07-25T00:00:00.000Z'];
-- Records from the 24th to the 25th
SELECT * FROM time_data:[d'2024-07-24']..[d'2024-07-25'];
-- Records from the 24th
SELECT * FROM time_data:[d'2024-07-24']..;
-- All records
SELECT * FROM time_data:[NONE]..;
```

The `..` open-range syntax also represents an infinite value inside a record range query, making it the greatest possible value and the inverse of `NONE`, the lowest possible value. A part of a record range query that begins with `NONE` and ends with `..` will thus filter out nothing.

```surql
CREATE temperature:['London', d'2025-02-19T00:00:00.000Z'] SET val = 5.5;
CREATE temperature:['London', d'2025-02-20T00:00:00.000Z'] SET val = 5.7;

-- Return all records as long as index 0 = 'London'
SELECT * FROM temperature:['London', NONE]..=['London', ..];
```

```surql title="Output"
[
	{
		id: temperature:[
			'London',
			d'2025-02-19T00:00:00Z'
		],
		val: 5.5f
	},
	{
		id: temperature:[
			'London',
			d'2025-02-20T00:00:00Z'
		],
		val: 5.7f
	}
]
```

Inside a schema, the keyword `any` is used to denote any possible value.

```surql
DEFINE FIELD anything ON TABLE person TYPE any;
```

## Values and truthiness

Any value is considered to be truthy if it is not NONE, NULL, or a default value for the data type. A data type at its default value is one that is empty, such as an empty string or array or object, or a number set to 0.

The following example shows the result of the `array::all()` method, which checks to see if all of the items inside an array are truthy or not.

```surql
array::all(["", 1, 2, 3]); // false because of ""
array::all([{}, 1, 2, 3]); // false because of {}
array::all(["SurrealDB", { is_nice_database: true }, 1, 2, 3]);  // true
```

As [the ! operator](/docs/reference/query-language/language-primitives/operators.md) reverses the truthiness of a value, a doubling of this operator can also be used to check for truthiness.

```surql
[
    !!"Has a value", !!"",             // true, false
    !!true, !!false,                   // true, false
    !!{ is_nice_database: true }, !!{} // true, false
    ];
```

## Nesting depth

_(since v3.3.0)_

A value can be nested at most 256 levels deep. Each array, set, object and range adds a level, and so does the key of a record ID, so a compound ID such as `t:[[1]]` counts towards the same limit. The limit is fixed and cannot be configured.

A write that would store a deeper value fails with `The value is nested too deeply`, whether the value arrives in one statement or grows one level at a time through repeated updates. A deeper value that is already stored returns the same error when it is read. The [parsing depth limits](/docs/reference/cli/db/environment-variables.md#nesting-depth-in-queries-versus-data) are separate: they bound how deeply a query or a message may nest, while this limit applies to the value itself.

```surql
CREATE deep:1 SET v = 1;
-- Wrap the value in one more array on each pass
FOR $i IN 1..300 {
	UPDATE deep:1 SET v = [v];
};
```

```surql title="Output of the FOR loop"
'The value is nested too deeply. A value may be nested at most 256 levels; reduce the nesting or split the data across records'
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/formatters

# Formatters

Formatting functions in SurrealQL accept certain text formats for date/time formatting.

The [string::is_datetime](/docs/reference/query-language/functions/database-functions/string.md#stringis_datetime) and [time::format](/docs/reference/query-language/functions/database-functions/time.md#timeformat) functions in SurrealQL accept certain text formats for date/time formatting. The possible formats are listed below.

## Date formatters

<table>
<thead>
  <tr>
    <th scope="col">Specifier</th>
    <th scope="col">Example</th>
    <th scope="col">Description</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td scope="row" data-label="Specifier">%Y</td>
    <td scope="row" data-label="Example">2001</td>
    <td scope="row" data-label="Description">The full proleptic Gregorian year, zero-padded to 4 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%C</td>
    <td scope="row" data-label="Example">20</td>
    <td scope="row" data-label="Description">The proleptic Gregorian year divided by 100, zero-padded to 2 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%y</td>
    <td scope="row" data-label="Example">01</td>
    <td scope="row" data-label="Description">The proleptic Gregorian year modulo 100, zero-padded to 2 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%m</td>
    <td scope="row" data-label="Example">07</td>
    <td scope="row" data-label="Description">Month number (01 to 12), zero-padded to 2 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%b</td>
    <td scope="row" data-label="Example">Jul</td>
    <td scope="row" data-label="Description">Abbreviated month name. Always 3 letters.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%B</td>
    <td scope="row" data-label="Example">July</td>
    <td scope="row" data-label="Description">Full month name.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%h</td>
    <td scope="row" data-label="Example">Jul</td>
    <td scope="row" data-label="Description">Same as %b.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%d</td>
    <td scope="row" data-label="Example">08</td>
    <td scope="row" data-label="Description">Day number (01 to 31), zero-padded to 2 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%e</td>
    <td scope="row" data-label="Example">8</td>
    <td scope="row" data-label="Description">Same as %d but space-padded. Same as %_d.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%a</td>
    <td scope="row" data-label="Example">Sun</td>
    <td scope="row" data-label="Description">Abbreviated weekday name. Always 3 letters.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%A</td>
    <td scope="row" data-label="Example">Sunday</td>
    <td scope="row" data-label="Description">Full weekday name.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%w</td>
    <td scope="row" data-label="Example">0</td>
    <td scope="row" data-label="Description">Day of the week. Sunday = 0, Monday = 1, ..., Saturday = 6.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%u</td>
    <td scope="row" data-label="Example">7</td>
    <td scope="row" data-label="Description">Day of the week. Monday = 1, Tuesday = 2, ..., Sunday = 7. ([RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339))</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%U</td>
    <td scope="row" data-label="Example">28</td>
    <td scope="row" data-label="Description">Week number starting with Sunday (00 to 53), zero-padded to 2 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%W</td>
    <td scope="row" data-label="Example">27</td>
    <td scope="row" data-label="Description">Same as %U, but week 1 starts with the first Monday in that year instead.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%G</td>
    <td scope="row" data-label="Example">2001</td>
    <td scope="row" data-label="Description">Same as %Y but uses the year number in [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) week date.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%g</td>
    <td scope="row" data-label="Example">01</td>
    <td scope="row" data-label="Description">Same as %y but uses the year number in [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) week date.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%V</td>
    <td scope="row" data-label="Example">27</td>
    <td scope="row" data-label="Description">Same as %U but uses the week number in [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) week date (01 to 53).</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%j</td>
    <td scope="row" data-label="Example">189</td>
    <td scope="row" data-label="Description">Day of the year (001 to 366), zero-padded to 3 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%D</td>
    <td scope="row" data-label="Example">07/08/01</td>
    <td scope="row" data-label="Description">Month-day-year format. Same as %m/%d/%y.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%x</td>
    <td scope="row" data-label="Example">07/08/01</td>
    <td scope="row" data-label="Description">Locale's date representation.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%F</td>
    <td scope="row" data-label="Example">2001-07-08</td>
    <td scope="row" data-label="Description">Year-month-day format ([RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339)). Same as %Y-%m-%d.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%v</td>
    <td scope="row" data-label="Example">8-Jul-2001</td>
    <td scope="row" data-label="Description">Day-month-year format. Same as %e-%b-%Y.</td>
  </tr>
</tbody>
</table>

## Time formatters

<table>
<thead>
  <tr>
    <th scope="col">Specifier</th>
    <th scope="col">Example</th>
    <th scope="col">Description</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td scope="row" data-label="Specifier">%H</td>
    <td scope="row" data-label="Example">00</td>
    <td scope="row" data-label="Description">Hour number (00 to 23), zero-padded to 2 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%k</td>
    <td scope="row" data-label="Example">0</td>
    <td scope="row" data-label="Description">Same as %H but space-padded. Same as %_H.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%I</td>
    <td scope="row" data-label="Example">12</td>
    <td scope="row" data-label="Description">Hour number in 12-hour clocks (01 to 12), zero-padded to 2 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%l</td>
    <td scope="row" data-label="Example">12</td>
    <td scope="row" data-label="Description">Same as %I but space-padded. Same as %_I.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%P</td>
    <td scope="row" data-label="Example">am</td>
    <td scope="row" data-label="Description">am or pm in 12-hour clocks.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%p</td>
    <td scope="row" data-label="Example">AM</td>
    <td scope="row" data-label="Description">AM or PM in 12-hour clocks.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%M</td>
    <td scope="row" data-label="Example">34</td>
    <td scope="row" data-label="Description">Minute number (00 to 59), zero-padded to 2 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%S</td>
    <td scope="row" data-label="Example">60</td>
    <td scope="row" data-label="Description">Second number (00 to 60), zero-padded to 2 digits.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%f</td>
    <td scope="row" data-label="Example">026490000</td>
    <td scope="row" data-label="Description">The fractional seconds (in nanoseconds) since last whole second.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%.f</td>
    <td scope="row" data-label="Example">.026490</td>
    <td scope="row" data-label="Description">Similar to %f but left-aligned.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%.3f</td>
    <td scope="row" data-label="Example">.026</td>
    <td scope="row" data-label="Description">Similar to .%f but left-aligned but fixed to a length of 3.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%.6f</td>
    <td scope="row" data-label="Example">.026490</td>
    <td scope="row" data-label="Description">Similar to .%f but left-aligned but fixed to a length of 6.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%.9f</td>
    <td scope="row" data-label="Example">.026490000</td>
    <td scope="row" data-label="Description">Similar to .%f but left-aligned but fixed to a length of 9.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%3f</td>
    <td scope="row" data-label="Example">026</td>
    <td scope="row" data-label="Description">Similar to %.3f but without the leading dot.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%6f</td>
    <td scope="row" data-label="Example">026490</td>
    <td scope="row" data-label="Description">Similar to %.6f but without the leading dot.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%9f</td>
    <td scope="row" data-label="Example">026490000</td>
    <td scope="row" data-label="Description">Similar to %.9f but without the leading dot.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%R</td>
    <td scope="row" data-label="Example">00:34</td>
    <td scope="row" data-label="Description">Hour-minute format. Same as %H:%M.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%T</td>
    <td scope="row" data-label="Example">00:34:59</td>
    <td scope="row" data-label="Description">Hour-minute-second format. Same as %H:%M:%S.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%X</td>
    <td scope="row" data-label="Example">00:34:59</td>
    <td scope="row" data-label="Description">Locale's time representation.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%r</td>
    <td scope="row" data-label="Example">12:34:59 AM</td>
    <td scope="row" data-label="Description">Hour-minute-second format in 12-hour clocks. Same as %I:%M:%S %p.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%x</td>
    <td scope="row" data-label="Example">07/08/01</td>
    <td scope="row" data-label="Description">Locale's date representation.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%F</td>
    <td scope="row" data-label="Example">2001-07-08</td>
    <td scope="row" data-label="Description">Year-month-day format ([RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339)). Same as %Y-%m-%d.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%v</td>
    <td scope="row" data-label="Example">8-Jul-2001</td>
    <td scope="row" data-label="Description">Day-month-year format. Same as %e-%b-%Y.</td>
  </tr>
</tbody>
</table>

## Timezones formatters

<table>
  <thead>
    <tr>
      <th scope="col">Specifier</th>
      <th scope="col">Example</th>
      <th scope="col">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Specifier">%Z</td>
      <td scope="row" data-label="Example">ACST</td>
      <td scope="row" data-label="Description">Local time zone name.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Specifier">%z</td>
      <td scope="row" data-label="Example">+0930</td>
      <td scope="row" data-label="Description">Offset from the local time to UTC (with UTC being +0000).</td>
    </tr>
    <tr>
      <td scope="row" data-label="Specifier">%:z</td>
      <td scope="row" data-label="Example">+09:30</td>
      <td scope="row" data-label="Description">Same as %z but with a colon.</td>
    </tr>
  </tbody>
</table>

## Date & time formatters

<table>
<thead>
  <tr>
    <th scope="col">Specifier</th>
    <th scope="col">Example</th>
    <th scope="col">Description</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td scope="row" data-label="Specifier">%c</td>
    <td scope="row" data-label="Example">Sun Jul 8 00:34:59 2001</td>
    <td scope="row" data-label="Description">Locale's date and time.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%+</td>
    <td scope="row" data-label="Example">2001-07-08T00:34:59.026490+09:30</td>
    <td scope="row" data-label="Description">[RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) / RFC 3339 date &amp; time format.</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%s</td>
    <td scope="row" data-label="Example">994518299</td>
    <td scope="row" data-label="Description">UNIX timestamp, the number of seconds since 1970-01-01T00:00:00.</td>
  </tr>
</tbody>
</table>

## Other formatters

<table>
<thead>
  <tr>
    <th scope="col">Specifier</th>
    <th scope="col">Example</th>
    <th scope="col">Description</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td scope="row" data-label="Specifier">%t</td>
    <td scope="row" data-label="Example">-</td>
    <td scope="row" data-label="Description">Literal tab (\t).</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%n</td>
    <td scope="row" data-label="Example">-</td>
    <td scope="row" data-label="Description">Literal newline (\n).</td>
  </tr>
  <tr>
    <td scope="row" data-label="Specifier">%%</td>
    <td scope="row" data-label="Example">-</td>
    <td scope="row" data-label="Description">Literal percent sign.</td>
  </tr>
</tbody>
</table>

## Examples

Seeing if an input with a date and time conforms to an expected format:

```surql
string::is_datetime("5sep2024pm012345.6789", "%d%b%Y%p%I%M%S%.f");
```

```surql title="Output"
true
```

Another example with a different format:

```surql
string::is_datetime("23:56:00 2015-09-05", "%Y-%m-%d %H:%M");
```

```surql title="Output"
false
```

Using a formatter to generate a string from a datetime:

```surql
time::format(d"2021-11-01T08:30:17+00:00", "%Y-%m-%d");
```

```surql title="Output"
"2021-11-01"
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/idioms

# Idioms

Accessing and manipulating data using idioms (paths) in SurrealQL.

Idioms in SurrealQL use paths to access and manipulate data within records. They allow you to navigate through nested data structures, access fields and array elements, call methods, and perform complex queries. Idioms are similar to expressions in other query languages that provide a path to data within documents or records.

An idiom is composed of a sequence of **parts** that define the path to a value within a record or data structure. Each part specifies how to navigate to the next piece of data. Idioms can be used in various parts of SurrealQL. The most common use case is in data retrieval queries such as `SELECT` statements, but they can also be used in the `WHERE` clause, `SET` clause, and more.

An idiom is made up of one or more **parts**, each of which can be one of several types:

- [**Field**](#field-access): Access a field by name.
- [**Index**](#index-access): Access an element of an array by its index.
- [**All**](#all-elements): Access all elements or fields.
- [**Last**](#last-element): Access the last element of an array.
- [**Where**](#where-filter): Filter elements based on a condition.
- [**Method**](#method-chaining): Call a method on the current data.
- [**Graph**](#graph-navigation): Navigate through graph relationships.
- [**Destructure**](#destructuring): Destructure nested objects.
- [**Optional**](#optional-parts): Indicate that the following part is optional.
- [**Recurse**](#recursive-paths): Recursively traverse paths such as graph and record links.

Each part is documented below with examples.

## Field access

Since SurrealDB is a document database at its core, each record is stored on an underlying key-value store storage engine with the ability to store arbitrary arrays, objects, and many other types of data. To access a field in an object, use a dot `.` followed by the field name.

This is mostly helpful when accessing fields within a record, but can also be used to access fields within an array.

For example, using the `CREATE` statement to add a record into the `person` table:

```surql title="Query"
CREATE person CONTENT {
    name: "John Doe",
    age: 30,
    address: {
      city: "New York",
      country: "USA"
    }
};
```

```surql title="Output"
[
	{
		address: {
			city: 'New York',
			country: 'USA'
		},
		age: 30,
		id: person:g87bnds1gcgrnoj4p5q3,
		name: 'John Doe'
	}
]
```

To access the `city` field within the `address` object, you can use the following idiom:

```surql title="Query"
SELECT address.city FROM person;
```

```surql title="Output"
[
  {
    "address": {
      "city": "New York"
    }
  }
]
```

In this example, `person.name` is an idiom that accesses the `name` field of the `person` record.

## Index access

To access an element in an array by its index, use square brackets `[]` with the index inside. For example, take a `school` record with some student results.

```surql title="Query"
CREATE student SET results = [
	{ score: 76, date: "2017-06-18T08:00:00Z", name: "Algorithmics" },
	{ score: 83, date: "2018-03-21T08:00:00Z", name: "Concurrent Programming" },
	{ score: 69, date: "2018-09-17T08:00:00Z", name: "Advanced Computer Science 101" },
	{ score: 73, date: "2019-04-20T08:00:00Z", name: "Distributed Databases" },
];
```

```surql title="Output"
[
	{
		id: student:urxaykt4qkbr8rs2o68j,
		results: [
			{
				date: '2017-06-18T08:00:00Z',
				name: 'Algorithmics',
				score: 76
			},
			{
				date: '2018-03-21T08:00:00Z',
				name: 'Concurrent Programming',
				score: 83
			},
			{
				date: '2018-09-17T08:00:00Z',
				name: 'Advanced Computer Science 101',
				score: 69
			},
			{
				date: '2019-04-20T08:00:00Z',
				name: 'Distributed Databases',
				score: 73
			}
		]
	}
]
```

To access the first student in the `results` array, you can use the following idiom:

```surql
SELECT results[0].score FROM student;
```

```surql title="Output"
[
  {
    results: [
      { score: 76 }
    ]
  }
]
```

Here, `results[0].score` accesses the score of the first student in the `results` array.

Note: the opposite of `[0]` is `[$]`, which returns the last element in an array. See [Last element](#last-element) below.

### Object keys from a parameter

Square brackets also take a string, which reads or writes the object field with that name. The string can come from a parameter, so the key does not have to be known when the query is written. In an `UPDATE`, this sets or increments a field chosen at run time, and creates it if it does not exist yet:

```surql
CREATE stats:1 SET counts = { a: 1 };

LET $key = "a";
UPDATE stats:1 SET counts[$key] += 1;

LET $key = "b";
UPDATE stats:1 SET counts[$key] = 5;
```

```surql title="Output"
[
	{
		counts: { a: 2, b: 5 },
		id: stats:1
	}
]
```

## All elements

To access all elements in an array or all fields in an object, use `.*`. This is useful when you want to access all the elements in an array or all the fields in an object.

```surql 
SELECT results.* FROM student;
```

```surql title="Output"
{
	results: [
		{
			date: '2017-06-18T08:00:00Z',
			name: 'Algorithmics',
			score: 76
		},
		{
			date: '2018-03-21T08:00:00Z',
			name: 'Concurrent Programming',
			score: 83
		},
		{
			date: '2018-09-17T08:00:00Z',
			name: 'Advanced Computer Science 101',
			score: 69
		},
		{
			date: '2019-04-20T08:00:00Z',
			name: 'Distributed Databases',
			score: 73
		}
	]
};
```

This idiom selects all elements in the `score` array.

The `.*` idiom is often seen in definitions and error messages.

```surql
DEFINE FIELD friends ON TABLE person TYPE array<record<person>>;
INFO FOR TABLE person;
```

The output for `INFO FOR TABLE person` includes an automatically generated definition for `friends.*`, namely every item inside the `friends` field.

```surql
{
	events: {},
	fields: {
		friends: 'DEFINE FIELD friends ON person TYPE array<record<person>>
		  PERMISSIONS FULL',
		"friends.*": 'DEFINE FIELD friends.* ON person TYPE record<person>
		  PERMISSIONS FULL'
	},
	indexes: {},
	lives: {},
	tables: {}
};
```

### Using `.*` to return values

The `.*` idiom in SurrealDB allows you to target all values in an object or all entries in an array. It can be used in various contexts such as querying, field definitions, and data manipulation. This section explains the behaviour of `.*` with practical examples.

When `.*` is applied to `NONE`, `NULL`, or a scalar value, the result is `NONE`.

```surql
-- These three return NONE
NONE.*;
NULL.*;
"Some string".*;
```

In mixed arrays, `.*` fetches linked records but leaves non-record elements unchanged.

```surql
-- person:one does not exist yet,
-- will evaluate to NONE
[1, person:one, "x"].*;

CREATE person:one SET name = "Geralt";

-- person:one now exists,
-- will access record and fetch fields
[1, person:one, "x"].*;
```

```surql title="Output"
-------- Query 1 --------

[
	1,
	NONE,
	'x'
]

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

{
	id: person:one,
	name: 'Geralt'
}

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

[
	1,
	{
		id: person:one,
		name: 'Geralt'
	},
	'x'
]
```

### Accessing all values in an object

When applied to an object, `.*` returns an array containing all the values of the object's properties.

```surql
{ a: 1, b: 2 }.*;
```

**SurrealDB 3.x**

```surql title="Output"
{ a: 1, b: 2 }
```

**SurrealDB 2.x**

```surql title="Output"
[1, 2]
```

To see just the values of this object, the [`object::values()`](/docs/reference/query-language/functions/database-functions/object.md#objectvalues) function can be used.

```surql
{ a: 1, b: 2 }.values();
```

```surql title="Output"
[1, 2]
```

#### Defining fields with `.*`

You can define fields using `.*` to specify constraints or types for all properties within an object field.

```surql
DEFINE FIELD obj ON test TYPE object;
DEFINE FIELD obj.* ON test TYPE number;
```

Here, we define a field `obj` of type `object` on the `test` table, and then specify that all properties within `obj` (`obj.*`) must be of type `number`.

With this done, attempting to insert a non-number value into any property of `obj` will result in an error.

```surql
CREATE test:1 SET obj.a = 'a';

-- Error
"Couldn't coerce value for field `obj.*` of `test:1`: Expected `number` but found `'a'`"
```

#### Using `.*` in different contexts

Depending on where `.*` is used, it can have different effects on the order of operations.

For example, if we want to return all the properties of the `person:tobie` record, we can do the following:

```surql
CREATE ONLY person:tobie SET name = 'Tobie';
SELECT * FROM ONLY person:tobie.*;    -- This works
SELECT * FROM ONLY (person:tobie.*);  -- Equivalent to above
SELECT *
  FROM ONLY { id: person:tobie, name: 'Tobie' }; -- Equivalent to above
(SELECT * FROM ONLY person:tobie).*; -- Equivalent to above
```

```surql title="Output"
{
	id: person:tobie,
	name: 'Tobie'
}
```

## Last element

You can use `[$]` to access the last element of an array (the opposite of `[0]`, which selects the first). This works anywhere idiom access is supported: field paths, `SELECT` projections, and `RETURN` expressions.

```surql
[76, 83, 69, 73][$];
```

```surql title="Output"
73
```

Referring to the `student` record above, the following idiom returns the score of the latest result:

```surql
SELECT results[$].score FROM student;
```

```surql title="Output"
[
	{
		results: {
			score: 73
		}
	}
]
```

`[$]` applies to arrays and sets. Sets are ordered, but their index operator follows sort order rather than insertion order: `[0]` returns the smallest value and `[$]` returns the greatest. You can also use [`set::last()`](/docs/reference/query-language/functions/database-functions/set.md#setlast) on sets. See [sets](/docs/reference/query-language/language-primitives/data-types/sets.md#using-the-index-operator-on-sets) for detail.

## Flattening

`...` after an array flattens it by one level, in the same way as [`array::flatten()`](/docs/reference/query-language/functions/database-functions/array.md#arrayflatten). Each nested array is replaced by its elements, and elements that are not arrays stay where they are:

```surql
[[1, 2], 3, [[4]], []]...;
```

```surql title="Output"
[1, 2, 3, [4]]
```

Only one level is removed, so `[[4]]` becomes `[4]`. Repeat the part to remove more: `$value......` flattens twice. Unlike `.flatten()`, which returns an error for a value that is not an array, `...` returns any other value unchanged, including a [set](/docs/reference/query-language/language-primitives/data-types/sets.md).

A common use is to collect the values of an array field across several records into one array:

```surql
CREATE post:1 SET tags = ['a', 'b'];
CREATE post:2 SET tags = ['b', 'c'];

(SELECT VALUE tags FROM post)...;
```

```surql title="Output"
['a', 'b', 'b', 'c']
```

In a path that reads a field from each element of an array, the parts after that field apply to each element rather than to the whole result. `post:1.comments.tags...` flattens the `tags` of each comment separately, and so leaves the result nested. Put the path in parentheses to flatten the result as a whole:

```surql
CREATE post:3 SET comments = [{ tags: ['x'] }, { tags: ['y', 'z'] }];

post:3.comments.tags...;
//- [['x'], ['y', 'z']]

(post:3.comments.tags)...;
//- ['x', 'y', 'z']
```

At the end of a field in a [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md#unique-index) statement, `...` makes the index hold an array as one value rather than one entry for each element, so that a `UNIQUE` index compares whole arrays.

## Method chaining

To call a method on the current data, use a dot `.` followed by the method name and parentheses `()` with arguments. SurrealDB supports method chaining, so you can call multiple methods (functions) on the same data. Learn more about [method chaining](/docs/reference/query-language/functions/database-functions.md#method-syntax) in the functions section.

For example, the following creates a new `person` record and then calls `uppercase()` on its name field.

```surql
CREATE person:john CONTENT {
    name: "John Doe",
    age: 30,
    address: {
      city: "New York",
      country: "USA"
    }
};

SELECT *, name.uppercase() FROM person;
```

```surql title="Output"
[
	{
		address: {
			city: 'New York',
			country: 'USA'
		},
		age: 30,
		id: person:john,
		name: 'JOHN DOE'
	}
]
```

In the example above, `uppercase()` is a method called on `person.name` to convert it to uppercase. Although this method is called as `.uppercase()`, it is actually the [`string::uppercase()`](/docs/reference/query-language/functions/database-functions/string.md#stringuppercase) function that is called.

SurrealDB will automatically recognize that the idiom part `.uppercase()` refers to the `string::uppercase()` function and call this function when the query is executed. What this means is that the following two queries are equivalent:

```surql title="Using method chaining"
SELECT *, name.uppercase() FROM person;
```

```surql title="Using function"
SELECT *, string::uppercase(name) FROM person;
```

To learn more about string method chaining in SurrealQL, see the [string functions](/docs/reference/query-language/functions/database-functions/string.md#method-chaining) section.

## Optional parts

> [!NOTE]
> Until SurrealDB 3.0.0, this operator was a single `?` question mark. Since this version, it was changed to `.?` to avoid conflicts with the `??` operator when parsing.

The `.?` operator is used to indicate that a part is optional (it may not exist). It also allows you to safely access nested data without having to check if the nested data exists and exit an idiom path early when the result is NONE.

```surql
SELECT person.spouse.?.name FROM person;
```

This idiom safely accesses `person.spouse.name` if `spouse` exists; otherwise, it returns `NONE`.

## Using optional parts

If some `person` records have a `spouse` field and others do not:

```surql
SELECT name, spouse.?.name AS spouse_name FROM person;
```

This idiom will return `NONE` for `spouse_name` if the `spouse` field is not present.

## Graph navigation

SurrealDB can also be used in the context of graph databases, where data is stored and navigated using graph traversal idioms. The [`RELATE` statement](/docs/reference/query-language/statements/relate.md) is used to create relationships between records. This allows you to traverse related records efficiently without needing to pull data from multiple tables and merging that data together using SQL JOINs.

For example, let's consider the following data:

```surql title="Create a new planet, city, and explorer records"
CREATE planet:unknown_planet;
CREATE city:el_dorado          SET name = "El Dorado";
CREATE explorer:drake          SET name = "Drake";
CREATE explorer:local_guide    SET name = "Local Guide";

RELATE explorer:drake->discovered->planet:unknown_planet;
RELATE explorer:drake->visited->city:el_dorado;
RELATE explorer:local_guide->assisted->explorer:drake;

```

```surql title="Retrieve all relationships from Drake"
SELECT 
    *,
    ->? AS actions,
    <-? AS was,
    <->? AS involved_in
FROM explorer:drake;
```

```surql title="Output"
[
	{
		actions: [
			discovered:sh9zbsz5u705cxv6qgoi,
			visited:hmtttiqqfa4mt9is1a7j
		],
		involved_in: [
			assisted:1pv8k3p1wpuf0guf5bvm,
			discovered:sh9zbsz5u705cxv6qgoi,
			visited:hmtttiqqfa4mt9is1a7j
		],
		id: explorer:drake,
		was: [
			assisted:1pv8k3p1wpuf0guf5bvm
		],
		name: 'Drake'
	}
]
```

Explanation:

- `*`: Selects all fields of `explorer:drake`.
- `->? AS actions`: Retrieves all outgoing relationships from Drake and aliases them as actions.
- `<-? AS was`: Retrieves all incoming relationships to Drake and aliases them as was.
- `<->? AS involved_in`: Retrieves all relationships connected to Drake, regardless of direction, and aliases them as `involved_in`.

## Destructuring

When working with nested data, you can destructure objects using the `.` and `{ ... }` idioms.

For example,

```surql title="Create a new person record"
CREATE person:1 SET name = 'John', age = 21, obj = { a: 1, b: 2, c: { d: 3, e: 4, f: 5 } };
```

```surql title="Output"
[
	{
		age: 21,
		id: person:1,
		name: 'John',
		obj: {
			a: 1,
			b: 2,
			c: {
				d: 3,
				e: 4,
				f: 5
			}
		}
	}
]
```

```surql
SELECT obj.{ a, c.{ e, f } } FROM ONLY person:1;
```

```surql title="Output"
{
	obj: {
		a: 1,
		c: {
			e: 4,
			f: 5
		}
	}
}
```

You can also OMIT fields that you don't want to destructure using the `OMIT` clause.

```surql 
SELECT * OMIT obj.c.{ d, f } FROM ONLY person:1;
```

```surql title="Output"
[
	{
		age: 21,
		id: person:1,
		name: 'John',
		obj: {
			a: 1,
			b: 2,
			c: {
				e: 4
			}
		}
	}
]
```

Extending the example in the [Graph Navigation](#graph-navigation) section, we can use the `->` idiom to navigate through the graph and destructure the `city` field.

```surql
SELECT ->visited->city.{name, id}
FROM explorer:drake;
```

```surql title="Output"
[
	{
		"->visited": {
			"->city": [
				{
					id: city:el_dorado,
					name: 'El Dorado'
				}
			]
		}
	}
]
```

### Using aliases when destructuring

The keyword `AS` is necessary inside `SELECT` statements when [using an alias](/docs/reference/query-language/statements/select.md#basic-usage) (a new name for a field).

```surql
LET $town = {
    location: (50.0, -5.4),
    population: 500
};

SELECT 
	location,
	population AS num_people
FROM ONLY $town;
```

```surql title="Output"
{
	location: (50, -5.4),
	num_people: 500
}
```

However, as destructuring involves defining the output shape of a new object, no `AS` keyword is needed. Instead, only the names of the fields are needed. Aliasing is done by choosing a new name, a `:` (colon) and the path to the value.

```surql
LET $town = {
    location: (50.0, -5.4),
    population: 500
};

RETURN $town.{
    location,
    num_people: population
};
```

Conceptually, this is somewhat close to a `RETURN` statement.

```surql
LET $town = {
    location: (50.0, -5.4),
    population: 500
};

RETURN {
    location: $town.location,
    num_people: $town.population,
};
```

### Destructuring the current item in a SELECT query

The current record in a `SELECT` query can be accessed and destructured using the `@` operator.

```surql
CREATE star:sun SET name = "The Sun";
CREATE planet:earth SET name = "Earth";
RELATE planet:earth->orbits->star:sun;

-- Regular SELECT query
SELECT 
    name,
    id,
    ->orbits->star AS orbits
FROM planet;

-- SELECT query using `@` and destructuring
SELECT @.{
    name,
    id,
    orbits: ->orbits->star
} FROM planet;
```

While the difference between the two methods is often cosmetic - aside from the note on aliases mentioned just above - using `@` to access the current record does lead to a different style of query that may be preferable. While a regular `SELECT` query first returns an array of results that can then be operated on, a `SELECT` query that uses `@` to access the current record can perform these operations first.

```surql
-- Use the .values() method to turn each record into
-- an array of values, then return all inside an array
SELECT @.{
    name,
    id,
    orbits: ->orbits->star
}.values()
    FROM planet;

-- Grab all records first, then use .map() to convert
-- each one into an array of values
(SELECT 
    name,
    id,
    ->orbits->star AS orbits
FROM planet)
    .map(|$obj| $obj.values());
```

Most importantly, however, the `@` operator is often necessary when using [recursive paths](#recursive-paths).

### Using expressions while destructuring

_(since v3.0.0)_

While the fields inside a destructuring operation have always been accessible, expressions were not. As of version `3.0.0.beta`, this limitation no longer exists.

```surql
CREATE person:one SET name = "Aeon";

person:one.{
    name,
	-- worked because 'name' can be accessed
    name_length: name.len(),
	-- an expression: did not work before, works now
    accessed_at: time::now()
};
```

```surql title="Output"
{
	accessed_at: d'2025-04-24T05:11:20.101Z',
	name: 'Aeon',
	name_length: 4
}
```

Expressions inside a destructuring operation have the same [predefined parameters](/docs/reference/query-language/language-primitives/parameters.md#parent-this) as any other expression, such as `$this` to the current object and `$parent` to the previous one.

```surql
CREATE person:one SET age = 18;
CREATE person:two SET age = 40;
CREATE person:three SET age = 18;

-- Find all 'person' records of the same age as 'person:one'
-- Here 'person:one' is the $parent of the inner operation
person:one.{
	id,
    age,
    same_age: SELECT * FROM person WHERE age = $parent.age
};

-- Now use array::complement to filter out the 'person:one' current record,
-- which is the parameter $this
person:one.{
	id,
    age,
    same_age: array::complement(SELECT * FROM person
      WHERE age = $parent.age, [$this])
};
```

```surql title="Output"
-------- Query --------

{
	age: 18,
	id: person:one,
	same_age: [
		{
			age: 18,
			id: person:one
		},
		{
			age: 18,
			id: person:three
		}
	]
}

-------- Query --------

{
	age: 18,
	id: person:one,
	same_age: [
		{
			age: 18,
			id: person:three
		}
	]
}
```

## Recursive paths

A recursive path allows record link or graph traversal down to a specified depth, as opposed to manually putting together a query to navigate down each level.

Using recursive graph traversal can be thought of as the equivalent of "show me all the third-generation descendants of Mr. Brown" as opposed to "show me the children and children's children and children's children's children of Mr. Brown".

The following shows a recursive query that returns the names of people known by records that the record `person:tobie` knows.

```surql
-- Get all names of people second to Tobie
person:tobie.{2}(->friends_with->person).name;
```

Other graph databases call these queries variable-length patterns or k-hop queries. Cypher's `(a)-[:knows*1..3]->(b)`, which gives every record one to three hops away, is `.{1..3+collect}` in SurrealQL. Without `+collect`, a range returns only the records at the deepest level it reaches:

```surql
CREATE person:a, person:b, person:c, person:d;
RELATE person:a->knows->person:b;
RELATE person:b->knows->person:c;
RELATE person:c->knows->person:d;

-- Every record one to three hops away
person:a.{1..3+collect}->knows->person;
//- [person:b, person:c, person:d]

-- Only the records at the deepest level reached
person:a.{1..3}->knows->person;
//- [person:d]
```

The same queries can also be written in [GQL](/docs/learn/querying/gql/overview.md), whose variable-length quantifier is `->{1,3}`.

As the syntax of recursive queries tends to be complex to the untrained eye, this section will explain them in order of difficulty, beginning with what queries were necessary before recursive paths were added in SurrealDB version 2.1.

### Overview

Take the following example that creates one planet, two countries, two states/provinces in each of these countries, and two cities in each of those states/provinces. The `CREATE` statements are followed by `UPDATE` statements to set record links between them, and `RELATE` to create bidirectional graph relations between them.

```surql
CREATE
	-- One planet
	planet:earth,
	-- Two countries
	country:us, country:canada,
	-- Four states/provinces
	state:california, state:texas,
	province:ontario, province:bc,
	-- Eight cities
	city:los_angeles, city:san_francisco,
	city:houston,     city:dallas,
	city:vancouver,   city:victoria,
	city:toronto,     city:ottawa;

-- Give them each names like 'earth', 'us', 'bc', etc.
UPDATE planet, country, state, province, city SET name = id.id();

-- Record and graph links from planet to country
UPDATE planet:earth     SET next = [country:us, country:canada];
RELATE planet:earth     ->has->    [country:us, country:canada];

-- Record and graph links from country to state/province
UPDATE country:us       SET next = [state:california, state:texas];
UPDATE country:canada   SET next = [province:ontario, province:bc];
RELATE country:us       ->has->    [state:california, state:texas];
RELATE country:canada   ->has->    [province:bc, province:ontario];

-- Record and graph links from state/province to city
UPDATE state:california SET next = [city:los_angeles, city:san_francisco];
UPDATE state:texas      SET next = [city:houston, city:dallas];
UPDATE province:ontario SET next = [city:toronto, city:ottawa];
UPDATE province:bc      SET next = [city:vancouver, city:victoria];
RELATE state:california ->has->    [city:los_angeles, city:san_francisco];
RELATE state:texas      ->has->    [city:houston, city:dallas];
RELATE province:bc      ->has->    [city:vancouver, city:victoria];
RELATE province:ontario ->has->    [city:toronto, city:ottawa];
```

While traversing each of these paths can be done manually, it requires a good deal of typing and knowing the exact depth to traverse.

Here is an example using record links:

```surql
SELECT 
	next AS countries,
	next.next AS states_provinces,
	next.next.next AS cities
FROM planet:earth;
```

```surql title="Output"
[
	{
		cities: [
			[
				[
					city:los_angeles,
					city:san_francisco
				],
				[
					city:houston,
					city:dallas
				]
			],
			[
				[
					city:toronto,
					city:ottawa
				],
				[
					city:vancouver,
					city:victoria
				]
			]
		],
		countries: [
			country:us,
			country:canada
		],
		states_provinces: [
			[
				state:california,
				state:texas
			],
			[
				province:ontario,
				province:bc
			]
		]
	}
]
```

And here is an example using graph links.

```surql
SELECT 
	-- Show all `country` records located at `out`
	->has->country AS countries,
	-- Show all `province` or `state` records located at `out`	
	->has->country->has->(province, state) AS state_provinces,
	-- Or use (?) to show any type of record located at `out`
	->has->(?)->has->(?)->has->(?) AS cities
FROM planet:earth;
```

```surql title="Output"
[
	{
		cities: [
			city:toronto,
			city:ottawa,
			city:vancouver,
			city:victoria,
			city:dallas,
			city:houston,
			city:los_angeles,
			city:san_francisco
		],
		countries: [
			country:canada,
			country:us
		],
		state_provinces: [
			province:ontario,
			province:bc,
			state:texas,
			state:california
		]
	}
]
```

### Basics of recursive paths

Using a recursive path allows you to instead set the number of steps to follow instead of manually typing. A recursive path is made by isolating `{}` braces in between two dots, inside which the number of steps is indicated.

```surql
-- Two steps down the record links at the `next` field
planet:earth.{2}.next;
-- Two steps down the `has` graph relation
planet:earth.{2}->has->(?);
```

```surql title="Output"
[
	state:california,
	state:texas,
	province:ontario,
	province:bc
]
```

The number of steps can be any integer from 1 to 256.

```surql
planet:earth.{0}->has->(?);
//- 'Found 0 for bound but expected at least 1.'
planet:earth.{500}->has->(?);
//- 'Found 500 for bound but expected 256 at most.'
```

A range can be inserted into the braces to indicate a desired minimum and maximum depth.

```surql
-- Returns [] because no 4th-level relations exist
planet:earth.{4}->has->(?);
-- Returns `city` records located at depth 3
planet:earth.{1..4}->has->(?);
-- Open-ended range: also returns `city` records at depth 3
planet:earth.{..}->has->(?);
```

```surql title="Output"
[
	city:toronto,
	city:ottawa,
	city:vancouver,
	city:victoria,
	city:dallas,
	city:houston,
	city:los_angeles,
	city:san_francisco
]
```

### Using () to provide instructions at each depth

Parentheses can be added to a recursive query. To explain their use, consider the following example that attempts to traverse up to a depth of 3 and return the `name` of the records at that level.

```surql
planet:earth.{1..3}->has->(?).name;
```

Unfortunately, the output shows that the query stopped at a depth of one. This is because the query is instructing the database to recurse the entire `->has->(?).name` path between 1 and 3 times, but after the first recursion it has reached a string. And a string on its own is of no use in a `->has->(?)` graph query which expects a record ID.

```surql title="Output"
[
	'canada',
	'us'
]
```

In fact, the above query is equivalent to the following statement which encloses `->has->(?).name` in parentheses.

```surql
planet:earth.{1..3}(->has->(?).name);
```

To make the query work, we can shrink the area enclosed in the parentheses to `->has->(?)`, isolating the part to recurse before moving on to `.name`. It will repeat as many times as instructed and only then move on to the `name` field.

```surql
planet:earth.{1..3}(->has->(?)).name;
```

```surql title="Output"
[
	'toronto',
	'ottawa',
	'vancouver',
	'victoria',
	'dallas',
	'houston',
	'los_angeles',
	'san_francisco'
]
```

The syntax for the query above can be broken down as follows.

```surql
-- starting point
planet:earth
-- desired depth
	.{1..3}
-- instructions for current document
	(->has->(?))
-- leftover idiom path
	.{name, id};
```

### Using `@` to refer to the current record

The `@` symbol is used in recursive queries to refer to the current document. This is needed in recursive `SELECT` queries, as without it there is no way to know the context.

```surql title="Unparsable queries"
-- Parse error: what is the `.` referring to?
-- DB: "Call recursive query on a `planet`? Its `name` field? Something else?"
SELECT .{1..3}(->has->(?)) FROM planet;

-- A similar query that can't be parsed
-- DB: "Call .len() on what?"
SELECT .len() FROM planet;
```

Adding `@` allows the parser to know that the current `planet` record is the starting point for the rest of the query.

```surql title="Parsable queries"
-- Will now call `.{1..3}(has->(?))` on every planet record it finds
SELECT @.{1..3}(->has->(?)) AS cities FROM planet;
-- Will now call `.len()` on every `name` field it finds
SELECT name.len()           AS length FROM planet;
```

### Using `{}` and `.@` to combine results

Inside the structure of a recursive graph query, the `@` symbol is used in the form of `.@` at the end of a path to inform the database that this is the path to be repeated during the recursion. This allows not just the fields on the final depth of the query to be returned, but each one along the way as well.

```surql
planet:earth
	.{1..2}
	.{
		name, 
		id,
-- Query with ->has->(?) on the current record
		contains: ->has->(?).@
	};
```

```surql title="Output"
{
	contains: [
		{
			contains: [
				province:ontario,
				province:bc
			],
			id: country:canada,
			name: 'canada'
		},
		{
			contains: [
				state:texas,
				state:california
			],
			id: country:us,
			name: 'us'
		}
	],
	id: planet:earth,
	name: 'earth'
}
```

The following two rules of thumb explain how the syntax inside the structure of the query works.

- The individual fields inside a recursive query are simply populated at each point,
- The field with `.@` is used as the gateway to the next depth.

To see this visually, here is the unfolded output of the query above. The `name` and `id` fields appear at each point, while `contains` is used to move on to the next depth.

```surql
-- Original query
planet:earth.{1..2}.{ name, id, contains: ->has->(?).@ };

-- Unfolds to:
planet:earth
	.{
		name, 
		id,
		contains: ->has->(?).{
		  name, 
		  id,
		  contains: ->has->(?)
	    }
	};
```

Similarly, only one `.@` can be present inside such a query, as this is the path that is used to follow the recursive query until the end.

```surql
planet:earth
	.{1..2}
	.{
		name, 
		id,
-- Query with ->has->(?) on the current record
		contains: ->has->(?).@,
        contains2: ->has->(?).@
	};
```

```surql
'Tried to use a `@` repeat recurse symbol in a position where it is not supported'
```

Here are some more simple examples of recursive queries and notes on the output they generate.

```surql
INSERT INTO person [
	{ id: person:tobie, name: 'Tobie', friends: [person:jaime, person:micha] },
	{ id: person:jaime, name: 'Jaime', friends: [person:mary] },
	{ id: person:micha, name: 'Micha', friends: [person:john] },
	{ id: person:john, name: 'John' },
	{ id: person:mary, name: 'Mary' },
	{ id: person:tim, name: 'Tim' },
];

INSERT RELATION INTO knows [
	{ id: knows:1, in: person:tobie, out: person:jaime },
	{ id: knows:2, in: person:tobie, out: person:micha },
	{ id: knows:3, in: person:micha, out: person:john },
	{ id: knows:4, in: person:jaime, out: person:mary },
	{ id: knows:5, in: person:mary, out: person:tim },
];

-- Any depth
person:tobie.{..}(->knows->person).name;

-- Minimum 2, maximum 5 iterations of recursion (or either)
person:tobie.{2..6}(->knows->person).name;
person:tobie.{2..}(->knows->person).name;
person:tobie.{..6}(->knows->person).name;

-- Generate complex recursive tree structures:
-- Fetches connections up to 3 levels deep, 
-- collecting their name, id, and connections along the way
-- 3 levels, because the first iteration is used to collect
-- the details for person:tobie
person:tobie.{..4}.{ id, name, connections: ->knows->person.@ };

-- @ is a shortcut to the current document, and acts as a shorthand to start an idiom path.
-- The "." can optionally be omitted
SELECT @{1..4}(->knows->person).name AS names_2nds FROM person;

-- Recursive idioms work with any idiom parts, not limited to graphs
-- Here, we recursively fetch friends and then collect their names
person:tobie.{1..5}(.friend).name;
```

### Behaviour of recursive queries

Recursive queries follow a few rules to determine how far to traverse and what to return. They are:

- Every step must produce a record, or an array of records. A record link and a graph traversal both satisfy this, while a step that lands on any other value ends the query with `Expected a record ID during recursive traversal`. This is what a recursive path can follow, so a path that projects a field partway through, such as `person:one.{1..2}.likes.name`, fails on the second iteration (which begins at the string `name` as opposed to a record ID) rather than returning names.
- `NONE`, `NULL`, and arrays which are empty or contain only `NONE` and/or `NULL` are considered a dead end.
- An iteration with the same value as the previous one is also considered a dead end.
- If an iteration with a dead end does not reach the minimum depth, it returns `NONE`.
- If it has already passed the minimum depth, it returns the last valid value.
- During each iteration, if it encounters an array value, all dead end values are automatically filtered out, ensuring no empty paths are included.

### Filtering recursive fields

Recursive syntax is not just useful in creating recursive queries, but parsing them as well. Take the following example that creates some `person` records, gives each of them two friends, and then traverses the `friends_with` graph for the first `person` records to find its friends, friends of friends, and friends of friends of friends. Since every level except the last contains another `connections` field, adding a `.{some_number}.connections` to a `RETURN` statement is all that is needed to drill down to a certain depth.

```surql
CREATE |person:1..21| RETURN NONE;
FOR $person IN SELECT * FROM person {
    LET $friends = (SELECT * FROM person
      WHERE id != $person.id ORDER BY rand() LIMIT 2);
    RELATE $person->friends_with->$friends;
};

LET $third_degree = person:1.{..3}.{ id, connections: ->friends_with->person.@ };
-- Object containing array of arrays of arrays of 'person'
RETURN $third_degree;
-- All connections: an array of arrays of arrays of 'person'
RETURN $third_degree.connections;
-- Secondary connections: an array of arrays of 'person'
RETURN $third_degree.{2}.connections;
-- Tertiary connections: an array of 'person'
RETURN $third_degree.{3}.connections;
-- Tertiary connections with aliased fields and original 'person' info
RETURN $third_degree.{
		original_person: id, 
		third_degree_friends: connections.{2}.connections
};
```

Possible output of the final query:

```surql title="Output for third_degree_friends query"
{
	original_person: person:1,
	third_degree_friends: [
		person:13,
		person:3,
		person:14,
		person:10,
		person:8,
		person:3,
		person:3,
		person:14
	]
}
```

### Path and unique node collection, shortest path

SurrealDB has a number of built-in algorithms that allow recursive queries to collect all paths, all unique nodes, and to find the shortest path to a record. These can be used by adding the following keywords to the part of the recursive syntax that specifies the depth to recurse:

- `{..+path}`: used to collect all walked paths.
- `{..+collect}`: used to collect all unique nodes walked.
- `{..+shortest=record:id}`: used to find the shortest path to a specified record id, such as `person:tobie` or `person:one`.

The originating (first) record is excluded from these paths by default. However, it can be included by adding `+inclusive` to the syntax above.

- `{..+path+inclusive}`
- `{..+collect+inclusive}`
- `{..+shortest=record:id+inclusive}`

To demonstrate the output of these three algorithms, take the following example showing a small network of friends. The network begins with `person:you`, followed by two friends (`person:friend1`, `person:friend2`), then three acquaintances known by these friends (`person:acquaintance1`, `person:acquaintance2`, `person:acquaintance3`), and finally a movie star (`person:star`) who is known by only one of the acquaintances.

```surql
CREATE 
	person:you, 
	person:friend1, person:friend2, 
	person:acquaintance1, person:acquaintance2, person:acquaintance3, 
	person:star;

-- You have two friends
RELATE person:you->knows->[person:friend1, person:friend2];
-- The first friend is shy and only knows one other person
RELATE person:friend1->knows->person:friend2;
-- The second friend is very social and knows many people you barely know
RELATE person:friend2->knows->[person:acquaintance1, person:acquaintance2, person:acquaintance3];
-- One of those people knows the movie star
RELATE person:acquaintance3->knows->person:star;
```

This representation of this small network of friends allows us to visualise the issues that these three algorithms solve. Using `+path` will output all of the possible paths from `person:you`, `+collect` will collect all of the records in this network, and `+shortest=person:star` will find the shortest path.

```text
‎
								  ┌───────►  person:friend1  
     ┌───►person:acquaintance1    │                                                                    
     │                │           │                                                
     │                │           ┼───►person:acquaintance2    person:star   
person:you            │           │                                 ▲        
     │                ▼           │                                 │        
     └────────► person:friend2────┤                                 │        
                                  └───►person:acquaintance3─────────┘                      
```

After specifying an algorithm to use, such as `{..+path}`, add the path that should be followed, in this case `->knows->person`.

#### +path

Adding `+path` will output all of the possible paths starting from `person:you`.

```surql
person:you.{..+path}->knows->person;
```

```surql title="Output"
[
	[
		person:friend2,
		person:acquaintance2
	],
	[
		person:friend2,
		person:acquaintance1
	],
	[
		person:friend1,
		person:friend2,
		person:acquaintance2
	],
	[
		person:friend1,
		person:friend2,
		person:acquaintance1
	],
	[
		person:friend2,
		person:acquaintance3,
		person:star
	],
	[
		person:friend1,
		person:friend2,
		person:acquaintance3,
		person:star
	]
]
```

#### +shortest

As the output of the previous example is fairly short, we can see that there are two ways to get from `person:one` to the movie star at `person:star`, one of which is one step shorter than the other.

To get the database to find the shortest path instead, change the algorithm to `+shortest=person:star`.

```surql
person:you.{..+shortest=person:star}->knows->person;
```

```surql title="Output"
[
	person:friend2,
	person:acquaintance3,
	person:star
]
```

The part after `+shortest` can also take a parameter if it is a record ID. The following example will return the same result as the previous one.

```surql
LET $you = SELECT VALUE id FROM ONLY person WHERE name = 'you' LIMIT 1;
LET $star = SELECT VALUE id FROM ONLY person WHERE name = 'star' LIMIT 1;
$you.{..+shortest=$star}->knows->person;
```

Where several paths have the same shortest length, `+shortest` returns only one of them. To get all of them, take the length of the shortest path and use it as the depth for `+path`, which then returns every path of exactly that many steps. Then keep the paths that end at the target. The depth must be a literal number, so this takes two queries. With one more edge added to the network above, two paths of three steps reach `person:star`:

```surql
RELATE person:friend1->knows->person:acquaintance3;

LET $shortest = person:you.{..+shortest=person:star}->knows->person;
$shortest.len();
//- 3

LET $paths = person:you.{3+path}->knows->person;
$paths.filter(|$p| $p.last() = person:star);
//- [[person:friend2, person:acquaintance3, person:star], [person:friend1, person:acquaintance3, person:star]]
```

The paths are stored in a parameter before filtering because a method written directly after a recursive path is applied inside the recursion, not to its result.

#### +collect

Using `+collect` will collect all of the unique collected records. As this collection is created by moving recursively one level at a time, the output will show the closest connections first and least close connections at the end.

```surql
person:you.{..+collect}->knows->person;
```

```surql title="Output"
[
	person:friend1,
	person:friend2,
	person:acquaintance2,
	person:acquaintance1,
	person:acquaintance3,
	person:star
]
```

#### +inclusive

Adding `+inclusive` will show the same output, except that the original `person:one` record will also be present.

```surql
person:you.{..+shortest=person:star+inclusive}->knows->person;
person:you.{..+collect+inclusive}->knows->person;
```

```surql title="Output"
-------- Query --------

[
	person:you,
	person:friend2,
	person:acquaintance3,
	person:star
]

-------- Query --------

[
	person:you,
	person:friend1,
	person:friend2,
	person:acquaintance2,
	person:acquaintance1,
	person:acquaintance3,
	person:star
]
```

#### Other notes

The unbounded syntax `..` can be replaced with a bounded range to ensure that the recursive query only goes down to a certain depth. For example, using `..2` with `+collect` will show all first- and second-degree relations starting from `person:you`:

```surql
person:you.{..2+collect}->knows->person;
```

```surql title="All first- and second-degree relations"
[
	person:friend1,
	person:friend2,
	person:acquaintance2,
	person:acquaintance1,
	person:acquaintance3
]
```

Doing the same with `+shortest=person:star` will return an empty array, because there is no path from `person:you` to `person:star` that only requires two hops.

```surql
person:you.{..2+shortest=person:star}->knows->person;
```

```surql title="Output"
[]
```

As shown in [a previous section](#using--to-provide-instructions-at-each-depth), parentheses can be used to show which path should be repeated during the recursion. After the path inside the parentheses, the destructuring operator, methods and so on can be used to modify the output. The query can also be written over multiple lines if desired.

```surql
-- Start with you
person:you
-- Get the shortest path
	.{..+shortest=person:star+inclusive}
-- by following ->knows->person
	(->knows->person)
-- then grab the names
	.name
-- and capitalize each one
	.map(|$n| $n.uppercase());
```

```surql title="Output"
[
	'YOU',
	'FRIEND2',
	'ACQUAINTANCE3',
	'STAR'
]
```

#### Do not use `.@` with algorithms

As these three methods use their own algorithms to follow a path, any attempt to construct your own path using `.@` will result in an error. For example, choosing `+path` along with a field `connections: ->knows->person.@` will return an error because `+path` on its own will use its own recursive planner to output every possible path as an array of arrays, while `->knowns->person.@` is an instruction to put together arrays of each record and the next result from the `->knows->person` path at any possible depth.

```surql
person:you.{..+path}.{
    id,
    connections: ->knows->person.@
};
```

```surql
'Can not construct a recursion plan when an instruction is provided'
```

Here is the output of both of these queries at a single depth to show the difference in output.

```surql
person:you.{..1}.{
    id,
    connections: ->knows->person.@
};

person:you.{..1+path}->knows->person;
```

```surql title="Output"
-------- Query --------

{
	connections: [
		person:friend2,
		person:friend1
	],
	id: person:you
}

-------- Query --------

[
	[
		person:friend2
	],
	[
		person:friend1
	]
]
```

#### Example using record links

As is the case with other recursive queries, these three algorithms can be used in the same way with any other path that can be repeated, such as record links. The following example shows the same network of friends as the one above, except that it uses record links instead of graph queries. To traverse these paths, a simple `.knows` is all that is required.

```surql
CREATE person:you SET knows = [person:friend1, person:friend2];
CREATE person:friend1 SET knows = [person:friend2];
CREATE person:friend2 SET knows = [person:acquaintance1, person:acquaintance2, person:acquaintance3];
CREATE person:acquaintance1, person:acquaintance2, person:star;
CREATE person:acquaintance3 SET knows = [person:star];

person:you.{..+shortest=person:star}.knows;
person:you.{..+path}.knows;
person:you.{..+collect}.knows;
```

## Combining idiom parts

Idioms can combine multiple parts to navigate complex data structures.

Suppose we have the following data:

```surql title="Create a new person record"
CREATE person:5 CONTENT {
    name: "Eve",
    friends: [
        {
            id: "person:6",
            name: "Frank",
            age: 25
        },
        {
            id: "person:7",
            name: "Grace",
            age: 19
        },
        {
            id: "person:8",
            name: "Heidi",
            age: 17
        }
    ]
};
```

```surql title="Output"
[
	{
		friends: [
			{
				age: 25,
				id: 'person:6',
				name: 'Frank'
			},
			{
				age: 19,
				id: 'person:7',
				name: 'Grace'
			},
			{
				age: 17,
				id: 'person:8',
				name: 'Heidi'
			}
		],
		id: person:5,
		name: 'Eve'
	}
]
```

To get the names of friends who are over 18:

```surql
SELECT friends[WHERE age > 18].name FROM person WHERE id = person:5;
```

```surql title="Output"
[
	{
		friends: {
			name: [
				'Frank',
				'Grace'
			]
		}
	}
]
```

## Notes on idioms

- **Chaining**: Idioms can be chained to traverse deeply nested structures.
- **Performance**: Be mindful of performance when using complex idioms; indexing fields can help.
- **NONE Safety**: Use optional parts (`?`) to handle `NONE` or missing data gracefully.
- **Methods**: Use built-in methods for data manipulation within idioms.
- **Type Casting**: Use type casting if necessary to ensure data is in the correct format.

## Best practices

- **Use Destructuring**: When selecting multiple fields, destructuring improves readability.
- **Limit Optional Parts**: Use optional parts judiciously to avoid masking data issues.
- **Validate Data**: Ensure data conforms to expected structures, especially when dealing with optional fields.
- **Index Fields**: Index fields that are frequently accessed or used in `WHERE` clauses for better performance.

## Summary

An idiom is a path built from parts: fields, indexes, filters, methods, graph traversals, destructuring, optional parts and recursion. These parts can be combined in a single path to access nested fields, filter arrays or traverse graph relationships.

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/operators

# Operators

A variety of operators in SurrealQL allow for complex manipulation of data, and advanced logic.

A variety of operators in SurrealQL allow for complex manipulation of data, and advanced logic.

<table>
	<thead>
		<tr>
			<th scope="col">Operator</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; gap: 0.5rem;">
					<a href="#-or-and">
						<code>&&</code>
					</a>
					<a href="#-or-and">
						<code>AND</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether both of two values are truthy
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; gap: 0.5rem;">
					<a href="#-or-or">
						<code>||</code>
					</a>
					<a href="#-or-or">
						<code>OR</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether either of two values is truthy
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#">
					<code>!</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Reverses the truthiness of a value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-1">
					<code>!!</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Determines the truthiness of a value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-2">
					<code>??</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether either of two values are truthy and not NULL
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-3">
					<code>?:</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether either of two values are truthy
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; gap: 0.5rem;">
					<a href="#-or-is">
						<code>=</code>
					</a>
					<a href="#-or-is">
						<code>IS</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Check whether two values are equal
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; gap: 0.5rem;">
					<a href="#-or-is-not">
						<code>!=</code>
					</a>
					<a href="#-or-is-not">
						<code>IS NOT</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Check whether two values are not equal
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-4">
					<code>==</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether two values are exactly equal
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-5">
					<code>?=</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether any value in a set is equal to a value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-6">
					<code>*=</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether all values in a set are equal to a value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#---">
					<code>~</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Compare two values for equality using fuzzy matching
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#---">
					<code>!~</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Compare two values for inequality using fuzzy matching
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#---">
					<code>?~</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether any value in a set is equal to a value using
				fuzzy matching
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#---">
					<code>*~</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether all values in a set are equal to a value using
				fuzzy matching
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-7">
					<code>&lt;</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether a value is less than another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-8">
					<code>&lt;=</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether a value is less than or equal to another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-9">
					<code>&gt;</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether a value is greater than another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-10">
					<code>&gt;=</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Check whether a value is greater than or equal to another
				value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-11">
					<code>+</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Add two values together
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-">
					<code>-</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Subtract a value from another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; gap: 0.5rem;">
					<a href="#-or-">
						<code>*</code>
					</a>
					<a href="#-or-">
						<code>×</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Multiply two values together
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; gap: 0.5rem;">
					<a href="#-or--1">
						<code>/</code>
					</a>
					<a href="#-or--1">
						<code>÷</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Divide a value by another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#-12">
					<code>**</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Raises a base value by another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; gap: 0.5rem;">
					<a href="#contains-or-">
						<code>CONTAINS</code>
					</a>
					<a href="#contains-or-">
						<code>∋</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether a value contains another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; gap: 0.5rem;">
					<a href="#containsnot-or-">
						<code>CONTAINSNOT</code>
					</a>
					<a href="#containsnot-or-">
						<code>∌</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether a value does not contain another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; gap: 0.5rem;">
					<a href="#containsall-or-">
						<code>CONTAINSALL</code>
					</a>
					<a href="#containsall-or-">
						<code>⊇</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether a value contains all other values
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; gap: 0.5rem;">
					<a href="#containsany-or-">
						<code>CONTAINSANY</code>
					</a>
					<a href="#containsany-or-">
						<code>⊃</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether a value contains any other value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; gap: 0.5rem;">
					<a href="#containsnone-or-">
						<code>CONTAINSNONE</code>
					</a>
					<a href="#containsnone-or-">
						<code>⊅</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether a value contains none of the following values
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem;">
					<a href="#inside-or--or-in">
						<code>INSIDE</code>
					</a>
					<a href="#inside-or--or-in">
						<code>IN</code>
					</a>
					<a href="#inside-or--or-in">
						<code>∈</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether a value is contained within another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem;">
					<a href="#notinside-or--or-not-in">
						<code>NOTINSIDE</code>
					</a>
					<a href="#notinside-or--or-not-in">
						<code>NOT IN</code>
					</a>
					<a href="#notinside-or--or-not-in">
						<code>∉</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether a value is not contained within another value
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem;">
					<a href="#allinside-or-">
						<code>ALLINSIDE</code>
					</a>
					<a href="#allinside-or-">
						<code>⊆</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether all values are contained within other values
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem;">
					<a href="#anyinside-or-">
						<code>ANYINSIDE</code>
					</a>
					<a href="#anyinside-or-">
						<code>⊂</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether any value is contained within other values
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem;">
					<a href="#noneinside-or-">
					<code>NONEINSIDE</code>
					</a>
					<a href="#noneinside-or-">
						<code>⊄</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether no value is contained within other values
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#outside">
					<code>OUTSIDE</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Checks whether a geometry type is outside of another
				geometry type
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<a href="#intersects">
					<code>INTERSECTS</code>
				</a>
			</td>
			<td scope="row" data-label="Description">
				Checks whether a geometry type intersects another geometry
				type
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem;">
					<a href="#matches">
						<code>@@</code>
					</a>
					<a href="#matches">
						<code>@[ref]@</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Checks whether the terms are found in a full-text indexed
				field
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Operator">
				<span style="display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem;">
					<a href="#knn">
						<code> &lt;|4|&gt; </code>
					</a>
					<a href="#knn">
						<code>&lt;|3,HAMMING| &gt;</code>
					</a>
				</span>
			</td>
			<td scope="row" data-label="Description">
				Performs a K-Nearest Neighbors (KNN) search to find a
				specified number of records closest to a given data point,
				optionally using a defined distance metric. Supports
				customising the number of results and choice of distance
				calculation method.
			</td>
		</tr>
	</tbody>
</table>

## `&&` or `AND`
The `and` operator checks whether both of two values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

```surql
10 AND 20 AND 30;
```

```surql title="Output"
30
```

<br />

## `||` or `OR`
The `or` operator checks whether either of two values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

```surql
0 OR false OR 10;
```

```surql title="Output"
10
```

<br />

## `!`
The `not` operator reverses the truthiness of a value.

```surql
!(TRUE OR FALSE);
//- false

!"Has a value";
//- false
```

Inside an `UPDATE`, `!` toggles a boolean field, because the right side of `SET` reads the value from before the statement:

```surql
CREATE light:one SET on = false;
UPDATE light:one SET on = !on;
```

```surql title="Output"
[{ id: light:one, on: true }]
```

<br />

## `!!`
The `not not` operator is simply an application of the `!` operator twice. It can be used to determine the truthiness of a value.

```surql
!!"Has a value";
```

```surql title="Output"
true
```

## `??`
The `null coalescing operator` checks whether either of two values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness) and not `NONE` or `NULL`.

```surql
NULL ?? 0 ?? false ?? 10;
```

```surql title="Output"
0
```

<br />

## `?:`
The `truthy coalescing operator` checks whether either of two values are [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

```surql
NULL ?: 0 ?: false ?: 10;
```

```surql title="Output"
10
```

<br />

## `=` or `IS`
The `equal` operator checks whether two values are equal.

```surql
true = "true";
```

```surql title="Output"
false
```

```surql
10 = "10";
```

```surql title="Output"
false
```

```surql
10 = 10.00;
```

```surql title="Output"
true
```
```surql
10 = "10.3";
```

```surql title="Output"
false
```

```surql
[1, 2, 3] = [1, 2, 3];
```

```surql title="Output"
true
```

```surql
[1, 2, 3] = [1, 2, 3, 4];
```

```surql title="Output"
false
```

```surql
{ this: "object" } = { this: "object" };
```

```surql title="Output"
true
```

```surql
{ this: "object" } = { another: "object" };
```

```surql title="Output"
false
```

<br />

## `!=` or `IS NOT`
The `not equal` operator checks whether two values are not equal.

```surql
10 != "15";
```

```surql title="Output"
true
```

```surql
10 != "test";
```

```surql title="Output"
true
```

```surql
[1, 2, 3] != [3, 4, 5];
```

```surql title="Output"
true
```

<br />

## `==`
The `exact` operator checks whether two values are exact. This operator also checks that each value has the same type.

```surql
10 == 10;
```

```surql title="Output"
true
```

```surql
10 == "10";
```

```surql title="Output"
false
```

```surql
true == "true";
```

```surql title="Output"
false
```

<br />

## `?=`
The `any equal` operator checks whether any value in an array equals another value.

```surql
[10, 15, 20] ?= 10;
```

```surql title="Output"
true
```

<br />

## `*=`
The `all equal` operator checks whether all values in an array equals another value.

```surql
[10, 10, 10] *= 10;
```

```surql title="Output"
true
```

<br />

## `~` `?~` `!~` `*~`
These operators used to compare two values for equality using fuzzy matching. They have been removed since 3.0 to avoid implicitly preferring one algorithm over another, as the type of fuzzy matching to use will depend on each individual case.

Please use the `string::similarity::*` functions instead:

```surql
let $threshold = 10;

string::similarity::smithwaterman("test text", "Test") > $threshold;
```

```surql title="Output"
true
```

<br />

## `<`
The `less than` operator checks whether a value is less than another value.

```surql
10 < 15;
```

```surql title="Output"
true
```

<br />

## `<=`
The `less than or equal` operator checks whether a value is less than or equal to another value.

```surql
10 <= 15;
```

```surql title="Output"
true
```

<br />

## `>`
The `greater than` operator checks whether a value is less than another value.

```surql
15 > 10;
```

```surql title="Output"
true
```

<br />

## `>=`
The `greater than or equal` operator checks whether a value is less than or equal to another value.

```surql
15 >= 10;
```

```surql title="Output"
true
```

<br />

## `+`
The `add` operator adds two values together.

```surql
10 + 10;
```

```surql title="Output"
20
```

```surql
"test" + " " + "this";
```

```surql title="Output"
"test this"
```

```surql
13h + 30m;
```

```surql title="Output"
13h30m
```

<br />

## `-`
The `subtract` operator subtracts a value from another value.

```surql
20 - 10;
```

```surql title="Output"
10
```

```surql
2m - 1m;
```

```surql title="Output"
1m
```

<br />

## `*` or `×`
The `multiply` operator multiplies a value by another value.

```surql
20 * 2;
```

```surql title="Output"
40
```

<br />

## `/` or `÷`
The `divide` operator divides a value by another value.

```surql
20 / 2;
```

```surql title="Output"
10
```

<br />

## `**`
The `power` operator raises a base value by another value.

```surql
20 ** 3;
```

```surql title="Output"
8000
```

<br />

## `CONTAINS` or `∋`
The `contains` operator checks whether a value contains another value.

```surql
[10, 20, 30] CONTAINS 10;
```

```surql title="Output"
true
```

```surql
"this is some text" CONTAINS "text";
```

```surql title="Output"
true
```

```surql
{
	type: "Polygon",
	coordinates: [[
		[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
		[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
		[-0.38314819, 51.37692386]
	]]
} CONTAINS (-0.118092, 51.509865);
```

```surql title="Output"
true
```

<br />

## `CONTAINSNOT` or `∌`
The `not contains` operator checks whether a value does not contain another value.

```surql
[10, 20, 30] CONTAINSNOT 15;
```

```surql title="Output"
true
```

```surql
"this is some text" CONTAINSNOT "other";
```

```surql title="Output"
true
```

```surql
{
	type: "Polygon",
	coordinates: [[
		[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
		[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
		[-0.38314819, 51.37692386]
	]]
} CONTAINSNOT (-0.518092, 53.509865);
```

```surql title="Output"
true
```

<br />

## `CONTAINSALL` or `⊇`
The `contains all` operator checks whether a value contains all of multiple values.

```surql
[10, 20, 30] CONTAINSALL [10, 20, 10];
```

```surql title="Output"
true
```

<br />

## `CONTAINSANY` or `⊃`
The `contains any` operator checks whether a value contains any of multiple values.

```surql
[10, 20, 30] CONTAINSANY [10, 15, 25];
```

```surql title="Output"
true
```

<br />

## `CONTAINSNONE` or `⊅`
The `contains none` operator checks whether a value contains none of multiple values.

```surql
[10, 20, 30] CONTAINSNONE [15, 25, 35];
```

```surql title="Output"
true
```

<br />

## `INSIDE` or `∈` or `IN`
The `inside` operator checks whether a value is contained within another value.

```surql
10 INSIDE [10, 20, 30];
```

```surql title="Output"
true
```

```surql
"text" INSIDE "this is some text";
```

```surql title="Output"
true
```

```surql
(-0.118092, 51.509865) INSIDE {
	type: "Polygon",
	coordinates: [[
		[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
		[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
		[-0.38314819, 51.37692386]
	]]
};
```

```surql title="Output"
true
```

This operator can also be used to check for the existence of a key inside an [object](/docs/reference/query-language/language-primitives/data-types/objects.md). To do so, precede `IN` with the field name as a string.

```surql
"name" IN {
    name: "Riga",
    country: "Latvia"
};
```

```surql title="Output"
true
```

`IN` can also be used with a record ID as long as the ID is expanded to include the fields. Both of the following queries will return `true`.

```surql
CREATE city:riga SET name = "Riga", country = "Latvia", population = 605273;

"name" IN city:riga.*;
"name" IN city:riga.{ name, country };
```

<br />

## `NOTINSIDE` or `∉` or `NOT IN`
The `not inside` operator checks whether a value is not contained within another value.

```surql
15 NOTINSIDE [10, 20, 30];
```

```surql title="Output"
true
```

```surql
"other" NOTINSIDE "this is some text";
```

```surql title="Output"
true
```

```surql
(-0.518092, 53.509865) NOTINSIDE {
	type: "Polygon",
	coordinates: [[
		[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
		[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
		[-0.38314819, 51.37692386]
	]]
};
```

```surql title="Output"
true
```

<br />

## `ALLINSIDE` or `⊆`
The `all inside` operator checks whether all of multiple values are contained within another value.

```surql
[10, 20, 10] ALLINSIDE [10, 20, 30];
```

```surql title="Output"
true
```

<br />

## `ANYINSIDE` or `⊂`
The `any inside` operator checks whether any of multiple values are contained within another value.

```surql
[10, 15, 25] ANYINSIDE [10, 20, 30];
```

```surql title="Output"
true
```

<br />

## `NONEINSIDE` or `⊄`
The `none inside` operator checks whether none of multiple values are contained within another value.

```surql
[15, 25, 35] NONEINSIDE [10, 20, 30];
```

```surql title="Output"
true
```

<br />

## `OUTSIDE`
The `outside` operator checks whether a geometry value is outside another geometry value.

```surql
(-0.518092, 53.509865) OUTSIDE {
	type: "Polygon",
	coordinates: [[
		[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
		[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
		[-0.38314819, 51.37692386]
	]]
};
```

```surql title="Output"
true
```

<br />

## `INTERSECTS`
The `intersects` operator checks whether a geometry value intersects another geometry value.

```surql
{
	type: "Polygon",
	coordinates: [[
		[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
		[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
		[-0.38314819, 51.37692386]
	]]
} INTERSECTS {
	type: "Polygon",
	coordinates: [[
		[-0.11123657, 51.53160074], [-0.16925811, 51.51921169],
		[-0.11466979, 51.48223813], [-0.07381439, 51.51322956],
		[-0.11123657, 51.53160074]
	]]
};
```

```surql title="Output"
true
```

<br />

## `MATCHES`
The `matches` operator checks whether the terms are found in a full-text indexed field.

```surql
SELECT * FROM book WHERE title @@ 'rust web';

[
	{
		id: book:1,
		title: 'Rust Web Programming'
	}
]
```
Using the matches operator with a reference checks whether the terms are found, highlights the searched terms, and computes the full-text score.

```surql
SELECT id,
		search::highlight('<b>', '</b>', 1) AS title,
		search::score(1) AS score
FROM book
WHERE title @1@ 'rust web'
ORDER BY score DESC;

[
	{
		id: book:1,
		score: 0.9227996468544006f,
		title: '<b>Rust</b> <b>Web</b> Programming'
	}
]
```

### `AND`, `OR`, and numeric operators inside `@@`

_(since v3.0.0)_

In addition to the `AND` keyword, the `OR` matches operator can also be used. This allows a single string to be compared against instead of needing to specify individual parts of the string.

```surql
CREATE document:1 SET text = "It is rare that I find myself penning a personal note in my chronicles.";
DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase;
DEFINE INDEX some_index ON document FIELDS text FULLTEXT ANALYZER simple;

-- @AND@ and @OR@: can use the entire string
SELECT * FROM document WHERE text @AND@ "personal rare";
SELECT * FROM document WHERE text @OR@ "personal nice weather today";

-- Separate AND and OR outside of matches operator:
-- Must specify parts of string to check for match
SELECT * FROM document WHERE text @@ "personal" AND text @@ "rare";
SELECT * FROM document WHERE text @@ "personal note";
SELECT * FROM document WHERE text @@ "personal"
  OR text @@ "nice weather today";
```

> [!WARNING]
> Before SurrealDB 3.3.1, the default query planner could ignore part of a condition with more than one `MATCHES`. When a full-text index served one `MATCHES` in an `AND`, the other `MATCHES` conditions in that `AND` were not checked, so `text @@ "personal" AND text @@ "rare"` could also return records that matched only `"personal"`. A `MATCHES` whose search text was read from a field, such as `$user.groups[0]` or `user:alice.groups[0]`, failed with `There was no suitable index supporting the expression`. Both are fixed in 3.3.1. If a query uses `MATCHES` to limit which records a user can see, check its results on earlier versions.

## `KNN`

K-Nearest Neighbors (KNN) is an algorithm used for classification or regression based on the closest data points in the feature space.

The efficiency and scalability of the KNN algorithm matter most when dealing with large datasets. Different implementations of KNN are tailored to optimise these aspects without compromising the accuracy of the results.

SurrealDB supports different K-Nearest Neighbors methods to perform KNN searches, each with unique requirements for syntax.
Below are the details for each method, including how to format your query with examples:

### Brute force method

Best for smaller datasets or when the highest accuracy is required.

```syntax title="SurrealQL Syntax"
<|K,DISTANCE_METRIC|>
```

- K: The number of nearest neighbors to retrieve.
- DISTANCE_METRIC: The metric used to calculate distances, such as EUCLIDEAN or MANHATTAN.

```surql
CREATE pts:3 SET point = [8,9,10,11];
SELECT id FROM pts WHERE point <|2,EUCLIDEAN|> [2,3,4,5];
```

### Approximate graph indexes

**HNSW**

#### HNSW

Recommended for very large datasets where speed is essential and some loss of accuracy is acceptable, **while the graph fits in memory**.

```syntax title="SurrealQL Syntax"
<|K,EF|>
```

- K: The number of nearest neighbors.
- EF: The size of the dynamic candidate list during the search, affecting the search's accuracy and speed.

```surql
CREATE pts:3 SET point = [8,9,10,11];
DEFINE INDEX mt_pts
  ON pts FIELDS point HNSW DIMENSION 4;
SELECT id FROM pts WHERE point <|10,40|> [2,3,4,5];
```

**DISKANN**

#### DISKANN

_(since v3.1.0)_

Recommended when embeddings are too large to keep an HNSW graph resident in RAM as the graph lives on disk with caching. Note that WASM targets do not support DISKANN, so use HNSW or brute force there.

The query syntax matches HNSW: use the approximate form `<|K, L|>` where the second value bounds the search candidate list (see [`DEFINE INDEX … DISKANN`](/docs/reference/query-language/statements/define/indexes.md#diskann-disk-based-approximate-nearest-neighbours) for defaults and supported `TYPE` / `DIST` combinations).

```syntax title="SurrealQL Syntax"
<|K,L|>
```

```surql
CREATE pts:3 SET point = [8,9,10,11];
DEFINE INDEX diskann_pts ON pts FIELDS point DISKANN DIMENSION 4 DIST EUCLIDEAN TYPE F32 DEGREE 8 L_BUILD 20;
SELECT id FROM pts WHERE point <|10,40|> [2,3,4,5];
```

### Combining a KNN search with a filter

A KNN search over an indexed field can be combined with additional `WHERE` conditions. When the vector field is indexed (HNSW or DISKANN), such a condition is pushed into the index search and evaluated *during* the graph traversal, so non-matching candidates are rejected before they occupy one of the `K` slots - rather than being filtered out after the neighbours have been retrieved.

You can confirm this by using the [`EXPLAIN`](/docs/reference/query-language/statements/explain.md) clause after a query to see its plan. Here, the condition appears as a `predicate` attribute on the `KnnScan` operator in  the output. See [Filtering through vector search](/docs/learn/data-models/vector-search/similarity-search.md#how-the-filter-is-applied) for a worked example.

<br /><br />

## Using the `ANY`/`ALL` operators for string indexes

_(since v2.4.0)_

An index on a string field is used by `=` and by `IN` with an array of values. The containment operators `CONTAINS`, `CONTAINSANY`, `ALLINSIDE` and `ANYINSIDE` return the same records on such a field, but they scan the table, because they only use an index defined on the elements of an array field (such as `tags.*`). `CONTAINS` on a string is a substring match. See [Using the `ANY`/`ALL` operators for string indexes](/docs/reference/query-language/statements/define/indexes.md#using-the-anyall-operators-for-string-indexes) for an example that shows both plans.

> [!NOTE]
> In SurrealDB 2.4 and later 2.x releases, `CONTAINSANY`, `ALLINSIDE` and `ANYINSIDE` with the values in an array used an index on a string field, and `CONTAINS` did not. From 3.0.0, use `=` or `IN` to query a string index.

## Types of operators, order of operations and binding power

To determine which operator is executed first, a concept called "binding power" is used. Operators with greater binding power will operate directly on their neighbours before those with lower binding power. The following is a list of all operator types from greatest to lowest binding power.

<table>
	<thead>
		<tr>
			<th scope="col">Operator name</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Type">
				`Unary`
			</td>
			<td scope="row" data-label="Description">
				The `Unary` operators are `!`, `+`, and `-`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`Nullish`
			</td>
			<td scope="row" data-label="Description">
				The `Nullish` operators are `?:` and `??`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`Range`
			</td>
			<td scope="row" data-label="Description">
				The `Range` operator is `..`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`Cast`
			</td>
			<td scope="row" data-label="Description">
				The `Cast` operator is `<type_name>`, with `type_name` a stand in for the type to cast into. For example, `<string>` or `<number>`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`Power`
			</td>
			<td scope="row" data-label="Description">
				The only `Power` operator is `**`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`MulDiv`
			</td>
			<td scope="row" data-label="Description">
				The `MulDiv` (multiplication and division) operators are `*`, `/`, `÷`, and `%`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`AddSub`
			</td>
			<td scope="row" data-label="Description">
				The `AddSub` (addition and subtraction) operators are `+` and `-`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`Relation`
			</td>
			<td scope="row" data-label="Description">
				The `Relation` operators are `<=`, `>=`, `∋`, `CONTAINS`, `∌`, `CONTAINSNOT`, `∈`, `INSIDE`, `∉`, `NOTINSIDE`, `⊇`, `CONTAINSALL`, `⊃`, `CONTAINSANY`, `⊅`, `CONTAINSNONE`, `⊆`, `ALLINSIDE`, `⊂`, `ANYINSIDE`, `⊄`, `NONEINSIDE`, `OUTSIDE`, `INTERSECTS`, `NOT`, and `IN`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`Equality`
			</td>
			<td scope="row" data-label="Description">
				The `Equality` operators are `=`, `IS`, `==`, `!=`, `*=`, `?=`, and `@`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`And`
			</td>
			<td scope="row" data-label="Description">
				The `And` operators are `&&` and `AND`.
			</td>
		</tr>
		<tr>
			<td scope="row" data-label="Type">
				`Or`
			</td>
			<td scope="row" data-label="Description">
				The `Or` operators are `||` and `OR`.
			</td>
		</tr>
	</tbody>
</table>

## Examples of binding power

The following samples show examples of basic operations of varying binding power. The original example is followed by the same example with the parts with higher binding power in parentheses, then the final expression after the first bound portion is calculated, and finally the output.

```surql title="MulDiv first, then AddSub"
1 + 3 * 4;
1 + (3 * 4);
-- Final expression
1 + 12;
-- Output
13
```

```surql title="Power first, then MulDiv"
2**3 * 3;
(2**3) * 3;
-- Final expression
8*3;
-- Output
24
```

```surql title="Unary first, then cast"
<string>-4;
<string>(-4);
-- Output
"-4"
```

```surql title="Cast first, then Power"
<number>"9"**9;
(<number>"9")**9;
-- Final expression
9**9;
-- Output
387420489
```

```surql title="AddSub first, then Relation"
"c" + "at" IN "cats";
("c" + "at") IN "cats";
-- Final expression
"cat" IN "cats";
-- Output
true
```

```surql title="And first, then Or"
true AND false OR true;
(true AND false) OR true;
-- Final expression
false OR true;
-- Output
true
```

```surql title="Unary, then Cast, then Power, then AddSub"
<decimal>-4**2+4;
((<decimal>(-4))**2)+4;
-- Output
20dec
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/parameters

# Parameters

Parameters can be used like variables to store a value which can then be used in a subsequent query.

Parameters can be used like variables to store a value which can then be used in subsequent queries. To define a parameter in SurrealQL, use the [`LET`](../surrealql/statements/let) statement. Parameter names must begin with a `$` (dollar sign) character.

## Defining parameters within SurrealQL

```surql
-- Define the parameter
LET $suffix = "Morgan Hitchcock";
-- Use the parameter
CREATE person SET name = "Tobie " + $suffix;
-- (Another way to do the same)
CREATE person SET name = string::join(" ", "Jaime", $suffix);
```

```surql title="Output"
[
    {
        "id": "person:3vs17lb9eso9m7gd8mml",
        "name": "Tobie Morgan Hitchcock"
    }
]

[
    {
        "id": "person:xh4zbns5mgmywe6bo1pi",
        "name": "Jaime Morgan Hitchcock"
    }
]
```

A parameter can store any value, including the result of a query.

```surql
-- Assuming the CREATE statements from the previous example
LET $founders = SELECT * FROM person;
$founders.{
    name,
    company
};
```

```surql title="Output"
[
	{
		company: 'SurrealDB',
		name: 'Jaime Morgan Hitchcock'
	},
	{
		company: 'SurrealDB',
		name: 'Tobie Morgan Hitchcock'
	}
]
```

## Scope of parameters

Parameters persist across the current connection, and thus can be reused between different namespaces and databases. In the example below, a created `person` record assigned to a parameter is reused in a query in a completely different namespace and database.

```surql
LET $billy = CREATE ONLY person:billy SET name = "Billy";
-- Fails as `person:billy` already exists
CREATE person CONTENT $billy;

USE NAMESPACE other_namespace;
USE DATABASE other_database;
-- Succeeds as `person:billy` does not yet
-- exist in this namespace and database
CREATE person CONTENT $billy;
```

Parameters can be defined using SurrealQL as shown above, or can be passed in using the client libraries as request variables.

## Redefining and shadowing parameters

Parameters in SurrealQL are immutable. The same parameter can be redefined using a `LET` statement.

```surql
LET $my_name = "Alucard";
LET $my_name = "Sypha";
RETURN $my_name;
```

```surql title="Output"
'Sypha'
```

Before SurrealDB 3.0, the `=` on its own was used as syntactic sugar for a `LET` statement. This has since been deprecated in order to make it clearer that parameters can be redeclared, but not modified.

**Before 3.X**

```surql
LET $my_name = "Alucard";
$my_name = "Sypha";
RETURN $my_name;
```

```surql title="Output"
'Sypha'
```

**Since 3.X**

```surql
LET $my_name = "Alucard";
$my_name = "Sypha";
RETURN $my_name;
```

```surql title="Output"
'There was a problem with the database: Parse error: Variable
  declaration without `let` is deprecated
 --> [4:1]
  |
4 | $my_name = "Sypha";
  | ^^^^^^^^^^^^^^^^^^^ replace with `let $my_name = ..`
'
```

If the parameter is redefined inside another scope, the original value will be shadowed. Shadowing refers to when a value is temporarily obstructed by a new value of the same name until the new scope has completed.

```surql
LET $nums = [
    [1,2],
    [3,4]
];

{
    LET $nums = $nums.flatten();
    -- Flattened into a single array,
    -- so $nums is shadowed as [1,2,3,4]
    RETURN $nums;
};

-- Returns the original unflattened $nums
RETURN $nums;
//- [[1,2], [3,4]]
```

Even a parameter defined using a [`DEFINE PARAM`](/docs/reference/query-language/statements/define/param.md) statement can be shadowed.

```surql
DEFINE PARAM $USERNAME VALUE "user@user.com";

LET $USERNAME = "some other email";
```

However, the parameter `$USERNAME` in this case is still defined as its original value, as can be seen via an [`INFO FOR DB`](/docs/reference/query-language/statements/info.md) statement.

```surql
{
	accesses: {},
	analyzers: {},
	apis: {},
	configs: {},
	functions: {},
	models: {},
	params: {
		USERNAME: "DEFINE PARAM $USERNAME VALUE 'user@user.com'
		  PERMISSIONS FULL"
	},
	tables: {},
	users: {}
}
```

As the shadowed `$USERNAME` parameter will persist over the length of the connection, the parameter `$USERNAME` will once again show up as its original defined value if the connection is discontinued and restarted.

## Defining parameters within client libraries

SurrealDB's client libraries allow parameters to be passed in as JSON values, which are then converted to SurrealDB data types when the query is run. The following example shows a variable being used within a SurrealQL query from the JavaScript library.

```javascript
let people = await surreal.query("SELECT * FROM article WHERE status INSIDE $status", {
	status: ["live", "draft"],
});
```

The JavaScript SDK can also take a variable written directly into the query text. With the `surql` template tag, each `${...}` value is sent as a parameter, not joined into the string, so the query is safe from injection. [Bound queries](/docs/reference/javascript/concepts/bound-queries.md) has more examples.

```javascript
import { surql } from "surrealdb";

const status = ["live", "draft"];
let people = await surreal.query(surql`SELECT * FROM article WHERE status INSIDE ${status}`);
```

## Reserved variable names

SurrealDB automatically predefines certain variables depending on the type of operation being performed. For example, `$this` and `$parent` are automatically predefined for subqueries so that the fields of one can be compared to another if necessary. In addition, the predefined variables `$access`, `$auth`, `$token`, and `$session` are protected variables used to give access to parts of the current database configuration and can never be overwritten.

```surql
LET $access = true;
LET $auth = 10;
LET $token = "Mytoken";
LET $session = rand::int(0, 100);
```

```surql title="Output"
-------- Query 1 --------

"'access' is a protected variable and cannot be set"

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

"'auth' is a protected variable and cannot be set"

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

"'token' is a protected variable and cannot be set"

-------- Query 4 --------

"'session' is a protected variable and cannot be set"
```

Other predefined variables listed below are not specifically protected, but should not be used in order to avoid unexpected behaviour.

### $access

Represents the name of the access method used to authenticate the current session.

```surql
IF $access = "admin" { SELECT * FROM account }
ELSE IF $access = "user" { SELECT * FROM $auth.account }
ELSE {}
```

### $action, $file, $target

These three parameters are used in the context of the permissions of a [`DEFINE BUCKET`](/docs/reference/query-language/statements/define/bucket.md) statement.

* `$action` represents the type of operation: one of "Put", "Get", "Head", "Delete", "Copy", "Rename", "Exists", and "List".
* `$file` represents the path to the file being accessed.
* `$target` represents the target file ref in copy/rename operations.

### $auth

Represents the currently authenticated record user.

```surql
DEFINE TABLE user SCHEMAFULL
    PERMISSIONS
        FOR select, update, delete WHERE id = $auth.id;
```

`$auth` holds the record ID of the signed-in record user, such as `user:ann`, and is `NONE` for a system user such as `root`. To get the user's record, follow the ID:

```surql
-- Signed in as the record user user:ann
$auth;
//- user:ann

$auth.*;
//- { email: 'ann@example.com', id: user:ann, name: 'Ann' }

$auth.name;
//- 'Ann'
```

The SDKs give the same record through their `info` method, which runs the [`info` RPC method](/docs/reference/rest-api/rpc-protocol.md#info). Both return only the fields the user is allowed to select, so give a field such as a password hash `PERMISSIONS FOR select NONE` to keep it out.

### $before, $after

Represent the values before and after a mutation on a field.

```surql
CREATE cat SET name = "Mr. Meow", nicknames = ["Mr. Cuddlebun"];
UPDATE cat SET nicknames += "Snuggles"
  WHERE name = "Mr. Meow" RETURN $before, $after;
```

```surql title="Output"
[
    {
        "after": {
            "id": "cat:6p71csv2zqianixf0dkz",
            "name": "Mr. Meow",
            "nicknames": [
                "Mr. Cuddlebun",
                "Snuggles"
            ]
        },
        "before": {
            "id": "cat:6p71csv2zqianixf0dkz",
            "name": "Mr. Meow",
            "nicknames": [
                "Mr. Cuddlebun"
            ]
        }
    }
]
```

`$before` and `$after` are also set in [`DEFINE EVENT`](/docs/reference/query-language/statements/define/event.md) clauses and in a [live query](/docs/reference/query-language/statements/live-select.md)'s `WHERE` clause and projection, where they hold the changed record before and after the write. `$before` is `NONE` for a created record, and `$after` is `NONE` for a deleted one.

### $event

Represents the type of table event triggered on an event. This parameter will be one of either `"CREATE"`, `"UPDATE"`, or `"DELETE"`.

```surql
DEFINE EVENT user_created ON TABLE user WHEN $event = "CREATE" THEN (
    CREATE log SET table = "user",
      event = $event,
      created_at = time::now()
);
```

`$event` is also set while a live query checks a changed record, so a live query can subscribe to some kinds of change only. The following live query sends a notification for each deleted record and none for creates or updates:

```surql
LIVE SELECT * FROM person WHERE $event = 'DELETE';
```

See [Filter by event](/docs/reference/query-language/statements/live-select.md#filter-by-event) for more examples.

### $input

Represents the initially inputted value in a field definition, as the value clause could have modified the $value variable.

```surql
CREATE city:london SET
    population = 8900000,
    year = 2019,
    historical_data = [];

INSERT INTO city [
    { id: "london", population: 9600000, year: 2023 }
]
ON DUPLICATE KEY UPDATE
-- Stick old data into historical_data
historical_data += {
    year: year,
    population: population
},
-- Then update current record with the new input using $input
population = $input.population,
year = $input.year;
```

```surql output="Response"
[
    {
        "historical_data": [
            {
                "population": 8900000,
                "year": 2019
            }
        ],
        "id": "city:london",
        "population": 9600000,
        "year": 2023
    }
]
```

### $parent, $this

`$this` represents the current record in a subquery, and `$parent` its parent.

```surql
CREATE user:1 SET name = "User1", member_of = "group1";
CREATE user:2 SET name = "User2", member_of = "group1";
CREATE user:3 SET name = "User3", member_of = "group1";
SELECT name, 
    (SELECT VALUE name FROM user WHERE member_of = $parent.member_of)
    AS group_members
    FROM user
    WHERE name = "User1";
```

```surql title="Output"
[
	{
		group_members: [
			'User1',
			'User2',
			'User3'
		],
		name: 'User1'
	}
]
```

```surql
INSERT INTO person (id, name) VALUES (1, "John Doe"),
  (2, "John Doe"),
  (3, "Jane Doe");
SELECT 
    *,
    (SELECT VALUE id FROM person WHERE $this.name = $parent.name) AS 
    people_with_same_name
    FROM person;
```

```surql title="Output"
[
	{
		id: person:1,
		name: 'John Doe',
		people_with_same_name: [
			person:1,
			person:2
		]
	},
	{
		id: person:2,
		name: 'John Doe',
		people_with_same_name: [
			person:1,
			person:2
		]
	},
	{
		id: person:3,
		name: 'Jane Doe',
		people_with_same_name: [
			person:3
		]
	}
]
```

### $reference

This parameter represents the reference in question inside an [`ON DELETE`](/docs/reference/query-language/language-primitives/record-references.md#specifying-deletion-behaviour) clause for record references.

### $request

This parameter represents the value of a request to a custom API defined using the [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) statement.

```surql
DEFINE API OVERWRITE "/test"
    FOR get, post 
        MIDDLEWARE
            api::timeout(1s)
        THEN {
            RETURN {
                status: 404,
                body: $request.body,
                headers: {
                    'bla': '123'
                }
            };
        };
```

The `$request` parameter may contain values at the following fields: `body`, `headers`, `params`, `method`, `query`, and `context`.

### $session

Represents values from the session functions as an object.

You can learn more about those values from the [security parameters](/docs/learn/security/authentication/users.md#session) section.

```surql
CREATE user SET 
    name = "Some User",
    on_database = $session.db;
```

```surql title="Output"
[
    {
        "id": "user:wa3ajflozlqoyurc4i4v",
        "name": "Some User",
        "on_database": "database"
    }
]
```

### $token

Represents values held inside the JWT token used for the current session.

You can learn more about those values from the [security parameters](/docs/learn/security/authentication/users.md#token) section.

```surql
DEFINE TABLE user SCHEMAFULL
  PERMISSIONS FOR select, update, delete, create
  WHERE $access = "users"
  AND email = $token.email;
```

### $value

Represents the value after a mutation on a field (identical to $after in the case of an event).

```surql
DEFINE EVENT email
  ON TABLE user WHEN $before.email != $after.email THEN (
    CREATE event SET 
        user = $value.id,
        time = time::now(),
        value = $after.email,
        action = 'email_changed'
);
```

In a live query, `$value` is the changed record after the write, the same as `$after`.

## Improvements to parameters and expressions in statements

_(since v3.0.0)_

Parameters and expressions have traditionally only been available in a limited fashion in SurrealQL statements. As of SurrealDB 3.0, parameters and expressions can be used in many places that were not possible before.

Some examples of this are:

### DEFINE statements

```surql
FOR $language IN ["en", "ja", "uk", "ie"] {
    DEFINE TABLE "language_" + $language SCHEMAFULL;
};

(INFO FOR DB).tables;
```

```surql title="Output"
{
	language_en: 'DEFINE TABLE language_en TYPE NORMAL SCHEMAFULL PERMISSIONS NONE',
	language_ie: 'DEFINE TABLE language_ie TYPE NORMAL SCHEMAFULL PERMISSIONS NONE',
	language_ja: 'DEFINE TABLE language_ja TYPE NORMAL SCHEMAFULL PERMISSIONS NONE',
	language_uk: 'DEFINE TABLE language_uk TYPE NORMAL SCHEMAFULL PERMISSIONS NONE'
}
```

### REMOVE statements

Parameterisation in `REMOVE` statements is particularly useful in the context of testing.

```surql
FOR $table IN ["test_user", "test_client"] {
    DEFINE TABLE $table;
    -- Do some tests
    REMOVE TABLE $table;
};
```

The following example shows an example of a test that might be performed using a `REMOVE FIELD` statement. Here, the `INFO FOR TABLE` statement is used to dynamically capture the defined fields of a table, followed by the [object::keys()](/docs/reference/query-language/functions/database-functions/object.md#objectkeys) function to retrieve each field as a string. The fields can then be removed one by one inside a `REMOVE FIELD` statement, with the time elapsed logged in a separate table.

```surql
DEFINE FIELD string_test ON test TYPE string;
DEFINE FIELD int_test ON test TYPE int;
DEFINE FIELD datetime_test ON test TYPE datetime;

CREATE |test:10000| SET 
    string_test = rand::string(10),
    int_test = rand::int(),
    datetime_test = rand::time()
RETURN NONE;

FOR $field IN (INFO FOR TABLE test).fields.keys() {
    LET $now = time::now();
    REMOVE FIELD $field ON test;
    LET $elapsed = time::now() - $now;
    CREATE log SET results = { field_name: $field,
      removed_in: $elapsed }
};
```

### The TIMEOUT clause in queries

```surql
DEFINE FUNCTION fn::get_timeout() -> duration {
    -- Do some HTTP call to get status
    -- Simulate the output with rand::enum() function
    rand::enum(100ms, 1s, 5s)
};

SELECT * FROM person TIMEOUT fn::get_timeout();
```

### The OMIT clause in queries

```surql
CREATE person SET name = "Galen", surname = "Pathwarden", age = 19;

SELECT * OMIT type::fields(["name", "id"]) FROM person;
```

```surql title="Output"
[
	{
		age: 19,
		surname: 'Pathwarden'
	}
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/record-links

# Record links

One of the most powerful features of SurrealDB is the ability to traverse from record-to-record without the need for traditional SQL JOINs. Each record ID points directly to a specific record in the database.

SurrealDB can traverse from record to record without the need for traditional SQL JOINs. Each record ID points directly to a specific record in the database, without needing to run a table scan query. Record IDs can be stored within other records, allowing them to be linked together.

## Creating a record
When you create a record without specifying the id, then a randomly generated id is created and used for the record id.

```surql
CREATE person SET name = 'Tobie';
```

```surql title="Output"
person:aio58g22n3upq16hsani
```

It's also possible to specify a specific record id when creating or updating records.

```surql
CREATE person:tester SET name = 'Tobie';
```

```surql title="Output"
person:tester
```

## Select directly off of record IDs

Because Record IDs are their own datatype in SurrealQL, you are able to select directly off of them.

```surql
CREATE person:tobie SET name = 'Tobie', email = 'tobie@surrealdb.com', opts.enabled = true;

-- Select the whole record
person:tobie.*;

-- Select specific fields
person:tobie.{ name, email };
```

## Storing record links within records

Records ids can be stored directly within other records, either as top-level properties, or nested within objects or arrays.

```surql
CREATE person:jaime SET name = 'Jaime', friends = [person:tobie, person:simon];
CREATE person:tobie SET name = 'Tobie', friends = [person:simon, person:marcus];
CREATE person:simon SET name = 'Simon', friends = [person:jaime, person:tobie];
CREATE person:marcus SET name = 'Marcus', friends = [person:tobie];
```

### Links stay within one database

A record ID holds a table and a key, but no namespace or database, so a link always points to a record in the database the query runs in. There is no syntax for a link to another database. A link that looks like one, such as `other_db_pet:1`, names a table called `other_db_pet` in the current database, and reads as `NONE` if that table has no such record:

```surql
CREATE owner:1 SET pet = other_db_pet:1;
SELECT pet.* FROM owner:1;
```

```surql title="Output"
[
	{
		pet: NONE
	}
]
```

Keep records that link to each other in the same database. Data that must stay in separate databases can store a plain value, such as the key, and look up the record after switching database with [`USE`](/docs/reference/query-language/statements/use.md).

Parameters are not tied to a database, so a value read in one database can be used after `USE` switches to another, or to another namespace. This applies to a parameter set with `LET` in the same query, and to a parameter set on the connection with the RPC [`let`](/docs/reference/rest-api/rpc-protocol.md#let) method, which lasts for the whole connection:

```surql
USE NS app DB main;
LET $pet_key = (SELECT VALUE pet_key FROM ONLY owner:1);

USE DB pets;
SELECT * FROM type::record('pet', $pet_key);
```

## Filtering by a field of a linked record

A query that would use a `JOIN` in SQL, such as finding the articles that have a certain tag, filters on a path through the link instead. The path reads the linked records while the `WHERE` clause runs, so no join or subquery is needed:

```surql
CREATE tag:rust SET name = 'rust';
CREATE tag:db SET name = 'databases';
CREATE article:one SET title = 'Ownership', tags = [tag:rust];
CREATE article:two SET title = 'Indexes', tags = [tag:db];
CREATE article:three SET title = 'SurrealDB in Rust', tags = [tag:rust, tag:db];

SELECT title, tags.name AS tags FROM article WHERE 'rust' IN tags.name;
```

```surql title="Output"
[
	{ tags: ['rust'], title: 'Ownership' },
	{ tags: ['rust', 'databases'], title: 'SurrealDB in Rust' }
]
```

`tags` holds an array of links, so `tags.name` is an array of names, such as `['rust', 'databases']`. A comparison with `=` tests the whole array against one string, which is never true, so `WHERE tags.name = 'rust'` returns no articles. An an operator that looks inside the array can be used:

```surql
-- Articles with the tag named 'rust'
SELECT VALUE title FROM article WHERE 'rust' IN tags.name;
//- ['Ownership', 'SurrealDB in Rust']

-- The same when the tag's record ID is known, without reading the tags
SELECT VALUE title FROM article WHERE tags CONTAINS tag:rust;
//- ['Ownership', 'SurrealDB in Rust']

-- Articles that have both tags
SELECT VALUE title FROM article WHERE tags.name CONTAINSALL ['rust', 'databases'];
//- ['SurrealDB in Rust']
```

For a link that holds a single record rather than an array, such as `author = person:tobie`, `=` works as expected: `WHERE author.name = 'Tobie'`. For relationships stored as graph edges instead of record links, see [Joining and querying related tables](/docs/build/migrating/from-other-databases/from-postgresql.md#joining-and-querying-related-tables).

## Fetching remote records from within records

Nested field traversal can be used to fetch the properties from the remote records, as if the record was embedded within the record being queried.

```surql
SELECT friends.name FROM person:tobie;
[
	{
		friends: {
			name: ["Simon", "Marcus"]
		}
	}
]
```

There is no limit to the number of remote traversals that can be performed in a query. Using `.` dot notation, SurrealDB does not differentiate between nested object properties, or remote records, and will fetch remote records asynchronously when needed for a query.

```surql
SELECT friends.friends.friends.name FROM person:tobie;
[
	{
		friends: {
			friends: {
				friends: {
					name: [
						[ ["Tobie", "Simon"], ["Simon", "Marcus"] ],
						[ ["Simon", "Marcus"] ]
					]
				}
			}
		}
	}
]
```

To return a whole linked record in place of its ID, add `.*` to the field, as in `SELECT name, billing_address.*, shipping_address.* FROM user`.

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/record-references

# Record references

Record references allow you to link records together, enabling you to traverse from one record to another.

A record reference is a link SurrealDB tracks in both directions, so the record being pointed at knows what points to it. This page covers adding a `REFERENCE` clause to a field, traversing references, and choosing what happens when a referenced record is deleted.

## Basic concepts

Reference tracking begins by adding a `REFERENCE` clause to any `DEFINE FIELD` statement, as long as the field is a top-level field of type `record` or array of records.

```surql
DEFINE FIELD comics ON person TYPE option<array<record<comic_book>>> REFERENCE;
-- Also works as `option` desugars to this syntax
DEFINE FIELD comics ON person TYPE array<record<comic_book>> | NONE REFERENCE;

-- `comics` field might not be a record, does not work
DEFINE FIELD comics ON person TYPE array<record<comic_book>> | string REFERENCE;
-- Not top-level field, does not work
DEFINE FIELD metadata.comics ON person TYPE array<record<comic_book>> REFERENCE;
```

This incoming record can then be picked up with the `<~` syntax that works in the same way that graph queries do.

```surql
DEFINE FIELD comics ON person TYPE option<array<record<comic_book>>> REFERENCE;
CREATE person:mat SET 
	name = "Mat", 
	comics = [comic_book:one];
CREATE person:nynaeve SET 
	name = "Nynaeve", 
	comics = [comic_book:one];
CREATE comic_book:one SET title = "Loki, God of Stories";

SELECT 
	*, 
	<~person AS owners
FROM comic_book;

SELECT 
	*, 
	<~person.{ id, name } AS owners
FROM comic_book;
```

```surql title="Output"
-------- Query --------

[
	{
		id: comic_book:one,
		owners: [
			person:mat,
			person:nynaeve
		],
		title: 'Loki, God of Stories'
	}
]

-------- Query --------

[
	{
		id: comic_book:one,
		owners: [
			{
				id: person:mat,
				name: 'Mat'
			},
			{
				id: person:nynaeve,
				name: 'Nynaeve'
			}
		],
		title: 'Loki, God of Stories'
	}
]
```

## Specifying linking tables

Incoming references can also be declared in a schema.

```surql
DEFINE FIELD comics ON person TYPE option<array<record<comic_book>>> REFERENCE;
DEFINE FIELD products ON publisher TYPE option<array<record<comic_book|book>>> REFERENCE;
DEFINE FIELD owners ON comic_book COMPUTED <~person;
DEFINE FIELD publishers ON comic_book COMPUTED <~publisher;

CREATE person:one, person:two SET comics = [comic_book:one];
CREATE publisher:one SET products = [comic_book:one, book:one];
CREATE comic_book:one SET title = "Loki, God of Stories";
SELECT * FROM comic_book;
```

```surql title="Output"
[
	{
		id: comic_book:one,
		owners: [
			person:one,
			person:two
		],
		publishers: [
			publisher:one
		],
		title: 'Loki, God of Stories'
	}
]
```

A computed field that picks up incoming references can be further narrowed down to specify not just the table name, but also the field name of the referencing record. This can be done by enclosing the part after `<~` in parentheses, adding the `FIELD` keyword and naming the field or fields via which incoming references will be shown.

```surql
DEFINE FIELD comics ON person TYPE option<array<record<comic_book>>> REFERENCE;
DEFINE FIELD borrowed_comics ON person TYPE option<array<record<comic_book>>> REFERENCE;
DEFINE FIELD owned_by ON comic_book COMPUTED <~(person FIELD comics);
DEFINE FIELD borrowed_by ON comic_book COMPUTED <~(person FIELD borrowed_comics);
DEFINE FIELD all_readers ON comic_book COMPUTED <~(person FIELD comics borrowed_comics);

CREATE person:one SET comics = [comic_book:one];
CREATE person:two SET borrowed_comics = [comic_book:one];
CREATE comic_book:one SET title = "Loki, God of Stories";
SELECT * FROM comic_book;
```

```surql title="Output"
[
	{
		all_readers: [ person:one, person:two ],
		borrowed_by: [ person:two ],
		id: comic_book:one,
		owned_by: [ person:one ],
		title: 'Loki, God of Stories'
	}
]
```

## Requiring the referenced record to exist

A `REFERENCE` clause does not check that the record being pointed at exists, so a field can hold a link to a record that was never created. To refuse such a value, add an `ASSERT` that calls [`record::exists()`](/docs/reference/query-language/functions/database-functions/record.md#recordexists) to the same field.

```surql
DEFINE TABLE user;
DEFINE FIELD author ON comment TYPE record<user> ASSERT record::exists($value) REFERENCE ON DELETE CASCADE;

CREATE user:alice;
CREATE ONLY comment:one SET author = user:alice;
//- { author: user:alice, id: comment:one }

CREATE comment:two SET author = user:missing;
//- 'Found user:missing for field `author`, with record `comment:two`, but field must conform to: record::exists($value)'
```

For a field that holds an array of records, the `ASSERT` receives the whole array, so check each element with `.all()`.

```surql
DEFINE FIELD tags ON post TYPE array<record<user>> ASSERT $value.all(|$v| record::exists($v)) REFERENCE;

CREATE post:one SET tags = [user:alice, user:missing];
//- 'Found [user:alice, user:missing] for field `tags`, with record `post:one`, but field must conform to: $value.all(|$v: any| record::exists($v))'
```

## Specifying deletion behaviour

When working with record links, it is very likely that you will want some behaviour to happen when a referencing link is deleted. Take the following example of a `person` who owns a `comic_book`, which is later deleted. Despite the deletion, a follow-up `SELECT * FROM person` still shows the comic book.

```surql
DEFINE FIELD comics ON person TYPE option<array<record<comic_book>>> REFERENCE;
DEFINE FIELD owned_by ON comic_book COMPUTED <~person;

CREATE comic_book:one SET title = "Loki, God of Stories";
CREATE person:one SET comics = [comic_book:one];
DELETE comic_book:one;
SELECT * FROM person;
```

```surql title="Output"
[
	{
		comics: [
			comic_book:one
		],
		id: person:one
	}
]
```

A query using `INFO FOR TABLE person` shows that the actual statement created using `REFERENCE` does not finish at this point, but includes the clause `ON DELETE IGNORE`. This is the default behaviour for references.

```surql
{
	events: {},
	fields: {
		comics: 'DEFINE FIELD comics ON person TYPE none | array<record<comic_book>> REFERENCE ON DELETE IGNORE PERMISSIONS FULL',
		"comics.*": 'DEFINE FIELD comics.* ON person TYPE record<comic_book> REFERENCE ON DELETE IGNORE PERMISSIONS FULL'
	},
	indexes: {},
	lives: {},
	tables: {}
}
```

This `ON DELETE` clause can be modified to have some other behaviour when a reference is deleted.

### ON DELETE IGNORE

As shown in the previous section, `ON DELETE IGNORE` is the default behaviour for references and this clause will be added automatically if not specified. It can be added manually to a statement to hint to others reading the code that this behaviour is desired.

```surql
-- Default, behaviour, so identical to:
-- DEFINE FIELD friends ON person TYPE option<array<record<person>>> REFERENCE;
DEFINE FIELD friends ON person TYPE option<array<record<person>>> REFERENCE ON DELETE IGNORE;
DEFINE FIELD friended_by ON person COMPUTED <~person;

CREATE person:one SET friends = [person:two];
CREATE person:two;
DELETE person:one;
person:two.*;
```

As the deletion of `person:one` is ignored when calculating the `friended_by` field, it will still show `person:one` even though the record itself has been deleted.

```surql
{
	friended_by: [
		person:one
	],
	id: person:two
}
```

### ON DELETE UNSET

`ON DELETE UNSET` will unset (remove) any linked records that are deleted. This can be thought of as the opposite of `ON DELETE IGNORE`.

```surql
DEFINE FIELD comments ON person TYPE option<array<record<comment>>> REFERENCE ON DELETE UNSET;
DEFINE FIELD author ON comment COMPUTED <~person;

CREATE person:one;
UPDATE person:one SET comments += (CREATE ONLY comment SET text = "Estonia is bigger than I expected!").id;
-- Give this one a parameter name so it can be deleted later
LET $comment = CREATE ONLY comment SET text = "I don't get the joke here?";
UPDATE person:one SET comments += $comment.id;
-- Now delete it
DELETE $comment;
-- Only one comment shows up for person:one now
person:one.comments.*.*;
```

```surql title="Output of person:one queries"
-------- Query --------

[
	{
		comments: [
			comment:gj1kb2e3tedn7kjcxxja,
			comment:6sztlhd6fhgc91dg2lby
		],
		id: person:one
	}
]

-------- Query --------

[
	{
		author: [
			person:one
		],
		id: comment:gj1kb2e3tedn7kjcxxja,
		text: 'Estonia is bigger than I expected!'
	}
]
```

### ON DELETE CASCADE

The `ON DELETE CASCADE` will cause a record to be deleted if any record it references is deleted. This is useful for records that should not exist if a record that links to them no longer exists.

```surql
DEFINE FIELD author ON comment TYPE record<person> REFERENCE ON DELETE CASCADE;
DEFINE FIELD comments ON person COMPUTED <~comment;

CREATE person:one;
CREATE comment:1 SET author = person:one, text = "5/10 for this blog post. The problems I have with it are...";
CREATE comment:2 SET author = person:one, text = "WOW! I never knew you could cut a rope with an arrow.";

-- Show all the details of comments for 'person:one'
person:one.comments.*.*;
DELETE person:one;
-- Comments no longer exist
SELECT * FROM comment;
```

```surql title="Output"
-------- Query --------

[
	{
		author: person:one,
		id: comment:1,
		text: '5/10 for this blog post. The problems I have with it are...'
	},
	{
		author: person:one,
		id: comment:2,
		text: 'WOW! I never knew you could cut a rope with an arrow.'
	}
]

-------- Query --------

[]

-------- Query --------

[]
```

#### Permissions during a cascade

A cascading delete runs the referencing record's own delete with permissions disabled, because referential integrity has to finish whatever the caller is allowed to do to that record. A record user can therefore cause a delete in a table whose `PERMISSIONS FOR delete` would refuse them directly.

That bypass stops at the referencing record. Where deleting it also removes a vertex's graph edges, the edge cascade runs with the caller's own permissions instead, so an edge table's `PERMISSIONS FOR delete` still decides whether those edges go. A caller cannot reach an edge table they may not delete from by deleting a vertex that cascades into it.

System users are unaffected by either rule: a root, namespace or database user holding the editor role is not subject to record permissions in the first place.

### ON DELETE REJECT

`ON DELETE REJECT` will outright make it impossible to delete a record that is referenced from somewhere else. For example, consider the case in which a house should not be demolished (deleted) until it has been disconnected from utilities such as gas, water, electricity, and so on. This can be simulated in a schema by adding a `REFERENCE ON DELETE REJECT` to the `utility` table, making it impossible for any `house` to be deleted if they link to it.

```surql
DEFINE FIELD connected_to ON utility TYPE option<array<record<house>>> REFERENCE ON DELETE REJECT;
DEFINE FIELD using ON house COMPUTED <~utility;

CREATE house:one;
CREATE utility:gas, utility:water SET connected_to = [house:one];
```

At this point, the `using` field on `house:one` automatically picks up the two references. Due to these references, the `house` record cannot be deleted.

```surql
house:one.*;
DELETE house:one;
```

```surql title="Output"
-------- Query --------

{
	id: house:one,
	using: [
		utility:gas,
		utility:water
	]
}

-------- Query --------

'Cannot delete `house:one` as it is referenced by `utility:gas` with an ON DELETE REJECT clause'
```

To delete the `house`, the `connected_to` references will first have to be removed.

```surql
UPDATE utility:gas   SET connected_to -= house:one;
UPDATE utility:water SET connected_to -= house:one;

DELETE house:one;
```

Note that an `ON DELETE UNSET` for a required field is effectively the same as an `ON DELETE REJECT`. In both of the following two cases, a `person` that has any referencing `comment` records will not be able to be deleted.

```surql
-- Non-optional field that attempts an UNSET when referencing 'person' is deleted
DEFINE FIELD author ON comment TYPE record<person> REFERENCE ON DELETE UNSET;
LET $person = CREATE ONLY person;
CREATE comment SET text = "Cats are so much better at climbing UP a tree than down! Lol", author = $person.id;
DELETE person;

-- Optional field which rejects the deletion of a referencing 'person'
DEFINE FIELD author ON comment TYPE option<record<person>> REFERENCE ON DELETE REJECT;
LET $person = CREATE ONLY person;
CREATE comment SET text = "Cats are so much better at climbing UP a tree than down! Lol", author = $person.id;
DELETE person;
```

The error message in these two cases will differ, but the behaviour is the same.

```surql
-------- Query --------

"An error occured while updating references for `person:97sfkadd56hqhimbf69m`: Couldn't coerce value for field `author` of `comment:kkigvk5knsoeg53p08n1`: Expected `record<person>` but found `NONE`"

-------- Query --------

'Cannot delete `person:3fm76xztvfab99eq780l` as it is referenced by `comment:ig0ogusbm64cier5ovv9` with an ON DELETE REJECT clause'
```

### ON DELETE THEN

The `ON DELETE THEN` clause allows for custom logic when a reference is deleted. This clause includes a parameter called `$this` to refer to the record in question, and `$reference` for the reference.

In the following example, a `person` record's `comments` field will remove any comments when they are deleted, but also add the same comment to a different field called `deleted_comments`.

```surql
DEFINE FIELD comments ON person TYPE option<array<record<comment>>> REFERENCE ON DELETE THEN {
    UPDATE $this SET
        deleted_comments += $reference,
        comments -= $reference;
};
DEFINE FIELD author ON comment COMPUTED <~person;

CREATE person:one SET comments += (CREATE ONLY comment SET text = "Estonia is bigger than I expected!").id;
LET $comment = CREATE ONLY comment SET text = "I don't get the joke here?";
UPDATE person:one SET comments += $comment.id;
DELETE $comment;
SELECT * FROM person:one;
```

```surql title="person:one before and after comment is deleted"
-------- Query --------

[
	{
		comments: [
			comment:lbeyh2icushpwo0ak5ux,
			comment:90tdnyoa14cge2ocmep7
		],
		id: person:one
	}
]

-------- Query --------

[
	{
		comments: [
			comment:lbeyh2icushpwo0ak5ux
		],
		deleted_comments: [
			comment:90tdnyoa14cge2ocmep7
		],
		id: person:one
	}
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/statements

# Statements

What SurrealQL statements are and how they structure work you send to the database.

In SurrealQL, a statement is a complete unit of work you send to the database: something the server can parse, execute, and respond to. For example, you might send statements to define a table, insert a record, or run a `SELECT` that returns records to your application.

Statements do different kinds of work. Some shape the schema (namespaces, tables, indexes, access rules). Others read or write data in the familiar sense. A third group handles control flow, and are used in transactions, conditionals, returning values from a block, and so on.

Execution context matters when running a statement. You run statements inside a session scoped to a namespace and database, with permissions that determine what succeeds. If something fails, the error message points at the statement boundary, which is why keeping statements readable is helpful when working on large projects or even just coming back to a statement after a long period of time.

For a grouped catalogue of every statement and links to full reference pages, see [SurrealQL statements overview](/docs/reference/query-language/statements/overview.md).

---

Source: https://surrealdb.com/docs/reference/query-language/language-primitives/transactions

# Transactions

Each statement within SurrealDB is run within its own transaction, or within client defined transactions that can contain multiple statements.

Each statement within SurrealDB is run within its own transaction by default. If a set of changes need to be made together, then groups of statements can be run together as a single transaction. If all of the statements within a transaction succeed, and the transaction is successful, then all of the data modifications made during the transaction are committed and become a permanent part of the database. If a transaction encounters errors and must be cancelled or rolled back, then any data modification made within the transaction is rolled back, and will not become a permanent part of the database.

## Starting a transaction

The `BEGIN` or `BEGIN TRANSACTION` statement starts a transaction in which multiple statements can be run together.

```surql title="Starting a transaction"
BEGIN [ TRANSACTION ];
```

The following query shows example usage of this statement.

```surql title="Example usage of BEGIN TRANSACTION"
-- Create a new account with the ID 'one' and set its initial balance to 135605.16
CREATE account:one SET balance = 135605.16;

-- Create another new account with the ID 'two' and set its initial balance to 91031.31
CREATE account:two SET balance = 91031.31;

-- Start a new database transaction. Transactions are a way to ensure multiple operations
-- either all succeed or all fail, maintaining data integrity.
BEGIN TRANSACTION;

-- Update the balance of account 'one' by adding 300.00 to the current balance.
-- This could represent a deposit or other form of credit on the balance property.
UPDATE account:one SET balance += 300.00;

-- Update the balance of account 'two' by subtracting 300.00 from the current balance.
-- This could represent a withdrawal or other form of debit on the balance property.
UPDATE account:two SET balance -= 300.00;

-- Finalise the transaction. This will apply the changes to the database. If there was an error
-- during any of the previous steps within the transaction, all changes would be rolled back and
-- the database would remain in its initial state.
COMMIT TRANSACTION;
```

## Committing a transaction

The [COMMIT](/docs/reference/query-language/statements/commit.md) statement is used to commit the statements within a transaction, ensuring that all data modifications become a permanent part of the database.

```surql title="Committing a transaction"
COMMIT [ TRANSACTION ];
```

The following query shows example usage of this statement.

```surql title="Example usage of COMMIT TRANSACTION"
-- Setup accounts
CREATE account:one SET balance = 135605.16;
CREATE account:two SET balance = 91031.31;

BEGIN TRANSACTION;

-- Move money
UPDATE account:one SET balance += 300.00;
UPDATE account:two SET balance -= 300.00;

-- Finalise all changes
COMMIT TRANSACTION;
```

## Cancelling a transaction

The [CANCEL](/docs/reference/query-language/statements/cancel.md) statement can be used to cancel the statements within a transaction, reverting or rolling back any data modification made within the transaction as a whole.

```surql title="Cancelling a transaction"
CANCEL [ TRANSACTION ];
```

The following query shows example usage of this statement.

```surql title="Example usage of CANCEL TRANSACTION"
-- Setup accounts
CREATE account:one SET balance = 135605.16;
CREATE account:two SET balance = 91031.31;

BEGIN TRANSACTION;

-- Move money
UPDATE account:one SET balance += 300.00;
UPDATE account:two SET balance -= 300.00;

-- Rollback all changes
CANCEL TRANSACTION;
```

## THROW to conditionally cancel a transaction

While transactions are automatically rolled back if an error occurs in any of its statements, [THROW](/docs/reference/query-language/statements/throw.md) can also be used to explicitly break out of a transaction at any point. `THROW` can be followed by any value which serves as the error message, usually a string.

```surql
CREATE account:one SET dollars =  100;
CREATE account:two SET dollars =  100;

LET $transfer_amount = 150;

BEGIN TRANSACTION;

UPDATE account:one SET dollars -= $transfer_amount;
UPDATE account:two SET dollars += $transfer_amount;
IF account:one.dollars < 0 {
    THROW "Insufficient funds, would have $" + <string>account:one.dollars + " after transfer"
};
COMMIT TRANSACTION;
SELECT * FROM account;
```

```surql title="Output when $transfer_amount set to 150"
'An error occurred: Insufficient funds, would have $-50 after transfer'
```

```surql title="Output when $transfer_amount set to 50"
[
	{
		dollars: 50,
		id: account:one
	},
	{
		dollars: 150,
		id: account:two
	}
]
```

## Nested transactions and savepoints

A transaction cannot contain another transaction, and SurrealQL has no savepoints to roll back part of one. A `BEGIN` inside an open transaction aborts the whole outer transaction, so none of its changes are written.

```surql
BEGIN;
CREATE account:three SET dollars = 100;
BEGIN;
CREATE account:four SET dollars = 100;
COMMIT;
COMMIT;
```

```surql title="Error output"
'Tried to start a transaction while another transaction was open'
'Cannot COMMIT: the transaction was aborted due to a nested BEGIN'
```

A transaction always commits or cancels as a whole. To keep the earlier steps when a later one fails, put them in separate transactions that each commit on their own.

## Transactions across several requests

A transaction written with `BEGIN` and `COMMIT` has to begin and end in the same query: one call to `query()` in an SDK, or one request to the HTTP `/sql` endpoint. When the query ends, a transaction that is still open is cancelled, and a `COMMIT` sent in a later request has no transaction to commit:

```surql
-- First request
BEGIN;
CREATE step:one;
```

```text title="Error output"
Missing COMMIT statement
```

```surql
-- Second request
COMMIT;
```

```text title="Error output"
Invalid statement: Cannot COMMIT without starting a transaction
```

To keep a transaction open while an application runs several queries, for example between migration steps that each need a result from the one before, use a client-side transaction from the SDK. It is started and finished by the client, and every query sent through it runs in the same transaction until the client commits or cancels it. Client-side transactions work over a WebSocket connection and with an embedded database, but not over HTTP. See [manual transactions in Rust](/docs/reference/rust/concepts/transaction.md) and [transactions in JavaScript](/docs/reference/javascript/concepts/transactions.md).

## See also

* [Transactions](/docs/learn/querying/concepts-and-guides/transactions.md)
* [Using transactions to test code for errors](/docs/learn/querying/concepts-and-guides/testing.md#using-manual-transactions-for-testing)

---

Source: https://surrealdb.com/docs/reference/query-language/scripting/arguments

# Arguments

Additional arguments can be passed in to the function from SurrealDB, and these are accessible as an array using the arguments object within the JavaScript function.

Additional arguments can be passed in to the function from SurrealDB. These are accessible as an array using the `arguments` object within the JavaScript function.

```surql 
-- Create a new parameter
LET $val = "SurrealDB";
-- Create a new parameter
LET $words = ["awesome", "advanced", "cool"];
-- Pass the parameter values into the function
CREATE article SET summary = function($val, $words) {
	const [val, words] = arguments;
	return `${val} is ${words.join(', ')}`;
};
```

```surql title="Output"
[
	{
		id: article:k59tbq3ivsdaf9nzryf5,
		summary: 'SurrealDB is awesome, advanced, cool'
	}
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/scripting/built-in-functions

# Built-in functions

Besides basic JavaScript utilities and classes for SurrealQL types, there are a handful of utilities built into the embedded scripting functions.

Besides basic JavaScript utilities and [classes for SurrealQL types](/docs/reference/query-language/scripting/type-conversion.md), there are a handful of utilities built into the embedded scripting functions.

<table>
  <thead>
    <tr>
      <th>Function</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="#async-fetchresource-options"><code>async fetch(resource, options)</code></a></td>
      <td>Full fledged fetch implementation closely matching the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">official specification</a>.</td>
    </tr>
    <tr>
      <td><a href="#async-querysurql"><code>async query(surql)</code></a></td>
      <td>Run SurrealQL subqueries from within the embedded scripting functions.</td>
    </tr>
    <tr>
      <td><a href="#async-valuevariable"><code>async value(variable)</code></a></td>
      <td>Retrieve values for SurrealQL variables from within the embedded scripting functions.</td>
    </tr>
  </tbody>
</table>

## `async fetch(resource, options)`

Full fledged fetch implementation closely matching the [official specification](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).

> [!NOTE]
> For complete documentation, please refer to the MDN documentation.

<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2">
                <code>resource</code>
                <label label="required" />
            </td>
            <td colspan="2">
                Accepts either a url in a string, or a URL or Request object.
            </td>
        </tr>
         <tr>
            <td colspan="2">
                <code>options</code>
            </td>
            <td colspan="2">
                Accepts various options related to the request. Refer to MDN docs for a full reference.
            </td>
        </tr>
    </tbody>
</table>

```surql
function() {
	// List all posts
	const posts = fetch('https://jsonplaceholder.typicode.com/posts');

	// Update post with ID 1
	const updated = fetch('https://jsonplaceholder.typicode.com/posts/1',
	  {
		method: 'PUT',
		body: JSON.stringify({
			id: 1,
			title: 'foo',
			body: 'bar',
			userId: 1,
		}),
		headers: {
			'Content-type': 'application/json; charset=UTF-8',
		},
	});

	return { posts, updated };
}
```

<br />

## `async query(surql)`

Run SurrealQL subqueries from within the embedded scripting functions.

> [!NOTE]
> Only subqueries can be executed with the query() function. This means that only a single query can currently be executed, and that only CRUD operations are allowed.

<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2">
                <code>surql</code>
                <label label="required" />
            </td>
            <td colspan="2">
                Accepts a single SurrealQL query, which is limited to a CRUD operation.
            </td>
        </tr>
    </tbody>
</table>

```surql
CREATE user:john, user:mary;

RETURN function() {
	// Select all users
	const users = await surrealdb.query("SELECT * FROM user");

	// Prepared query
	const query = new surrealdb.Query("SELECT * FROM $id", {
		id: new Record('user', 'mary')
	});

	// Execute prepared query
	const mary = (await surrealdb.query(query))[0];

	// Assign variables later to prepared query
	query.bind('id', new Record('user', 'john'));

	// Execute prepared query
	const john = (await surrealdb.query(query))[0];

	return { john, mary };
}
```

<br />

## `async value(variable)`

Retrieve values for SurrealQL variables from within the embedded scripting functions.

<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2">
                <code>variable</code>
                <label label="required" />
            </td>
            <td colspan="2">
                Accepts the path to a variable
            </td>
        </tr>
    </tbody>
</table>

```surql
LET $something = 123;
LET $obj = {
	nested: 456
};

LET $arr = [
	{ value: 1 },
	{ value: 2 },
	{ value: 3 },
	{ value: 4 },
	{ value: 5 },
	{ value: 6 },
];

RETURN function() {
	// Get the value for a variable
	const something = await surrealdb.value("$something");

	// Get the value for a nested property
	const nested = await surrealdb.value("$obj.nested");

	// Filter properties from an array
	const fromArray = await surrealdb.value("$arr[WHERE value >
	  3].value");

	return { something, nested, fromArray };
}
```

```surql title="Output"
{
	fromArray: [
		4,
		5,
		6
	],
	nested: 456,
	something: 123
}
```

<br /><br />

## `console`

The `console` object writes to the server log, which is the output of the `surreal db start` process. Each method logs at the matching level, and the arguments are converted to strings and joined with spaces.

| Method                              | Log level |
| ----------------------------------- | --------- |
| `console.log()`, `console.info()`   | `info`    |
| `console.warn()`                    | `warn`    |
| `console.error()`                   | `error`   |
| `console.debug()`                   | `debug`   |
| `console.trace()`                   | `trace`   |

The default log level is `info`, so `console.debug()` and `console.trace()` produce output only when the server is started with a more verbose [`--log`](/docs/reference/cli/db/commands/start.md) level. Nothing is returned to the client, and the output does not appear in the result of the query.

```surql
CREATE x SET v = function() {
	console.log('hello from js');
	console.warn('warned');
	console.debug('not shown at the default level');
	return 1;
};
```

```text title="Server log"
INFO surrealdb_core::fnc::script::globals::console: hello from js
WARN surrealdb_core::fnc::script::globals::console: warned
```

---

Source: https://surrealdb.com/docs/reference/query-language/scripting/function-context

# Function context

Embedded scripting functions inherit the context in which they are ran in. The this context of every embedded function is automatically set to the current document on every invocation.

Embedded scripting functions inherit the context in which they are run. The `this` context of every embedded function is set to the current record on every invocation, which allows the function to read the fields of the record being updated.

```surql
CREATE film:dune SET ratings = [
	{ rating: 6, user: user:one },
	{ rating: 8, user: user:two }
];

UPDATE film:dune SET featured = function() {
	return this.ratings.filter(
		({ rating }) => rating >= 7
	).map(({ rating, ...data }) => {
		return {
			...data,
			rating: rating * 10
		};
	});
};
```

```surql title="Output"
[
	{
		featured: [
			{ rating: 80f, user: user:two }
		],
		id: film:dune,
		ratings: [
			{ rating: 6, user: user:one },
			{ rating: 8, user: user:two }
		]
	}
]
```

`this` holds the record as it was before the statement, the same image that every other expression in a `SET` clause reads. A record that is being created has no earlier image, so inside a `CREATE` statement `this` is `undefined`, and reading a field from it fails with `cannot read property 'ratings' of undefined`. Instead, create the record first and set the scripted field with `UPDATE`, as above.

## Linked records

A record link in `this` is a record ID, not the record it points to, so a path such as `this.owner.name` is `undefined` in JavaScript. To use fields of a linked record, pass them to the function as arguments. Arguments are SurrealQL expressions, so the path follows the links before the function runs, and the function receives the values in `arguments`:

```surql
CREATE tool:hammer SET name = 'Hammer';
CREATE person:one SET name = 'Ann', tool = tool:hammer;
CREATE car:one SET owner = person:one;

UPDATE car:one SET label = function(owner.name, owner.tool.name) {
	const [name, tool] = arguments;
	return `${name} with a ${tool}`;
};
```

```surql title="Output"
[
	{
		id: car:one,
		label: 'Ann with a Hammer',
		owner: person:one
	}
]
```

Where the value only combines fields, a [computed field](/docs/reference/query-language/statements/define/field.md) does the same without scripting, and stays current when the linked records change:

```surql
DEFINE FIELD label ON car COMPUTED owner.name + ' with a ' + owner.tool.name;
```

---

Source: https://surrealdb.com/docs/reference/query-language/scripting/overview

# Scripting functions overview

SurrealDB allows for advanced functions with complicated logic, by allowing embedded functions to be written in JavaScript.

SurrealDB allows for advanced functions with complicated logic, by allowing embedded functions to be written in JavaScript. These functions support the ES2020 JavaScript specification.

## Setup

To allow scripting functions to be used, the `--allow-scripting` flag must be passed in when using the `surreal db start` command to start the database.

## Simple function

Embedded JavaScript functions within SurrealDB support all functionality in the ES2020 specification including async / await functions, and generator functions. Any value from SurrealDB is converted into a JavaScript type automatically, and the return value from the JavaScript function is converted to a SurrealDB value.

```surql
CREATE person SET scores = function() {
	return [1,2,3].map(v => v * 10);
};
```

```surql title="Output"
[
	{
		id: person:zju99ptfepm87ylqf2h2,
		scores: [
			10,
			20,
			30
		]
	}
]
```

## Bitwise operators

SurrealQL has no bitwise operators and no `math::bitand`-style functions. The `array::boolean_*` functions work on the truthiness of elements, not on the bits of an integer. Where bitwise logic is needed, an [embedded scripting function](#setup) can apply the JavaScript operators `&`, `|`, `^` and `<<`, and a `DEFINE FUNCTION` can wrap the call so that queries use a plain function name.

```surql
DEFINE FUNCTION fn::bitand($a: int, $b: int) -> int {
	function($a, $b) { return arguments[0] & arguments[1]; }
};

DEFINE FUNCTION fn::bitor($a: int, $b: int) -> int {
	function($a, $b) { return arguments[0] | arguments[1]; }
};

[fn::bitand(12, 10), fn::bitor(12, 10)];
```

```surql title="Output"
[8, 14]
```

The server must be started with `--allow-scripting`, otherwise the call fails with `Scripting functions are not allowed`. JavaScript applies bitwise operators to 32-bit signed integers, so bits above the 32nd are dropped: `fn::bitor(4294967296, 1)` returns `1`.

## In this section

- [Arguments](/docs/reference/query-language/scripting/arguments.md) - pass values from the surrounding query into the function
- [Function context](/docs/reference/query-language/scripting/function-context.md) - what `this` is bound to, and what the function inherits
- [Type conversion](/docs/reference/query-language/scripting/type-conversion.md) - how SurrealDB values arrive in JavaScript and come back
- [Built-in functions](/docs/reference/query-language/scripting/built-in-functions.md) - the JavaScript utilities and SurrealDB classes available inside a function
- [SurrealQL functions](/docs/reference/query-language/scripting/surrealql-functions.md) - call native SurrealQL functions from JavaScript

---

Source: https://surrealdb.com/docs/reference/query-language/scripting/surrealql-functions

# SurrealQL functions

Embedded JavaScript functions access native SurrealQL via surrealdb.functions for richer, performant server-side logic.

Embedded scripting functions have access to native SurrealQL functions, allowing for complex and performant operations otherwise not possible.
---

Embedded scripting functions have access to native SurrealQL functions, allowing for complex and performant operations otherwise not possible. SurrealQL functions are published under the `surrealdb.functions` variable. Custom functions are not available within the embedded JavaScript function at the moment.

```surql
function() {
	// Using the rand::uuid::v4() function
	const uuid = surrealdb.functions.rand.uuid.v4();
};
```

---

Source: https://surrealdb.com/docs/reference/query-language/scripting/type-conversion

# Type conversion

Any value from SurrealDB is converted into a JavaScript type automatically, and the return value from the JavaScript function is converted to a SurrealQL value.

Any value from SurrealDB is converted into a JavaScript type automatically, and the return value from the JavaScript function is converted to a SurrealQL value. Boolean values, Integers, Floats, Strings, Arrays, Objects, and Date objects are all converted automatically to and from SurrealQL values.

```surql
CREATE user:test SET created_at = function() {
	return new Date();
};
```

```surql title="Output"
[
	{
		created_at: d'2026-04-02T01:13:18.408Z',
		id: user:test
	}
]
```

In addition, a number of special classes are included within the JavaScript functions for the additional types which are not built into JavaScript. These enable the creation of [`duration`](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) values, [`record`](/docs/reference/query-language/language-primitives/data-types/record-ids.md) ids, and [`UUID`](/docs/reference/query-language/language-primitives/data-types/strings.md#uuid-literal-values-with-the-u-prefix) values from within JavaScript.

Any values of these types passed into embedded scripting functions are also represented with these special classes.

```surql
CREATE user:test SET
	session_timeout = function() {
		return new Duration('1w');
	},
	best_friend = function() {
		return new Record('user', 'joanna');
	},
	identifier = function() {
		return new Uuid('03412258-988f-47cd-82db-549902cdaffe');
	}
;
```

```surql title="Output"
[
	{
		best_friend: user:joanna,
		id: user:test1,
		identifier: u'03412258-988f-47cd-82db-549902cdaffe',
		session_timeout: 1w
	}
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/access

# ACCESS

The ACCESS statement can be used to manage access grants.

> [!NOTE]
> Before SurrealDB 3.0.0, the `ACCESS` statement was experimental and required the server to be started with `--allow-experimental bearer_access`. From 3.0.0 it is available without that flag.

The `ACCESS` statement can be used to manage access grants. It provides the ability to generate access grants using certain access methods, such as bearer keys defined with the [`DEFINE ACCESS ... TYPE BEARER`](/docs/reference/query-language/statements/define/access/bearer.md) statement, as well as the ability to show, revoke and purge such grants.

By default, the `ACCESS` statement will default to referencing access methods defined at the current level specified with the [`USE`](/docs/reference/query-language/statements/use.md) statement. As with other statements, access methods defined at any level can be referenced by using the `ON` clause.

Operations that either create, revoke or purge access grants using the `ACCESS` statement will be logged in the server as long as it is running with the `INFO` level (the default) or any higher verbosity level. These log lines carry a target ending in `statements::access`, such as `surrealdb_core::expr::statements::access`.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ACCESS @name [ ON [ ROOT | NAMESPACE | DATABASE ] ]
	GRANT [ FOR USER @name | FOR RECORD @record ]
	| SHOW [ GRANT @id | ALL | WHERE @expression ] 
	| REVOKE [ GRANT @id | ALL | WHERE @expression ] 
	| PURGE [ EXPIRED | REVOKED [ , EXPIRED | REVOKED ] ] [ FOR @duration ]
```

## `GRANT`

The `GRANT` clause creates and returns a grant for a certain subject using the specified access method. This subject can be a [system user](/docs/learn/security/authentication/users.md#system-users) or [record user](/docs/learn/security/authentication/users.md#record-users). Access grants can be used to access SurrealDB as that subject until they become expired or revoked.

When creating a grant, a secret (e.g. a key) corresponding with the grant will be returned. This secret should be stored securely, as it will no longer be displayed by SurrealDB, instead being printed as `[REDACTED]` whenever the grant details are shown.

```syntax title="SurrealQL Syntax"
ACCESS @name [ ON [ ROOT | NAMESPACE | DATABASE ] ] 
	GRANT [ FOR USER @name | FOR RECORD @record ]
```

### Example: grant for automation using system user

```surql
-- Define system user for automation
DEFINE USER automation ON DATABASE PASSWORD 'secret' ROLES VIEWER;
-- Define bearer access method to generate API keys
DEFINE ACCESS api
  ON DATABASE TYPE BEARER FOR USER DURATION FOR GRANT 10d;
-- Generate bearer grant to be used by the automation
ACCESS api GRANT FOR USER automation;
```

```surql title="Output"
-- Query 1
NONE
-- Query 2
NONE
-- Query 3
{
        ac: 'api',
        creation: d'2024-12-16T16:15:51.517384293Z',
        expiration: d'2024-12-26T16:15:51.517386053Z',
        grant: {
                id: 'BNb2pS0GmaJz',
                key:
                  'surreal-bearer-BNb2pS0GmaJz-5eTfQ5uEu8jbRb3oblqVMAt8'
        },
        id: 'BNb2pS0GmaJz',
        revocation: NONE,
        subject: {
                user: 'automation'
        },
        type: 'bearer'
}
```

### Example: grant for end-user using record user

```surql
-- Create record representing a user
CREATE user:1 CONTENT { name: "tobie" };
-- Define bearer access method to generate API keys
DEFINE ACCESS api
  ON DATABASE TYPE BEARER FOR RECORD DURATION FOR GRANT 10d;
-- Generate bearer grant to be used by the user
ACCESS api GRANT FOR RECORD user:1;
```

```surql title="Output"
-- Query 1
[
        {
                id: user:1,
                name: 'tobie'
        }
]
-- Query 2
NONE
-- Query 3
{
        ac: 'api',
        creation: d'2024-12-16T16:16:41.996932810Z',
        expiration: d'2024-12-26T16:16:41.996934501Z',
        grant: {
                id: 'sRLEKGxObJuM',
                key:
                  'surreal-bearer-sRLEKGxObJuM-iUUFe1vijFDaFDW7jceZJDkX'
        },
        id: 'sRLEKGxObJuM',
        revocation: NONE,
        subject: {
                record: user:1
        },
        type: 'bearer'
}
```

## `SHOW`

The `SHOW` clause displays the details of grants created with a specific access method. The statement allows showing the details of individual grants, all grants or only grants matching a particular SurrealQL expression. Beware that, in situations where grants are automatically created, showing all grants at once may be impractical and filtering is advised.

Note that any secrets (e.g. keys) associated with the grant will not be displayed and instead will be shown as `[REDACTED]`.

```syntax title="SurrealQL Syntax"
ACCESS @name [ ON [ ROOT | NAMESPACE | DATABASE ] ]
	SHOW [ GRANT @id | ALL | WHERE @expression ] 
```

### Example: Showing the details of a specific grant

```surql
-- Create record representing a user
CREATE user:1 CONTENT { name: "tobie" };
-- Define bearer access method to generate API keys
DEFINE ACCESS api
  ON DATABASE TYPE BEARER FOR RECORD DURATION FOR GRANT 10d;
-- Generate bearer grant to be used by the user
ACCESS api GRANT FOR RECORD user:1;
```

```surql title="Output"
-- Query 1
[
        {
                id: user:1,
                name: 'tobie'
        }
]
-- Query 2
NONE
-- Query 3
{
        ac: 'api',
        creation: d'2024-12-16T16:17:24.903832476Z',
        expiration: d'2024-12-26T16:17:24.903834523Z',
        grant: {
                id: 'JdvDFKMCVYoM',
                key:
                  'surreal-bearer-JdvDFKMCVYoM-0ahEAVY6egVdg33Vs5gc6J4h'
        },
        id: 'JdvDFKMCVYoM',
        revocation: NONE,
        subject: {
                record: user:1
        },
        type: 'bearer'
}
```

```surql
ACCESS api SHOW GRANT JdvDFKMCVYoM;
```

```surql title="Output"
{
        ac: 'api',
        creation: d'2024-12-16T16:17:24.903832476Z',
        expiration: d'2024-12-26T16:17:24.903834523Z',
        grant: {
                id: 'JdvDFKMCVYoM',
                key: '[REDACTED]'
        },
        id: 'JdvDFKMCVYoM',
        revocation: NONE,
        subject: {
                record: user:1
        },
        type: 'bearer'
}
```

### Example: Showing the details of all grants for users of a certain name

Since the `subject` attribute of grants associated with a record is a record identifier, it can be used as a [record link](/docs/reference/query-language/language-primitives/record-links.md) in order to access any record fields. This can be used to filter grants associated with record users matching certain conditions based on arbitrary data.

```surql
-- Create records representing users
CREATE user:1 CONTENT { name: "tobie" };
CREATE user:2 CONTENT { name: "jaime" };
-- Define bearer access method to generate API keys
DEFINE ACCESS api
  ON DATABASE TYPE BEARER FOR RECORD DURATION FOR GRANT 10d;
-- Generate bearer grants to be used by the users
ACCESS api GRANT FOR RECORD user:1;
ACCESS api GRANT FOR RECORD user:2;
```

```surql title="Output"
-- Query 1
[
        {
                id: user:1,
                name: 'tobie'
        }
]
-- Query 2
[
        {
                id: user:2,
                name: 'jaime'
        }
]
-- Query 3
NONE
-- Query 4
{
        ac: 'api',
        creation: d'2024-12-16T16:18:57.061692071Z',
        expiration: d'2024-12-26T16:18:57.061694228Z',
        grant: {
                id: 'HaJ19zCnP6RI',
                key:
                  'surreal-bearer-HaJ19zCnP6RI-R545vHcTbSCYdHnxIsVnjSFu'
        },
        id: 'HaJ19zCnP6RI',
        revocation: NONE,
        subject: {
                record: user:1
        },
        type: 'bearer'
}
-- Query 5
{
        ac: 'api',
        creation: d'2024-12-16T16:18:57.063673293Z',
        expiration: d'2024-12-26T16:18:57.063674755Z',
        grant: {
                id: 'ND2ZegEHfUGl',
                key:
                  'surreal-bearer-ND2ZegEHfUGl-JGPSr162qJ2bN8kURV8mYaLv'
        },
        id: 'ND2ZegEHfUGl',
        revocation: NONE,
        subject: {
                record: user:2
        },
        type: 'bearer'
}
```

```surql
ACCESS api SHOW WHERE subject.record.name = "tobie";
```

```surql title="Output"
[
        {
                ac: 'api',
                creation: d'2024-12-16T16:18:57.061692071Z',
                expiration: d'2024-12-26T16:18:57.061694228Z',
                grant: {
                        id: 'HaJ19zCnP6RI',
                        key: '[REDACTED]'
                },
                id: 'HaJ19zCnP6RI',
                revocation: NONE,
                subject: {
                        record: user:1
                },
                type: 'bearer'
        }
]
```

## `REVOKE`

The `REVOKE` clause revokes grants created with a specific access method. Revoking a grant ensures that the grant can no longer be used to authenticate. The grant will continue existing in revoked form and the time of the revocation is recorded in the details of the grant. Grants that have already been revoked cannot be revoked again. The statement allows revoking individual grants, all grants or only grants matching a particular SurrealQL expression.

```syntax title="SurrealQL Syntax"
ACCESS @name [ ON [ ROOT | NAMESPACE | DATABASE ] ]
	REVOKE [ GRANT @id | ALL | WHERE @expression ] 
]
```

### Example: Revoking a specific grant

```surql
-- Create record representing a user
CREATE user:1 CONTENT { name: "tobie" };
-- Define bearer access method to generate API keys
DEFINE ACCESS api
  ON DATABASE TYPE BEARER FOR RECORD DURATION FOR GRANT 10d;
-- Generate bearer grant to be used by the user
ACCESS api GRANT FOR RECORD user:1;
```

```surql title="Output"
-- Query 1
[
        {
                id: user:1,
                name: 'tobie'
        }
]
-- Query 2
NONE
-- Query 3
{
        ac: 'api',
        creation: d'2024-12-17T10:36:09.215762475Z',
        expiration: d'2024-12-27T10:36:09.216227523Z',
        grant: {
                id: 'NJ2I2d7OXxN9',
                key:
                  'surreal-bearer-NJ2I2d7OXxN9-Oa5LqF36IzfURpo6Bhxy9WMF'
        },
        id: 'NJ2I2d7OXxN9',
        revocation: NONE,
        subject: {
                record: user:1
        },
        type: 'bearer'
}
```

```surql
ACCESS api REVOKE GRANT NJ2I2d7OXxN9;
```

```surql title="Output"
[
        [
                {
                        ac: 'api',
                        creation: d'2024-12-17T10:36:09.215762475Z',
                        expiration: d'2024-12-27T10:36:09.216227523Z',
                        grant: {
                                id: 'NJ2I2d7OXxN9',
                                key: '[REDACTED]'
                        },
                        id: 'NJ2I2d7OXxN9',
                        revocation: d'2024-12-17T10:36:52.740438379Z',
                        subject: {
                                record: user:1
                        },
                        type: 'bearer'
                }
        ]
]
```

### Example: Revoking all grants for users of a certain name

Since the `subject` attribute of grants associated with a record is a record identifier, it can be used as a [record link](/docs/reference/query-language/language-primitives/record-links.md) in order to access any record fields. This can be used to filter grants associated with record users matching certain conditions based on arbitrary data.

```surql
-- Create records representing users
CREATE user:1 CONTENT { name: "tobie" };
CREATE user:2 CONTENT { name: "jaime" };
-- Define bearer access method to generate API keys
DEFINE ACCESS api
  ON DATABASE TYPE BEARER FOR RECORD DURATION FOR GRANT 10d;
-- Generate bearer grants to be used by the users
ACCESS api GRANT FOR RECORD user:1;
ACCESS api GRANT FOR RECORD user:2;
```

```surql title="Output"
-- Query 1
[
        {
                id: user:1,
                name: 'tobie'
        }
]
-- Query 2
[
        {
                id: user:2,
                name: 'jaime'
        }
]
-- Query 3
NONE
-- Query 4
{
        ac: 'api',
        creation: d'2024-12-17T10:42:35.040901759Z',
        expiration: d'2024-12-27T10:42:35.040903414Z',
        grant: {
                id: 'mjSACes6sej4',
                key:
                  'surreal-bearer-mjSACes6sej4-WbEPMgmLTO3Jfg3po4we9m0V'
        },
        id: 'mjSACes6sej4',
        revocation: NONE,
        subject: {
                record: user:1
        },
        type: 'bearer'
}
-- Query 5
{
        ac: 'api',
        creation: d'2024-12-17T10:42:35.043162877Z',
        expiration: d'2024-12-27T10:42:35.043164533Z',
        grant: {
                id: 'RFilJMRp9lZi',
                key:
                  'surreal-bearer-RFilJMRp9lZi-OmflYxXwikDAvm8CNpsWYxd6'
        },
        id: 'RFilJMRp9lZi',
        revocation: NONE,
        subject: {
                record: user:2
        },
        type: 'bearer'
}
```

```surql
ACCESS api REVOKE WHERE subject.record.name = "tobie";
```

```surql title="Output"
[
        [
                {
                        ac: 'api',
                        creation: d'2024-12-17T10:42:35.040901759Z',
                        expiration: d'2024-12-27T10:42:35.040903414Z',
                        grant: {
                                id: 'mjSACes6sej4',
                                key: '[REDACTED]'
                        },
                        id: 'mjSACes6sej4',
                        revocation: d'2024-12-17T10:43:23.944198560Z',
                        subject: {
                                record: user:1
                        },
                        type: 'bearer'
                }
        ]
]
```

## `PURGE`

The `PURGE` clause completely removes grants created with a specific access method that have already been expired or revoked. In scenarios with very large amount of grants associated with an access method (e.g. when grants are automatically generated), purging inactive grants can improve the performance and experience of auditing grants with the `SHOW` clause. In some very high volume scenarios where grants are created by the hundreds of millions, purging may be necessary to limit the probability of collisions resulting in failure to create new grants. Note that the performance cost of validating a grant is independent from the number of existing grants, regardless of whether they are active or inactive.

Beware that any details associated with purged grants will be permanently lost and will no longer be available for auditing purposes. The `FOR` clause can be used to establish a minimum grace period after which expired or revoked grants should be purged. As with other security-sensitive operations related with the `ACCESS` statement, the purging of grants is logged in the SurrealDB server.

The clause will return the details of all grants that have successfully been purged and its performance will depend on the number of purged grants.

```syntax title="SurrealQL Syntax"
ACCESS @name [ ON [ ROOT | NAMESPACE | DATABASE ] ]
	PURGE [ EXPIRED | REVOKED [ , EXPIRED | REVOKED ] ] [ FOR @duration ]
]
```

### Example: Purging grants that have been expired

```surql
DEFINE ACCESS api ON DATABASE TYPE BEARER FOR RECORD;
ACCESS api PURGE EXPIRED;
```

### Example: Purging grants that have been revoked for more than 90 days

```surql
DEFINE ACCESS api ON DATABASE TYPE BEARER FOR RECORD;
ACCESS api PURGE REVOKED FOR 90d;
```

### Example: Purging all grants that have been invalid for more than a year

```surql
DEFINE ACCESS api ON DATABASE TYPE BEARER FOR RECORD;
ACCESS api PURGE EXPIRED, REVOKED FOR 1y;
```

## Improving error output

To improve the output of an error message inside an `ACCESS` statement, a [`THROW`](/docs/reference/query-language/statements/throw.md) statement can be used.

Follow these steps to see the output in practice.

First, start the SurrealDB server with a root user:

```bash
surreal db start --user root --pass secret
```

Log in as the root user, choose the namespace `test` and database `test`, and run the following `DEFINE ACCESS` command.

```surql
DEFINE ACCESS account ON DATABASE TYPE RECORD
    SIGNUP ({
    IF $email = "me@me.com" {
        THROW "That's my email!!!"
    } ELSE {
        CREATE user SET email = $email,
          pass = crypto::argon2::generate($pass)}
    })
    SIGNIN ( SELECT * FROM user WHERE email = $email
      AND crypto::argon2::compare(pass, $pass) )
    DURATION FOR TOKEN 15m, FOR SESSION 12h
;
```

As the `THROW` statement checks for the email address `me@me.com`, this can be tested using CURL at the [`signup`](/docs/reference/rest-api/http-protocol.md#signup) endpoint via the following command.

```bash
curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account", "email": "me@me.com", "pass": "strongpassword"}' http://localhost:8000/signup
```

The error output shows the user-defined error message `That's my email!!!`.

```bash title="Output"
{"code":400,"details":"Request problems detected","description":"There is a problem with your request. Refer to the documentation for further information.","information":"There was a problem with the database: An error occurred: That's my email!!!"
```

In all other cases, the signup process will work, returning a token.

```bash
curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account", "email": "someotheremail@me.com", "pass": "strongpassword"}' http://localhost:8000/signup
```

```bash title="Output"
{"code":200,"details":"Authentication succeeded","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3NDYwNzIxOTAsIm5iZiI6MTc0NjA3MjE5MCwiZXhwIjoxNzQ2MDczMDkwLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiI3YTc0ZjQ5ZS02OWMxLTRiMjMtYmRhNy05YThkNTNjNWFiZmIiLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJBQyI6ImFjY291bnQiLCJJRCI6InVzZXI6b2tlZjN5bmI4eXJkd2l3b3h1YjEifQ.4t1xwVkl36PeTFuBj0d41D6A-bwCx7LoNoLlj6yokA8wOKwEa1ldjBzTldZEmylLgP5-q8D4wp6f9Y6ntBZyPA","refresh":null}
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/access

# ALTER ACCESS

The ALTER ACCESS statement can be used to modify an existing defined access.

_(since v3.0.5)_

The `ALTER ACCESS` statement can be used to modify an existing defined [access](/docs/reference/query-language/statements/define/access.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER ACCESS [ IF EXISTS ] @name
  ON [ ROOT | NAMESPACE | DATABASE ]
  [ AUTHENTICATE @expression | DROP AUTHENTICATE ]
  [ CONTEXT @expression | DROP CONTEXT ]
  [ DURATION
    [ FOR GRANT [ @duration | NONE ] ]
    [ FOR TOKEN [ @duration | NONE ] ]
    [ FOR SESSION [ @duration | NONE ] ]
  ]
  [ COMMENT @string | DROP COMMENT ]
```

Note that this statement does not allow modification of the access type itself (`RECORD` / `JWT` / `BEARER`), only its duration, the `AUTHENTICATE` and `CONTEXT` clauses, and a `COMMENT`.

## Example usage

```surql
-- Define an access
DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET email = $email,
	  pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE email = $email
	  AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h;

-- Shorten the token duration
ALTER ACCESS account ON DATABASE DURATION FOR TOKEN 1m;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/analyzer

# ALTER ANALYZER

The ALTER ANALYZER statement can be used to modify an existing defined analyzer.

_(since v3.0.5)_

The `ALTER ANALYZER` statement can be used to modify an existing defined [analyzer](/docs/reference/query-language/statements/define/analyzer.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER ANALYZER [ IF EXISTS ] @name
  [ FUNCTION fn::@function | DROP FUNCTION ]
  [ TOKENIZERS @tokenizer, ... | DROP TOKENIZERS ]
  [ FILTERS @filter, ... | DROP FILTERS ]
  [ COMMENT @string | DROP COMMENT ]
```

## Example usage

```surql
-- Define an analyzer
DEFINE ANALYZER example_edgengram TOKENIZERS class FILTERS
  edgengram(1,3);

-- Shorten the edgengram
ALTER ANALYZER example_edgengram FILTERS edgengram(1,2);

-- Check the output
search::analyze("example_edgengram", "Apple banana!!");
```

```surql title="Output"
[
	'A',
	'Ap',
	'b',
	'ba',
	'!',
	'!!'
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/api

# ALTER API

The ALTER API statement can be used to modify an existing defined API.

_(since v3.0.5)_

The `ALTER API` statement can be used to modify an existing defined [API](/docs/reference/query-language/statements/define/api.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER API [ IF EXISTS ] @endpoint
  [ FOR any [ @api_config ] [ THEN @expression | DROP THEN ] ]
  [ FOR @http_method, ... [ MIDDLEWARE @function, ... ] [ THEN @expression ] ]
  [ FOR @http_method, ... DROP THEN ]
  [ COMMENT @string | DROP COMMENT ]
```

## Example usage

```surql
-- Define a simple API
DEFINE API "/test"
    FOR get
        THEN {
            {
                body: {
                    some: "data"
                }
            };
        };

-- Make a random function
DEFINE FUNCTION fn::feeling_lucky() -> int {
    rand::enum(200, 404)
};

-- Set it as the HTTP status
ALTER API "/test" FOR get THEN {
    {
        status: fn::feeling_lucky(),
        body: {
            some: "data"
        }
    }
};

-- status is either 200 or 404
api::invoke("/test");
```

```surql title="Possible output"
{
	body: {
		some: 'data'
	},
	headers: {
		"x-surreal-request-id": '95fd0577-5b97-4dc1-b98e-c284f0e36a63'
	},
	request_id: '95fd0577-5b97-4dc1-b98e-c284f0e36a63',
	status: 404
}
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/bucket

# ALTER BUCKET

The ALTER BUCKET statement can be used to modify an existing defined bucket.

_(since v3.0.5)_

The `ALTER BUCKET` statement can be used to modify an existing defined [bucket](/docs/reference/query-language/statements/define/bucket.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER BUCKET [ IF EXISTS ] @name
  [ READONLY | DROP READONLY ]
  [ BACKEND @string | DROP BACKEND ]
  [ PERMISSIONS @expression ]
  [ COMMENT @string | DROP COMMENT ]
```

## Example usage

```surql
DEFINE BUCKET my_bucket BACKEND "memory";

ALTER BUCKET my_bucket COMMENT "Should we make this read-only too??";
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/config

# ALTER CONFIG

The ALTER statement can be used to change authentication access and behaviour, global parameters, table configurations, table events, schema definitions, and indexes.

_(since v3.0.5)_

The `ALTER CONFIG` statement can be used to modify an existing defined [config](/docs/reference/query-language/statements/define/config.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER CONFIG [ IF EXISTS ]
  ( API
      [ MIDDLEWARE @function(...), ... ]
      PERMISSIONS [ NONE | FULL | @expression ]
  | GRAPHQL
      TABLES [ AUTO | NONE | INCLUDE @table, ... | EXCLUDE @table, ... ]
      FUNCTIONS [ AUTO | NONE | INCLUDE @function, ... | EXCLUDE @function, ... ]
      [ DEPTH @integer ]
      [ COMPLEXITY @integer ]
      [ INTROSPECTION NONE ]
  | DEFAULT
      NAMESPACE @namespace
      DATABASE @database
  )
  [ COMMENT @string | DROP COMMENT ]
```

## Example usage

```surql
DEFINE CONFIG GRAPHQL TABLES AUTO FUNCTIONS AUTO;

ALTER CONFIG GRAPHQL FUNCTIONS NONE;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/database

# ALTER DATABASE

The ALTER DATABASE statement can be used to modify the database.

_(since v3.0.0)_

The `ALTER DATABASE` statement can be used to modify the database. `ALTER DATABASE` is used on the current database, which is why a `IF EXISTS` clause does not exist.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER DATABASE COMPACT
```

## COMPACT

Performs storage compaction on the current database keyspace. To compact other resources, use [ALTER SYSTEM](/docs/reference/query-language/statements/alter/system.md) to compact the entire datastore, [ALTER NAMESPACE](/docs/reference/query-language/statements/alter/namespace.md) to compact the current namespace keyspace, or [ALTER TABLE](/docs/reference/query-language/statements/alter/table.md) to compact a specific table keyspace.

The actual compaction used will depend on the datastore, such as RocksDB or SurrealKV.

This clause will not work with in-memory storage which has nothing persistent to compact, producing the following error:

```surql
'The storage layer does not support compaction requests.'
```

A successful compaction will return `NONE`.

```surql
ALTER DATABASE COMPACT;
```

```surql title="Output"
NONE
```

## See also

* [`DEFINE DATABASE`](/docs/reference/query-language/statements/define/database.md)

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/event

# ALTER EVENT

The ALTER EVENT statement can be used to modify an existing defined event.

_(since v3.0.5)_

The `ALTER EVENT` statement can be used to modify an existing defined [event](/docs/reference/query-language/statements/define/event.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER EVENT [ IF EXISTS ] @name ON [ TABLE ] @table
  [ ASYNC [ RETRY @retry ] [ MAXDEPTH @max_depth ] | DROP ASYNC ]
  [ WHEN @condition | DROP WHEN ]
  [ THEN @action, ... | DROP THEN ]
  [ COMMENT @string | DROP COMMENT ]
```

## Example usage

```surql
DEFINE FIELD status ON post TYPE "submitted" | "published" DEFAULT "submitted";

-- Define an event
DEFINE EVENT publish_post ON TABLE publication
    WHEN $event = "CREATE"
    THEN (
        FOR $post IN $after.posts {
            UPDATE $post SET status = "published";
        }        
    );

-- Make it async
ALTER EVENT publish_post ON TABLE publication ASYNC;

CREATE post:one SET content = "I read the news today, oh boy...";
CREATE post:two SET content = "On the banks of Tuonela Bleach the skeletons of kings";
CREATE post:three SET content = "뭐 화끈한 일 뭐 신나는 일 없을까";
CREATE publication SET posts = [post:one, post:two, post:three];

SELECT * FROM post;
```

```surql title="Output once async events processed"
[
	{
		content: 'I read the news today, oh boy...',
		id: post:one,
		status: 'submitted'
	},
	{
		content: '뭐 화끈한 일 뭐 신나는 일 없을까',
		id: post:three,
		status: 'submitted'
	},
	{
		content: 'On the banks of Tuonela Bleach the skeletons of kings',
		id: post:two,
		status: 'submitted'
	}
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/field

# ALTER FIELD

The ALTER FIELD statement is used to change or entirely drop clauses of a defined field on a table.

The `ALTER FIELD` statement is used to change or entirely drop clauses of a defined field on a table.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER FIELD [ IF EXISTS ] @name ON [ TABLE ] @table 
[ 
    DROP TYPE |
    DROP FLEXIBLE |
    DROP READONLY |
    DROP INLINE |
    DROP VALUE |
    DROP ASSERT |
    DROP DEFAULT |
    DROP COMMENT |
    DROP REFERENCE |
    FLEXIBLE |
    READONLY |
    INLINE |
    REFERENCE |
    TYPE @type |
    VALUE @value |
    ASSERT @expression |
    DEFAULT [ ALWAYS ] @expression |
    [ PERMISSIONS [ NONE | FULL
		| FOR select @expression
		| FOR create @expression
		| FOR update @expression
		| FOR delete @expression
	] ]
    COMMENT @string |
]
```

## Examples

As `ALTER FIELD` contains the same clauses available in a [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statement, be sure to see that page for more examples.

Here is one example in which the `name` field is defined for a record `user`:

```surql
DEFINE FIELD name ON user TYPE string;
```

Later on, a database-wide [parameter](/docs/reference/query-language/statements/define/param.md) is defined to disallow certain user names. This can be followed up with an `ALTER FIELD` statement to add the `ASSERT` clause to it.

```surql
DEFINE PARAM $DISALLOWED_NAMES VALUE ["Lord British", "Lord Blackthorn"];
ALTER FIELD name ON user ASSERT $value NOT IN $DISALLOWED_NAMES;
CREATE user SET name = "Lord British";
```

```surql title="Output"
"Found 'Lord British' for field `name`, with record `user:yn4yttkg5w683q2937bq`, but field must conform to: $value NOTINSIDE $DISALLOWED_NAMES""
```

## See also

* [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md)

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/function

# ALTER FUNCTION

The ALTER FUNCTION statement can be used to modify an existing defined function.

_(since v3.0.5)_

The `ALTER FUNCTION` statement can be used to modify an existing defined [function](/docs/reference/query-language/statements/define/function.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER FUNCTION [ IF EXISTS ] fn::@name
  [ ( [ $argument: @type, ... ] ) ] [ -> @type | DROP RETURNS ]
  [ { @query ... } ]
  [ COMMENT @string | DROP COMMENT ]
  [ PERMISSIONS [ NONE | FULL | WHERE @condition ] ] 
```

## Example usage

```surql
-- Declare a function
DEFINE FUNCTION fn::get_message($input: any) -> option<string> {
    $input.message
};

-- No `message` field, returns nothing
fn::get_message("wrong input");

-- Tighten up the valid input for the function
ALTER FUNCTION fn::get_message($input: { error_code: 200, message: string } | {error_code: 404, message: string} ) {
    $input.message
};

-- Returns an error now
fn::get_message("wrong input");

-- Okay, returns 'Looks good'
fn::get_message({ error_code: 200, message: "Looks good" });
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/indexes

# ALTER INDEX

The ALTER INDEX statement is used to alter a defined index on a table.

_(since v3.0.0)_

The `ALTER INDEX` statement is used to alter a defined index on a table.

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER INDEX @name ON TABLE @table
    COMMENT @string |
    PREPARE REMOVE |
    DROP COMMENT
```

## `PREPARE REMOVE` clause

As the name implies, an `ALTER INDEX PREPARE REMOVE` statement alters an index to prepare it for removal. This statement sets up a step in which the index has been decommissioned (prepared for removal), but not yet removed. At this point, `SELECT` queries with the `EXPLAIN` clause can be used to monitor query performance without the index.

```surql
-- 1. Decommission the index
ALTER INDEX my_index ON my_table PREPARE REMOVE;

-- 2. Monitor query performance and verify queries still work
SELECT ... FROM my_table EXPLAIN;

-- 3. If satisfied, permanently remove the index
REMOVE INDEX my_index ON my_table;
```

If removing the index is no longer desired, it can be restored to a useful state by using a [REBUILD INDEX](/docs/reference/query-language/statements/rebuild.md) statement.

```surql
REBUILD INDEX my_index ON my_table;
```

## See also

* [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md)

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/namespace

# ALTER NAMESPACE

The ALTER NAMESPACE statement can be used to modify the namespace.

_(since v3.0.0)_

The `ALTER NAMESPACE` statement can be used to modify the namespace. `ALTER NAMESPACE` is used on the current namespace, which is why a `IF EXISTS` clause does not exist.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER NAMESPACE COMPACT
```

## COMPACT

Performs storage compaction on the current namespace keyspace. To compact other resources, use [ALTER SYSTEM](/docs/reference/query-language/statements/alter/system.md) to compact the entire datastore, [ALTER DATABASE](/docs/reference/query-language/statements/alter/database.md) to compact the current database keyspace, or [ALTER TABLE](/docs/reference/query-language/statements/alter/table.md) to compact a specific table keyspace.

The actual compaction used will depend on the datastore, such as RocksDB or SurrealKV.

This clause will not work with in-memory storage which has nothing persistent to compact, producing the following error:

```surql
'The storage layer does not support compaction requests.'
```

A successful compaction will return `NONE`.

```surql
ALTER NAMESPACE COMPACT;
```

```surql title="Output"
NONE
```

## See also

* [`DEFINE NAMESPACE`](/docs/reference/query-language/statements/define/namespace.md)

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/overview

# ALTER

The ALTER statement can be used to change the behaviour of database resources.

The `ALTER` statement can be used to change the behaviour of database resources.

There are two main cases in which to use an `ALTER` statement:

* Modifying previously defined resources. This can currently be used to modify tables and fields. For other such modifications, use the `OVERWRITE` clause in other `DEFINE` statements.
* Modifying other resources using clauses not present in other `DEFINE` statements. Examples of this are the `PREPARE REMOVE` clause to prepare an index for removal, the `COMPACT` clause to compact the system/namespace/database/single table, and the `QUERY_TIMEOUT` clause to define or drop the query timeout for the entire datastore.

Each resource has its own `ALTER` statement:

| Statement | Alters |
| --- | --- |
| [`ALTER ACCESS`](/docs/reference/query-language/statements/alter/access.md) | An existing access method |
| [`ALTER ANALYZER`](/docs/reference/query-language/statements/alter/analyzer.md) | An existing analyzer |
| [`ALTER API`](/docs/reference/query-language/statements/alter/api.md) | An existing API definition |
| [`ALTER BUCKET`](/docs/reference/query-language/statements/alter/bucket.md) | An existing bucket |
| [`ALTER CONFIG`](/docs/reference/query-language/statements/alter/config.md) | Authentication access and GraphQL behaviour |
| [`ALTER DATABASE`](/docs/reference/query-language/statements/alter/database.md) | The current database |
| [`ALTER EVENT`](/docs/reference/query-language/statements/alter/event.md) | An existing event |
| [`ALTER FIELD`](/docs/reference/query-language/statements/alter/field.md) | A field's clauses, or drop them entirely |
| [`ALTER FUNCTION`](/docs/reference/query-language/statements/alter/function.md) | An existing custom function |
| [`ALTER INDEX`](/docs/reference/query-language/statements/alter/indexes.md) | An index on a table, including `PREPARE REMOVE` |
| [`ALTER NAMESPACE`](/docs/reference/query-language/statements/alter/namespace.md) | The current namespace |
| [`ALTER PARAM`](/docs/reference/query-language/statements/alter/param.md) | An existing parameter |
| [`ALTER SEQUENCE`](/docs/reference/query-language/statements/alter/sequence.md) | An existing sequence |
| [`ALTER SYSTEM`](/docs/reference/query-language/statements/alter/system.md) | The entire datastore, including `QUERY_TIMEOUT` |
| [`ALTER TABLE`](/docs/reference/query-language/statements/alter/table.md) | A table's schema, such as moving from schemaless to schemafull |
| [`ALTER USER`](/docs/reference/query-language/statements/alter/user.md) | An existing database, namespace or root user |

Some examples of `ALTER` statements are as follows.

## Modify a table schema

When starting a new project, you may require a table to be schemaless to allow for flexibility in the data structure. However, as the project progresses, you may want to lock down the schema to prevent new fields from being added.

An example of `ALTER` to modify an existing table:

```surql
DEFINE TABLE user SCHEMALESS;
DEFINE FIELD name ON TABLE user TYPE string;
CREATE user SET name = "LordofSalty";

-- Now make it schemafull to ensure that no other fields can be used
ALTER TABLE user SCHEMAFULL;
```

## Modify table permissions

You can also use the `ALTER` statement to change a table's permissions. An `ALTER` statement only needs to include the items to be altered, not the entire definition.

```surql
-- Will show up as DEFINE TABLE user TYPE ANY SCHEMAFULL PERMISSIONS NONE
DEFINE TABLE user SCHEMAFULL;

-- Now defined as DEFINE TABLE user TYPE ANY SCHEMAFULL PERMISSIONS FULL
ALTER TABLE user PERMISSIONS FOR create FULL;
```

## Using `IF EXISTS` clause

You can use the `IF EXISTS` clause to prevent an error from occurring when trying to alter a table that does not exist.

```surql
ALTER TABLE IF EXISTS user SCHEMAFULL;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/param

# ALTER PARAM

The ALTER PARAM statement can be used to modify an existing defined param.

_(since v3.0.5)_

The `ALTER PARAM` statement can be used to modify an existing defined [param](/docs/reference/query-language/statements/define/param.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER PARAM [ IF EXISTS ] $name
  [ VALUE @value ]
  [ COMMENT @string | DROP COMMENT ]
  [ PERMISSIONS [ NONE | FULL | WHERE @condition ] ]
```

Note that `ALTER PARAM` does not support `DROP VALUE` as a parameter without a value is not valid.

## Example usage

```surql
DEFINE PARAM $MODE VALUE "production" COMMENT "Don't use this param yet";

ALTER PARAM $MODE DROP COMMENT;

-- Check the statement
(INFO FOR DB).params.MODE;
```

```surql title="Output: comment is gone"
"DEFINE PARAM $MODE VALUE 'production' PERMISSIONS FULL"
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/sequence

# ALTER SEQUENCE

The ALTER SEQUENCE statement is used to modify a defined sequence.

_(since v3.0.0)_

The `ALTER SEQUENCE` statement is used to modify a defined sequence.

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER SEQUENCE [ IF EXISTS ] @name [ TIMEOUT @duration ]
```

## Examples

The timeout of a sequence can be modified via an `ALTER SEQUENCE` statement. For example, a sequence can be included in the schema but effectively disabled if given a timeout of 0ns, aft