# 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). _(since v3.1.0)_

**[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/ai-agents/ai-frameworks.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 you are not keeping a document store, a graph database, and a vector index 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/ai-frameworks

# AI frameworks

Framework integrations for AI and machine learning libraries. Use SurrealDB for vectors, memory, and pipelines.

SurrealDB integrates with widely used AI and machine learning frameworks so you can use a single database for features, training or evaluation pipelines, and agent orchestration. Each integration page explains setup, typical patterns, and links to upstream documentation where helpful.

Available integrations:

- [Agno](/docs/build/integrations/ai-frameworks/agno.md)
- [CAMEL](/docs/build/integrations/ai-frameworks/camel.md)
- [CrewAI](/docs/build/integrations/ai-frameworks/crewai.md)
- [Dagster](/docs/build/integrations/ai-frameworks/dagster.md)
- [Google Agent Development Kit](/docs/build/integrations/ai-frameworks/google-agent.md)
- [Hermes](/docs/build/integrations/ai-frameworks/hermes.md)
- [LangChain](/docs/build/integrations/ai-frameworks/langchain.md)
- [LlamaIndex](/docs/build/integrations/ai-frameworks/llamaindex.md)
- [PydanticAI](/docs/build/integrations/ai-frameworks/pydantic-ai.md)
- [smolagents](/docs/build/integrations/ai-frameworks/smolagents.md)

For a comparative table and links to deeper guides, start from the [AI frameworks overview](/docs/build/integrations/ai-frameworks/overview.md).

---

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 mcp` vs `surreal start`

| | **`surreal mcp`** (stdio) | **`surreal 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 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 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 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 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 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 mcp`)

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

```bash
surreal 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 mcp`](/docs/reference/cli/surrealdb-cli/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 start`](/docs/reference/cli/surrealdb-cli/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/surrealdb-cli/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](/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/surrealdb-cli/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 mcp` CLI reference](/docs/reference/cli/surrealdb-cli/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. 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)

## 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 serializes both keys and values into a Uint8Array, utilizing IndexedDB as a binary key-value store - offering good performance, and with the ability to offer all of the functionality and features that SurrealDB offers when running in alternative ways.

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

## 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 either an in-memory engine or [SurrealKV](/docs/running/file-backed.md).

In this document, we will cover how to embed SurrealDB in both browser and 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);
```

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.

Why it matters: 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. Picking an engine is not a one-off aesthetic choice - it ties your operational playbook to concrete behaviours (compaction, backup, 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 (beta) 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.

How to choose: 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.

## 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

---

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 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) under the hood) 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 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 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 sql with `surreal 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 powerful tool for building data pipelines and workflows. It is a popular choice for 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 provides a powerful framework for developing and deploying intelligent agents in Google Cloud. This integration is particularly valuable since 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), making it easily accessible to 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. Files and folders, full-text and semantic search, and everything queryable with SurrealQL, because it is just a 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 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 for free. A folder is a row 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 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"})
```

One file per turn rather than 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 rows 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>/`. 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 export`](/docs/reference/cli/surrealdb-cli/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
```

Tree on the left, file 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 rows 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 markdownfile `demo.md`, and even an HWPX file `demo.hwpx` file that 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(1.2,0.75) 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 is an excellent database choice for [LangChain](https://python.langchain.com/docs/introduction/) projects primarily because of its [multi-model capabilities](/docs/learn/data-models.md), which streamline data management by requiring only a single database.

This unified system adeptly handles structured and unstructured data, incorporating 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.

For LangChain applications, which often juggle diverse data types for tasks like context retrieval and complex data interactions, SurrealDB's ability to consolidate these needs into one platform simplifies architecture, reduces latency, and ensures data consistency, making it a highly efficient and powerful backend solution.

In this guide, we'll walk through how to use SurrealDB as a vector store for LangChain.

<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 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}]")
```

Under the hood 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

Now that you have a basic understanding of how to use SurrealDB with LangChain, let's explore some additional resources to help you dive deeper and build more sophisticated applications.

To help you get started quickly, we provide several example implementations:

* 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 comprehensive 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/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 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, enabling you to leverage SurrealDB's powerful features like vector search, graph relationships, and structured data storage. These integrations make it easy to build sophisticated applications combining LLMs, agents, data pipelines and more - all while using familiar tools and frameworks.

<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/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/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/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 designed to help you quickly, confidently, and painlessly build 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 import --conn 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 rows 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/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 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 columns:

- `_airbyte_raw_id`: a uuid assigned by Airbyte to each event that is processed. The column type in SurrealDB is string. The connector use 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 column type in SurrealDB is datetime.
- `_airbyte_data`: a json blob representing with the event data. The column 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 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 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 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 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 a comprehensive set of 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 comprehensive 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 a comprehensive 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 offers comprehensive data management capabilities to help you efficiently store, organise, and transform your data. Through integrations with leading data platforms, you can build robust ETL pipelines, ingest data from diverse sources, and maintain data quality at scale. The integrations below enable seamless data workflows while leveraging 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/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 the model once every time it is used for the first time.

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. **Blazing-fast vector storage** - **SurrealDB** (≥ v1.5) ships an in-memory HNSW index, queried with the `<|K,EF|>` operator in SurrealQL.

Below you’ll wire them together, from install → ingestion → search → production-ready 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))
```

*Why bulk-insert?* One SurrealQL call → one network round-trip - **much faster** than inserting row-by-row.

## 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 intside 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 rows. 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 that to generate an OpenAI embedding using [`text-embedding-3-small`](https://platform.openai.com/docs/guides/embeddings/embedding-models) that can be seen using 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 offers comprehensive support for vector embeddings, enabling powerful semantic search and machine learning capabilities across your data. Through integrations with leading embedding providers, you can easily 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 offers comprehensive support for vector embeddings, enabling powerful semantic search and machine learning capabilities across your data. Through integrations with leading embedding providers, you can easily 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 offers comprehensive support for vector embeddings, enabling powerful semantic search and machine learning capabilities across your data. Through integrations with leading embedding providers, you can easily 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/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

---

Source: https://surrealdb.com/docs/build/migrating/from-files-and-streams

# Migrating

Migrating

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/surrealdb-cli/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

SurrealDB Studio can be used to import CSV data to SurrealDB.

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). However, due to this there are 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 as suppose to being available as just 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 for 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/surrealdb-cli/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 communicated 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 fix`](/docs/reference/cli/surrealdb-cli/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 fix surrealkv://mydata

# For RocksDB
surreal fix rocksdb:somedatabase
```

### Limitations of the surreal fix command

Although the `surreal 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 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 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 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 export`](/docs/reference/cli/surrealdb-cli/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 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 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/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 automatically see your data. 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 can be used to automatically see your data and provide you with 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 tha 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 import` command:

```bash
surreal 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/surrealdb-cli/commands/export.md) and [import command](/docs/reference/cli/surrealdb-cli/commands/import.md) documentation.

## Severity levels

In this section, we will explore the different severity levels of the migration report and the actions you need to take to migrate your data. These 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 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**: Use of closures stored inside a record will have to be removed, there is currently no new feature which can replace the stored closures.

**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**: Use the `ANALYZE` stastement will have to be removed.

## 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 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] }]`
```

## Unlikely break - edge cases

### 21. `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
```

### 22. `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
```

### 23. `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
```

### 24. `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.

### 25. `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.

### 26. 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.

### 27. `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 the special behaviour of 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" 
```

### 28. 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 expressions 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 ...`

### 29. `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 with an `ASSERT $value().len()` is equal to a certain number can now have 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/overview

# Upgrading

This guide will help you upgrade your current SurrealDB installation to a newer release.

When moving to a new version of SurrealDB, it is important to follow the upgrade instructions to ensure 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`](/docs/reference/cli/surrealdb-cli/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 provides 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 exports MySQL tables to SurrealDB.

It supports inconsistent full syncs and consistent incremental syncs, and together provides 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 COLUMNS 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
```

### 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 provides 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.

## 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 the 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 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.*;
```

## 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)

---

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/surrealdb-cli/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/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 that seeks to do one thing, and one thing well: store and execute trained ML models. SurrealML does not intrude on the training frameworks that are already out there, instead 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, giving the user 100% freedom on how they deploy and interact with the SurrealML engine.

While this is all exciting, let us check to see if SurrealML works on your machine. There is nothing worse than reading about a package only to find that it does not work. The first move is to install the package.

## 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 out of the box 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. Now we need to save it and this is where `SurrealML` comes in. 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 or 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 is zero 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 under the hood. 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 be also be loaded by either Rust or any Python version that has surrealML and execute inference installed.

Now that we are able to load and execute our model locally, how do we deploy our model onto SurrealDB and run it? In the next section, we cover uploading.

## Model deployment

Before we try and upload our model, we need to have a node running. We can do 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;
```

What is happening here is that we are feeding the columns 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 column `price_prediction`. We then use the calculated predictions to filter the rows 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.

If we have a loaded a model 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 will explore what is going on under the hood 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).

Under the hood, 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 neutron 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 massive 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/blink

# Blink note-taking app

Blink - a Notion-style workspace demo built with SurrealDB and WebAssembly.

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. It is a demo, not a hosted product: the value is seeing 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 or sync behaviour feels, and how SurrealDB’s API surface maps to a rich client. If you are evaluating SurrealDB for knowledge or agent tooling, Blink is a concrete reference implementation rather than a minimal CRUD example.

Getting started: clone and run the project from the [Blink repository on GitHub](https://github.com/kearfy/blink). Expect to read the README for build steps and browser requirements - WASM workflows vary by toolchain.

If you want more guided walkthroughs in the same spirit, browse the [tutorials](/docs/explore/tutorials/tutorials/overview.md) and [demos](/docs/explore/tutorials/demos/overview.md) sections.

---

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.

## Demos

- [Blink note-taking app](/docs/explore/tutorials/demos/blink.md) - a small app you can clone and run
- [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

To quickly test out SurrealDB and SurrealQL functionality, we've included demo data which you can download and import into SurrealDB.

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/surrealdb-cli/commands/import.md) into SurrealDB using the [CLI](/docs/reference/cli/surrealdb-cli/overview.md).

## Surreal Deal Store - there is a lot in store for you!

Surreal Deal Store is our new and improved 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 shows relationships between records and SurrealDB 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/surrealdb-cli/commands/start.md).

```bash
# Create a new in-memory server
surreal start --user root --pass secret --allow-all
```

Lastly, use the [import command](/docs/reference/cli/surrealdb-cli/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 import --endpoint http://localhost:8000 --user root --pass secret \
  --ns main --db main surreal-deal-store.surql
```

To import the surreal downloaded the [Surreal Deal store (mini)](https://datasets.surrealdb.com/surreal-deal-store-mini.surql) use the command below:

```bash
surreal 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 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 rows 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 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 leverage 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.

It is important to not 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 leverage 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.

It is also important to 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 unspecific 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 later 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 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 you 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 that 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 manaually. 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 leverage 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 [build 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 leverage 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 [build 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.

It is also important to 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 your 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 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. It is important to 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 indentity 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.

## 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 an 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 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 COLUMNS username UNIQUE;
DEFINE INDEX unique_email ON user COLUMNS 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 all the presence detection triggered is interesting but it's not very useful. 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 another 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 changes 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 then lifecycle of a Live Query. It automatically subscribe to the Live Query when enabled and it will kill the Live Query on cleanup (when the component is unmounted). Correctly cleaning Live Queries would prevent from any memory leak.

```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 the number of frameworks and programming languages you can choose to do so is quite extensive, 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 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}]")
```

Under the hood 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 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 sql --conn 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 sql --conn 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 sql --conn 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, there are a couple of early decisions to be made, such as creating tables and defining the fields that will be in these tables and also the datatypes of the records, and how the tables you describe relate to each other which includes data sharing or manipulation.

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.

In other words, a schema is 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/surrealdb-cli/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 column will be added to the table 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 how you can make a table in SurrealDB deciding what to go with 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, enhancing the system's overall adaptability and functionality.

## 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 leverage the strengths of each based on your application's needs.

With respect to Performance Optimisation, 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 is an advanced technique. It leverages the structured, interconnected nature of knowledge graphs to provide the LLM with a richer, 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 - are 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 ito 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/surrealdb-cli/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, to which we will go with 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
```

Want to give it a try 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. Here's a breakdown of 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/surrealdb-cli/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**: Utilise 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. Happy coding!

---

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` column:

```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 via HTTP & REST with which you can handle different queries from creating simple tables to having 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/surrealdb-cli/commands/start.md) in your terminal:

```bash
surreal start --user root --pass secret
```

The above command starts SurrealDB with [authentication](/docs/reference/cli/surrealdb-cli/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).

It is also important to 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. Then hash 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 check the hash value to the unhashed.

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 we have defined a User we can now log in. To do so, head to the `POST /signin` and using 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 then in a body of the request and add the table content in the body of the request. E.g:

```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 steps are crucial 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 code, 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 `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 eyeball them and make sure that they 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 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 inside `main()`.

```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 `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).

But how do you “think” in a document model database? 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 column 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 tokenizer 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 experimentisation.

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");
```

## 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/surrealdb-cli/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.
- **Powerful 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 rows 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 columns.

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 (column).

```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;
  |              ^^^^^
'
```

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 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 and keeps indexes meaningful when you add spatial indexing in your schema.

---

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. For dense datasets, consider geohash prefixes or spatial indexes (where supported) 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, making it easy to query every record with exactly this hash and no need to use 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) under one roof 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, index and 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", not 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`

What sets SurrealDB apart from graph-only databases is that **edges are tables**, so you can store fields on them like any other row.

```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` row does not keep a `comments` field. A `wrote` edge connects user and comment, and **the edge row** 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 rows 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 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, 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->posts.*` 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`”.

## Chaining paths

From comments, find authors and then all of their comments:

```surql
SELECT <-wrote<-user->wrote->comment FROM comment;
```

## 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 row 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).

## 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 };
```

## 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.

## 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 row, then `comment` to the comment row, 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 rich 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 just foreign keys or nested objects. That enables powerful, intuitive traversal-based queries that reflect real-world systems.

But how do you “think” in a graph database? Instead of focusing on how to break data into tables (relational) or embed data in documents (document model), you concentrate on expressing data as nodes and defining the edges that describe relationships. 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` rows 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; 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?"
	}
]
```

## 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 row 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 row, 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 row 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 is not only shorthand: it can 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).

## 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 row.

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 row, **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 pretty unique 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.

When combining drop tables, table views and live queries, you have a very easy-to-set up, event-based and real-time solution from capturing events, creating metrics, dropping stale data and live selects for visualisation.

### Custom events

If you have something even more bespoke in mind, you can even [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 under the hood, it’s optimised for efficient range selection, through the magic of [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 crucial information into the record ID itself allows you to query only a range of record IDs instead of a whole table.

This effectively means fewer worries about selecting the right indexes or partitions since the ID field already does that naturally in your data as you scale with 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 however not the only way of doing it, you can 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.

It’s very easy and performant to get the connected data, since you don’t have to do any table scans for that either since it links 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 row 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

Now that we’ve established a common understanding of time series data, 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,
    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 under the hood, it’s optimised for efficient range selection, through the magic of [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 crucial information into the record ID itself allows you to query only a range of record IDs instead of a whole table.

This effectively means fewer worries about selecting the right indexes or partitions since the ID field already does that naturally in your data as you scale with 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 however not the only way of doing it, you can 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.

It’s very easy and performant to get the connected data, since you don’t have to do any table scans for that either since it links 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)
	}
]
```

Now that we’ve explored a bit how to store and query event data, let’s turn this 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 pretty unique 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.

When combining drop tables, table views and live queries, you have a very easy-to-set up, event-based and real-time solution from capturing events, creating metrics, dropping stale data and live selects for visualisation.

#### Custom events
If you have something even more bespoke in mind, you can even [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 a 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'.

Let's 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 story, 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 rows. 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’s approach unifies these features to allow you to move from multiple data stores (a dedicated vector database plus separate document database and finally a graph database to stitch them together) to a single source of truth.

But how do you “think” in a vector database? Unlike relational or document models, where the focus is 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.

It 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" />

The vector search feature of SurrealDB will help you do more and dig deeper into your data. This can be used in place of, or together with full-text search.

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 pushes it down *into* 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.

### 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, the condition is not being pushed down (for example, when the vector field is not indexed). A DISKANN index produces an identical `KnnScan` line.

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().

When it comes to search, you can always use brute force.

In SurrealDB, you can use the [brute force approach](/docs/reference/query-language/statements/define/indexes.md#brute-force-method) to search through your vector embeddings and data.

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.

This is where vector indexes come in.

## 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/surrealdb-cli/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 the latest version of SurrealDB is being used (3.1.0), which contains [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 module init`:

```sh
surreal 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 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 module`

From your project directory, compile to a `.surli` artefact with the Surreal CLI:

```sh
surreal 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 module build --debug --out demo.surli .
```

See the [module command reference](/docs/reference/cli/surrealdb-cli/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/surrealdb-cli/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 start --lazy-surrealism
```

Or set:

**Bash**

```bash
SURREAL_LAZY_SURREALISM=true surreal start
```

**PowerShell**

```powershell
$env:SURREAL_LAZY_SURREALISM = "true"
surreal start
```

## Lifecycle

The end-to-end lifecycle is: **build** your crate with [`surreal module`](/docs/reference/cli/surrealdb-cli/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 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/surrealdb-cli/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 module`](/docs/reference/cli/surrealdb-cli/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 module info demo.surli
surreal 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 module init

# Build quickly while iterating
surreal module build --debug --out demo.surli .

# See function signature
surreal module sig --fnc can_drive demo.surli

# Run function with argument 
surreal module run --fnc can_drive --arg 17 demo.surli
```

---

Source: https://surrealdb.com/docs/learn/extensions/plugins/overview

# Surrealism plugins

Surrealism is a plugin feature that allows users to compile Rust functions into modules that can be called from a SurrealDB instance.

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 module init`](/docs/reference/cli/surrealdb-cli/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 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 module init` through to calling the function from SurrealQL.

## Getting started: regular Rust code

Starting a Surrealism project is best done using the `surreal module init` command, which is similar to `cargo run` 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/surrealdb-cli/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 module build --out demo.surli /Users/my_name/my_rust_code
```

For faster local iteration:

```bash
surreal 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/surrealdb-cli/commands/start.md) with the `surreal 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 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 start --user root --pass secret
```

You can then connect through [SurrealDB Studio](/docs/explore/studio.md) or the CLI with the [surreal sql](/docs/reference/cli/surrealdb-cli/commands/sql.md) command:

**Bash**

```bash
SURREAL_CAPS_ALLOW_EXPERIMENTAL=files,surrealism SURREAL_BUCKET_FOLDER_ALLOWLIST="/Users/my_name/my_rust_code/" surreal 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 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.

**Before SurrealDB 3.3**

`AS` connects the module name to the file.

```surql
DEFINE MODULE mod::test AS f"test:/demo.surli";
```

**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;
```

And now the magic begins! Let's give the `parse_number()` function a try, now 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.

In this section we will learn about the multitude of 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 and tricks to get the most out of your queries. For even more information after reading this section, feel free to look into the [reference](/docs/reference/query-language.md) section of the documentation which 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 rest of the import story.

- **`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/surrealdb-cli/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 import`](/docs/reference/cli/surrealdb-cli/commands/import.md)** command is the usual companion to `POST /import`.

---

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.

SurrealDB works well in these environments, but it's important to use the right connection patterns.

## 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({
  user: 'your-user',
  pass: '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({ user: '...', pass: '...' });
```

If you can store pre-generated tokens can securely in environment variables or platform secrets, then using a token directly may be preferred.

```javascript
await db.signin({ token: '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 not only be sent as a single request, but even performed over 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 (_(since v3.1.0)_) 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, while executing the same statement 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).

## 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;
-- (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'
```

## 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 show 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"],
});
```

## 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 it is an opt out of the ACID guarantees by default in 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`).

## Fast table counts

For `SELECT count() … GROUP ALL` over a whole table, a `COUNT` index maintains a running total instead of scanning every row 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). From 3.2.5, a bare `count()` projection can [imply `GROUP ALL`](/docs/reference/query-language/statements/select.md#bare-count-implies-group-all).

---

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. Round-trip is the mental model.

```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.

The "format" here is not JSON or CBOR; it 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](/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](/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. Sequences are SurrealDB's answer to this: shared, durable generators that work on a single node or in a cluster without needing to construct the logic in application code.

## When a sequence helps

Reach for 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 shine 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 also 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 that means less chatter under load and fewer surprises when 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.

## 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/surrealdb-cli/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).

## `$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 row 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.

## 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) (_(since v3.1.0)_) 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 validate`](/docs/reference/cli/surrealdb-cli/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 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.

## 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 properties are worth knowing 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
```

With the location of the error in clear sight, a fix is that much 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) (_(since v3.1.0)_) 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/surrealdb-cli/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;such  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
{
	body: {
		num: 1
	},
	context: {},
	headers: {},
	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
{
	body: {
		num: 2
	},
	context: {},
	headers: {},
	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 into `context`, 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 + { context: { called_at: $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");
```

Possible output:

```surql
{
	body: {
		num: 2
	},
	context: {
		called_at: d'2026-01-16T01:49:44.115351Z'
	},
	headers: {},
	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 data-modifying statements - INSERT, SET, REMOVE, and DELETE - on the /gql endpoint.

_(since v3.2.0)_

ISO GQL 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/surrealdb-cli/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 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 SurrealDB graph data with ISO GQL - a Cypher-like graph pattern language over HTTP and RPC.

_(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 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 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 { people { 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 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 sql` against `memory` or a file path - no separate `surreal start`):

**Bash**

```bash
echo 'eval::gql("MATCH (n:person) RETURN n.name AS name ORDER BY name");' | surreal 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 sql --user root --pass secret --allow-eval-query --pretty --hide-welcome
```

To explore interactively instead, open the same REPL without piping:

```bash
surreal sql --user root --pass secret --allow-eval-query
```

**Remote server** (`surreal start` + `surreal sql -e ws://…`): pass `--allow-eval-query` on **`surreal start`** only. The client REPL does not enable `eval` at runtime.

```bash
surreal 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)` | Rows in table `person` |
| `-[k:knows]->` | Rows 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 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 sql`, or `surreal 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 reach for 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 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 is on by default:

```bash
surreal 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 start --log info --user root --pass secret \
  --allow-experimental gql memory
```

**Bash**

```bash
export SURREAL_CAPS_ALLOW_EXPERIMENTAL=gql
surreal start --log info --user root --pass secret memory
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "gql"
surreal 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 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 sql --user root --pass secret --allow-eval-query
```

**Two processes (remote):** enable eval on **`surreal start`**, then connect with `surreal sql` as usual - capability flags on the client do not turn on `eval` for a remote engine.

```bash
surreal 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

In this section, you will explore GraphQL, an industry-wide recognised protocol for interacting with your data, allowing you to query your data using any preferred method which offers precision and efficiency in data retrieval.

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](/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.

## Key features

GraphQL offers a number of key features that make it a powerful tool for working with SurrealDB:

- **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 | `people(filter, where, order, limit, start, version)` - pluralised table name |
| Aggregate | `people_aggregate(filter, groupBy, …)` |
| Create / update / delete | `createPerson`, `updatePerson`, `deletePerson` (bulk: `createPeople`, …) |

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 **`peopleConnection`** 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 and many others can 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 `people` for table `person`), a **singular fetch** field `person(id: …)`, and `people_aggregate` for aggregates. Under the hood 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-since-vv310-) 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 {
	people {
		name
		age
	}
}
```

```graphql title="Output"
{
	"data": {
		"people": [
			{
				"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 { people { 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 {
	people(limit: 1) {
		name
		age
	}
}
```

```graphql title="Output"
{
	"data": {
		"people": [
			{
				"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 { people(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.

**GraphQL**

```graphql title="Query"
query {
	people(where: { age: { eq: 23 } }) {
		name
	}
}
```

```graphql title="Output"
{
	"data": {
		"people": [
			{
				"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 { people(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 {
	people(order: { asc: age }) {
		name
		age
	}
}
```

```graphql title="Output"
{
	"data": {
		"people": [
			{
				"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 { people(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 start`](/docs/reference/cli/surrealdb-cli/commands/start.md) command, Docker, or SurrealDB Studio.

**CLI**

```bash
surreal start --log debug --user root --password secret
```

**Docker**

```bash
docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:nightly 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 {
  {
    item(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": {
    "item": [
      {
        "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 a powerful HTTP API that allows you to interact with the database programmatically. This API can be used to perform a wide range of database operations, from querying data to modifying records and managing database structures.

The HTTP API is designed to be simple and intuitive, with a RESTful interface that provides a consistent way to interact with the database. You can use the API to perform 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 start` command](/docs/reference/cli/surrealdb-cli/commands/start.md).

```bash
surreal 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 { person { name } }" }' http://localhost:8000/graphql
```

```json title="Response"
{"data":{"person":[{"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 start`](/docs/reference/cli/surrealdb-cli/commands/start.md) command.

```bash
surreal 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 [a serving in 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 `person` table for all records, you can write the following GraphQL query:

```graphql
{
    user {
        firstName
        lastName
        email
    }
}
```

<img src="~/assets/img/image/surrealist/graphql-querying-fields.png" alt="SurrealDB Studio GraphQL query" />

And to get the person with the email "Jon.Doe@surrealdb.com", you can write the following GraphQL query:

```graphql
{
    user(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.

## 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 databases 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).

## Running SurrealDB

### Using SurrealDB Cloud

The easiest way to deploy and scale up SurrealDB is by using SurrealDB Cloud, which allows you to focus on building great products while we take care of running and maintaining it in the most performant and scalable way.

Read more about running SurrealDB [using SurrealDB Cloud ](/cloud).

### Running SurrealDB as a server

When starting the SurrealDB server, it is important to run the server using 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).

Additionally, ensure that the `rocksdb` storage engine is used to store data.

```sh
surreal start --log info rocksdb://path/to/mydatabase
```

### Running SurrealDB embedded in Rust

It is common knowledge among Rust developers that 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 crucial.

```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 = "2", features = ["allocator", "storage-mem", \
  "storage-surrealkv", "storage-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
```

## 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 leveraging external libraries or
implementations.

With native support for indexes in the core database engine, SurrealDB leverages 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.

Indexing support in SurrealDB is in active development, with work focusing on increased support for additional operators, compound indexes, additional index types, and overall improved index performance.

> [!NOTE]
> Currently no indexes are used when performing `UPDATE` or `DELETE` queries on large table, even where indexes are defined.
> We'll be adding support for indexes within `UPDATE`, `UPSERT`, and `DELETE` statements in SurrealDB release `v2.3.0`.

In the meantime, you can improve the performance of `UPDATE` and `DELETE` statements by combining these with a `SELECT` statement.

To improve the performance of an `UPDATE` statement, use a `SELECT` statement within a subquery, selecting only the `id`
field. This will use any defined indexes:

```surql
UPDATE (SELECT id FROM user WHERE age < 18)
SET adult = false;
```

To improve the performance of an `DELETE` statement, use a `SELECT` statement within a subquery, selecting only the `id`
field. This will use any defined indexes:

```surql
DELETE (SELECT id FROM user WHERE age < 18);
```

### Index strategies explained

When using `SELECT`, 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";
```

As such, when updating a single record on a table that contains a unique index, `UPSERT` is much more performant than `UPDATE`.

```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";

-- Don't do this: full table scan to find and update a record
UPDATE user SET name = "Bob Bobson" WHERE email = "bob@bob.com";

-- Do this instead: use the index instead to go directly to the record, no table scan
UPSERT user SET name = "Bob Bobson", email = "bob@bob.com";
```

### 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. One particularly powerful feature is the
ability 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 `name` 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 leverage 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'
	}
]
```

---

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 row 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
	}
]
```

---

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

By default, creates and updates deliver the full record; deletes return nothing unless you add a clause like `RETURN BEFORE`. 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"
'b87cbb0d-ca15-4f0a-8f86-caa680672aa5'
```

## Filtering with WHERE

You can narrow the subscription the same way you would a normal `SELECT`: only rows that match your predicate participate.

```surql
LIVE SELECT * FROM person WHERE age > 18;
```

## 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.

## 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 details some of the patterns you'll want to use when using SurrealDB when time is of the essence, reacting autonomously to changes in the database is required, or when you want to query on aggregated data 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 differentiate one between the other.

```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 at all 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 nice, we'll use a little bit of SurrealQL magic. 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 underscore 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 cheat the system a little bit 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.

Fortunately, there is another method: you can 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(())
}
```

## 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 not only creates an `alert` whenever a weather condition is set to critical, but can even use 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 our powerful database query language that closely resembles traditional SQL but comes with unique 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) that enhance its capabilities. These functions allow you to perform advanced operations and leverage the full potential of SurrealDB's features. Whether you are working with data retrieval, manipulation, or complex computations, SurrealQL's functions offer the tools you need to build robust and scalable applications.

### 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 familiarize yourself with SurrealQL, explore the various [statements](/docs/reference/query-language/statements/overview.md) and their syntax. The statements pages provides comprehensive examples and explanations for each statement type, helping you understand how to construct queries and interact with SurrealDB effectively.

SurrealQL empowers you to leverage the full potential of SurrealDB and enables you to build robust and scalable applications. Let's dive into the world of SurrealQL and unlock the capabilities of SurrealDB together!

### 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) provides a powerful way to [interact with the database directly from your terminal](/docs/reference/cli/surrealdb-cli/overview.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 `node-fetch` library:

   ```javascript
   const fetch = require('node-fetch');

   async function querySurrealDB() {
     // Create a new person record
     await fetch('http://localhost:8000/rpc', {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
       },
       body: JSON.stringify({
         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), our intuitive user interface specifically designed for SurrealDB, 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 provide a convenient way to query data, insert records, and manage database operations programmatically.

SurrealDB offers a variety of SDKs that allow you to interact with the database programmatically. These SDKs provide a convenient and efficient way to query data, insert records, and manage database operations using 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) offers a seamless way to integrate SurrealDB with your Python applications. Whether you are building web applications, data analysis tools, or automation scripts, the Python SDK provides the necessary functions to interact with the database efficiently.

- **.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, making it suitable for building robust and scalable applications.

- **Rust**: The [Rust SDK](/docs/reference/rust.md) leverages the safety and performance features of the Rust programming language. It allows you to interact with SurrealDB in a type-safe manner, ensuring that your database operations are both efficient and reliable.

### 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

In this section, you will explore SurrealQL queries using the SurrealDB CLI. The SurrealDB CLI provides a powerful command-line interface for writing, executing, and visualising SurrealQL queries in real-time.

To get started, [install the SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) on your local machine.

## Getting started

After installing the SurrealDB CLI, you can start writing SurrealQL queries by running the [`surreal start`](/docs/reference/cli/surrealdb-cli/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 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 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 sql`](/docs/reference/cli/surrealdb-cli/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 sql --endpoint http://localhost:8000 --ns main --db main --user root --pass secret --pretty
```

```bash title="Start a SurrealDB shell (in-memory)"
surreal 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 sql --endpoint http://localhost:8000 --ns main --db main --user root --pass secret --pretty --hide-welcome
```

**PowerShell**

```powershell
'SELECT * FROM person;' | surreal 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/surrealdb-cli/overview.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 available for Rust, JavaScript, Python, Go, Java, .NET, and PHP.

SurrealDB supports a number of methods for connecting to the database and performing data queries. Each 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. Here is a list of all the Supported SDKs:

- [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)

## 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**: Only want to retrieve 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. In this section, we'll look at 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 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.

In this section, you will explore [SurrealQL](/docs/reference/query-language.md), a powerful database query language that closely resembles traditional SQL but comes with unique 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. Whether you are querying data, modifying records, or managing database structures, SurrealQL provides a comprehensive set of capabilities to meet your needs.

## Key features

SurrealQL offers several key features that make it a powerful tool for working with SurrealDB:

- **Familiar syntax**: SurrealQL adopts a syntax similar to traditional SQL, making it easy for developers familiar with SQL to transition to SurrealDB seamlessly.

- **Advanced querying**: SurrealQL supports a wide range of querying capabilities, including filtering, sorting, aggregating, and joining data from multiple tables.

- **Data manipulation**: With SurrealQL, you can easily insert, update, and delete records in your SurrealDB database, allowing you to manage your data effectively.

- **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 they 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 sql`](/docs/reference/cli/surrealdb-cli/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 under the hood, 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 outset, because standard CRUD (Create, Read, Update, Delete) operations do not require a schema to be defined at the outset. 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 is not all about defining data, however. 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.

**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"];
```

**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"];
```

## Conclusion

These anonymous functions provide a flexible way to define small, reusable pieces of logic that can be used throughout your queries. By leveraging them, you can write more modular and maintainable SurrealQL code.

---

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 rows, 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/surrealdb-cli/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 dependant 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/surrealdb-cli/commands/import.md), so plan backfills separately or use the [sql](/docs/reference/cli/surrealdb-cli/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.

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 reach for 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.
* Stabilize 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.

---

Source: https://surrealdb.com/docs/learn/schema-management/files/buckets

# Buckets

Defining file buckets: memory, disk, 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-peristent storage.
* A folder on disk for persistence.
* 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, or as "file:/", followed by the path, for storage on disk.

### 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 start --user root --pass \
  secret --allow-experimental files

# Windows (PowerShell)
$env:SURREAL_BUCKET_FOLDER_ALLOWLIST = "/" 
surreal start --user root --pass secret --allow-experimental files
```

### 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.

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 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 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` column, a duplicate entry for that column or 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. Composite indexes are mainly used to create a unique index 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;
```

### 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`. From 3.2.5, a bare `count()` projection [implies `GROUP ALL`](/docs/reference/query-language/statements/select.md#bare-count-implies-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 rows. `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 the an analyzer with
DEFINE ANALYZER example_ascii TOKENIZERS class FILTERS ascii;
-- Since 3.0.0: only FULLTEXT used to benefit from concurrent full-text search
DEFINE INDEX userNameIndex ON TABLE user FIELDS name FULLTEXT ANALYZER example_ascii BM25 HIGHLIGHTS;
```

- `SEARCH` or `FULLTEXT`: By using the `SEARCH` keyword, you enable full-text search on the specified column.
- `ANALYZER ascii`: Uses a custom [analyzer](/docs/reference/query-language/statements/define/analyzer.md) called `example_ascii` which uses the class tokenizier and `ascii` filter to analysing 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 overtime 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. They `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 SEARCH ANALYZER simple BM25(1.2,0.75) 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, it's essential to consider the fields most frequently queried or used to optimise performance.

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/insert.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. Therefore, it's essential to balance the need for read performance with 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 start`](/docs/reference/cli/surrealdb-cli/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` change whether undefined resources may be created implicitly to allow CRUD operations to perform.

### 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 statment. 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/reference/query-language/statements/define/field.md#computed-fields) (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 comprehensive 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/reference/query-language/statements/define/field.md#computed-fields) 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 modeling with active/inactive periods, probability-impact calculations, and automated risk scoring using [futures](/docs/reference/query-language/statements/define/field.md#futures). Demonstrates [unique constraints](/docs/reference/query-language/statements/define/indexes.md#unique-indexes) 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 [futures](/docs/reference/query-language/statements/define/field.md#futures). 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 modeling for complex 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-indexes). Shows how to model complex financial relationships with type-specific behaviors.

```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 comprehensive 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 modeling 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 comprehensive audit trails. Demonstrates complex organisational modeling, [geospatial tracking](/docs/reference/query-language/language-primitives/data-types/geometries.md#point), and mission-critical data management patterns.

```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 catalog, and shopping cart management. Features flexible address storage, multi-currency support, and comprehensive timestamp tracking. Demonstrates modern e-commerce data modeling 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, [materialized views](/docs/reference/query-language/statements/define/table.md#pre-computed-table-views), and search optimization 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 modeling 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 maximum size

In addition to a type, both arrays and sets can have a required number of items built into the type definition itself. 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);
```

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, 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, 30];
-- 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#assertions)

## 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!

```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 not only makes the code cleaner, but makes it easy to reuse in other parts of the schema as well.

```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, making it the perfect type for 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;
```

By the way, 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 is the lever for mutating 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.

## 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;
DEFINE 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 column 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 diffent 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.

```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`, and when an existing `product` is updated if the value is 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` in that a default value is calculated if no other is indicated, otherwise accepting the value given in a query.

```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

As `DEFINE FIELD` statements are computed in alphabetical order, be sure to keep this in mind when using fields that rely on the values of others.

The following example is identical to the above except that `full_name` has been chosen for the previous field `name`. The `full_name` field will be calculated after `first_name`, but before `last_name`.

```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;

-- Creates a `person` with `full_name` of "bob BOBSON", not "bob bobson"
CREATE person SET first_name = "Bob", last_name = "BOBSON";
```

A good rule of thumb is to organise your `DEFINE FIELD` statements in alphabetical order so that the field definitions show up in the same order as that in which they are computed.

## 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)

With this in mind, here are some of the items to keep in mind 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 show, a `WHERE` clause is needed to find two users starting at a `num` of 50, as `START 50` starts based on the user of the record ID, which is 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;
```

Not only is the `START 50 LIMIT 2` query more performant, but 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 perfomant 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. One 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
	}
]
```

When dealing with a large number of records, a more performant option is to 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 from 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>>>;
```

---

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 but worth adding 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 columns 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

## 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 ensures robust resistance against 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 surreal 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. This integration is simple and reliable thanks to JSON Web Key Set (JWKS) support implemented by SurrealDB.
- [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 algorithms 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 a powerful [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 and robust 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/surrealdb-cli/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 coworkers high up the org chart that are not particularly tech-savvy.

```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.

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 leverage 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.

### 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 show 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.

Most powerful features - scripting, functions, network access - 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 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 start`](/docs/reference/cli/surrealdb-cli/commands/start.md) configure the **running database instance**. That is what enforces permissions when clients connect over HTTP, WebSocket, RPC, or [`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) to a remote endpoint.

[`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) accepts the same flags, but they apply differently:

| How you connect | Configure capabilities on |
| --- | --- |
| Remote server (`ws://`, `http://`, …) | **`surreal start`** (or server env vars) |
| Embedded storage (`memory`, `rocksdb://…`, …) | **`surreal sql`** |

When using a remote server, flags on `surreal 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/surrealdb-cli/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/surrealdb-cli/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">
                Deny 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">
                --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 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 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 start --allow-funcs --deny-funcs "http","crypto::md5"

// Allow certain custom functions only (all custom functions start with "fn::")
surreal start --allow-funcs "fn::shared_fn"
```

## 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 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 start --allow-net internal.example.com:443

# Allow access to some private networks but not to others
surreal 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 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

_(since v2.2.0)_

The `--allow-arbitrary-query` and `--deny-arbitrary-query` allows database administrators to allow or deny arbitrary quering by either guest, record or system users, or a combination of those. This capability settings 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 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 start`** when clients connect to a remote instance. It is not required on `surreal sql` for remote connections - only for [embedded REPL sessions](/docs/reference/cli/surrealdb-cli/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).

---

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 - matching how major providers sign tokens at scale. 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.

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.

## 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 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 leverage 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 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 with 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.

## 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.
	);
```

## 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
;
```

## 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 ensure that untrusted data is passed to SurrealDB as SurrealQL parameters, which are independent of the query syntax, preventing SQL injection attacks.

### 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
```

## 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. It is important to note that, although the set of accepted syntax is very conservative, sanitization is less safe that 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>']
```

## 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 to be 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";
```

## 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 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 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
```

## 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;
```

## 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/surrealdb-cli/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 start --web-crt "cert.pem" --web-key "key.pem"
```

## 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. It is important to note that 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 leverage 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 leveraging 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.

It is important to note that, 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.

## Untrusted queries

Due to the powerful SurrealQL language and the addition of 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.

## 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 later 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.

## 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, it is important to take steps to protect tokens in storage from being stolen as a result of these attacks. This is specially 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 you 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 oportunity 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 specially not true in the case of generic backend services such as SurrealDB, where protecting against [Cross-Site Request Forgery (CRSF)](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 configure 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.

## 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 leveraging 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/surrealdb-cli/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

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 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 [expimental 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 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 sprinkle [`.expect()`](/docs/reference/query-language/functions/database-functions/value.md#valueexpect) (_(since v3.1.0)_) 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 version` command.
- The command line arguments that you are using to start the SurrealDB server with the `surreal 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

# Enterprise Edition

What Enterprise Edition adds over Community: distributed live queries. S3-backed file storage, audit logging, FIPS-validated cryptography, trusted execution, and contractual support.

Enterprise Edition is the same server with a licence key applied. The binary differs, so the capabilities below exist only in an Enterprise build and are no-ops on a Community one; nothing about your schema, queries or SDK code changes when you move across.

## What the edition adds

| Capability | What it is | Documented |
| --- | --- | --- |
| **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 |
| **File storage** | S3-compatible backends for file and blob objects, addressed through `DEFINE BUCKET`. | [`DEFINE BUCKET`](/docs/reference/query-language/statements/define/bucket.md) |
| **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 |

## 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/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 [`surrealctl`](/docs/manage/surrealctl/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.

`surrealctl instance list` prints the same list in a terminal, and `--json` makes it scriptable. See [surrealctl instances](/docs/manage/surrealctl/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 export` and `surreal 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.
- **[surrealctl](/docs/manage/surrealctl.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.

`surrealctl instance backup` triggers one from a terminal, which is what you want in a deployment pipeline that runs a migration. See [surrealctl instances](/docs/manage/surrealctl/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 export`](/docs/reference/cli/surrealdb-cli/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 export` and `surreal 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. `surrealctl instance capabilities` does the same from a terminal. See [surrealctl instances](/docs/manage/surrealctl/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 resize an instance:

1. Open **Instance settings → Compute**.
2. Select the instance type you want.
3. Set the value under **Storage capacity** if you also need more disk.
4. 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.

> [!IMPORTANT]
> Storage expansion is unavailable on free instances. Move to a paid instance type to change storage capacity.

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.

`surrealctl instance pause`, `surrealctl instance resume`, and `surrealctl 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.

`surrealctl instance endpoint` prints the same endpoint, and `surrealctl instance token` issues a token for it. Both are useful in scripts and in CI, where opening a browser is not an option. See [surrealctl instances](/docs/manage/surrealctl/instances.md).

---

Source: https://surrealdb.com/docs/manage/instances/connect/via-cli

# Via CLI

Open an interactive SurrealQL shell against an instance with surreal sql, and where to get the token.

Open an interactive SurrealQL shell against an instance with the [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.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 sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) connects to the endpoint and opens a prompt:

```bash title="Connect to an instance"
surreal 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 `surrealctl 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/surrealdb-cli/overview.md):** every command and flag.
- **[surrealctl](/docs/manage/surrealctl/instances.md):** the management-plane CLI. `surrealctl instance sql` opens the same shell against a named instance without you supplying an endpoint, and calls `surreal` 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.

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

`surrealctl instance create` performs the same deploy, which is what you want in CI or in a provisioning script. See [surrealctl instances](/docs/manage/surrealctl/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. A backup you have never restored is an assumption, not a plan.
- **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 for a reason.** 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 export and surreal 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/surrealdb-cli/overview.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 export`](/docs/reference/cli/surrealdb-cli/commands/export.md) writes the contents of one database to a `.surql` file:

```bash title="Export a database to a file"
surreal export \
  --conn 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.

## Importing

[`surreal import`](/docs/reference/cli/surrealdb-cli/commands/import.md) replays that file into a target database:

```bash title="Import a file into an instance"
surreal import \
  --conn 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.
- **[surrealctl instances](/docs/manage/surrealctl/instances.md):** `surrealctl instance import` and `surrealctl 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

`surrealctl instance metrics` and `surrealctl instance logs` print the same data for scripting or for feeding an external monitor. `surrealctl instance watch` follows the state of an instance during a change. See [surrealctl instances](/docs/manage/surrealctl/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.

That default matters. 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, and an allowance nobody remembers adding is the one worth removing.

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. `surrealctl instance capabilities` reads and writes the same configuration from a terminal. See [surrealctl instances](/docs/manage/surrealctl/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.

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.

`surrealctl instance metrics` and `surrealctl instance update` let you read the signals and apply the change from a terminal. See [surrealctl instances](/docs/manage/surrealctl/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.

`surrealctl instance update` sets the version from a terminal, and `surrealctl instance status` reports what an instance is running. The second command is useful for checking a fleet at once. See [surrealctl instances](/docs/manage/surrealctl/instances.md).

## Compared with self-hosting

Self-hosted operators replace the `surreal` binary, run `surreal 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.

_(since v3.1.0)_

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 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 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 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 start`](/docs/reference/cli/surrealdb-cli/commands/start.md) and in the [environment variables](/docs/reference/cli/surrealdb-cli/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`, it does not replace 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. As Community builds never reads them, 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. **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.

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.

_(since v3.1.0)_

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.

A short list of 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 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 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 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/surrealdb-cli/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

Server log levels, text and JSON formats, file and socket output, slow-query logging, and OpenTelemetry log levels.

SurrealDB writes server logs to stderr by default. You can set flags on [`surreal start`](/docs/reference/cli/surrealdb-cli/commands/start.md) or the matching [environment variables](/docs/reference/cli/surrealdb-cli/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 start`](/docs/reference/cli/surrealdb-cli/commands/start.md) and in [Environment variables](/docs/reference/cli/surrealdb-cli/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), the use of which is recommended 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, label catalogue and the complete metric reference for SurrealDB Community and Enterprise, with a migration table from 3.0.

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.

_(since v3.1.0)_

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_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).

### 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>

### 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 row 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**. |

### 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_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** | All 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.

## 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).

**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).

**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 cheap.

## 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/surrealdb-cli/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 start ...
```

Push metrics, logs, and traces to a local collector:

```bash
SURREAL_TELEMETRY_PROVIDER=otlp \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
surreal 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).

---

Source: https://surrealdb.com/docs/manage/observability/slow-query-logging

# Slow-query logging

The Enterprise slow-query log pipeline: how a query qualifies, record shape, rotation, hash chaining, redaction and pipeline self-metrics.

_(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 rows 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

Identical to 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

Identical to 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).

**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).

**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 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 start`](/docs/reference/cli/surrealdb-cli/commands/start.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 SurrealDB’s async runtime with the Tokio console - tasks, polls, and bottlenecks separate from OpenTelemetry metrics and traces.

_(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 `surrealctl org create`. `surrealctl org use` sets which organisation later commands apply to. See [surrealctl organisations](/docs/manage/surrealctl/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.
- **[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.
- **[surrealctl organisations](/docs/manage/surrealctl/organisations.md):** Organisations, teams, invitations, and tokens from the command line.

---

Source: https://surrealdb.com/docs/manage/organisations/aws-marketplace

# AWS Marketplace

Subscribe through AWS Marketplace so charges appear on your AWS bill, and link the subscription to an organisation.

Subscribe through AWS Marketplace to pay for managed instances on your existing AWS bill.

You subscribe on the Marketplace listing, and AWS charges you for the usage. SurrealDB holds no card for the organisation.

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 in [AWS Marketplace](https://aws.amazon.com/marketplace).
2. Select **Subscribe**.
3. Confirm the contract and the pricing terms.
4. Complete the AWS account and payment linkage as prompted.
5. Follow the set-up steps shown after you subscribe.

The last step signs you in to your SurrealDB account, where you authorise the link between the purchase and your organisation.

After the link is in place, [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.

## 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

- **[Billing](/docs/manage/organisations/billing.md):** The direct billing route, and the usage view both 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

Organisation-level billing details, payment methods, discount codes, invoices, and the usage that produces 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).

`surrealctl org spend` and `surrealctl org usage` print the same figures for a script or an internal cost report. See [surrealctl organisations](/docs/manage/surrealctl/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.

## AWS Marketplace subscriptions

If you subscribed through AWS Marketplace, billing runs through your AWS account. Invoices and payment details are in the **AWS Billing and Cost Management** console, and do not appear in Studio. See [AWS Marketplace](/docs/manage/organisations/aws-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

Common questions about managed instances: getting started, limits, security, pricing, legal terms, and troubleshooting.

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 export` and `surreal 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. Storage can increase but never decrease. See [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 export` and `surreal 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 `surrealctl 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/surrealdb-cli/overview.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 ask in the [Discord](https://discord.gg/surrealdb) community. Include steps to reproduce the problem, the instance id, what you expected, and what happened instead.

---

Source: https://surrealdb.com/docs/manage/organisations/members-and-roles

# Members and roles

Invite people to an organisation, track pending invitations, and choose between the Owner, Admin, and Member roles.

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.

`surrealctl team invite` sends an invitation. `surrealctl team list` reports current membership, which helps when you review access across several organisations. See [surrealctl organisations](/docs/manage/surrealctl/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. Custom roles and per-user granular permissions are planned.

## 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.
- **[surrealctl organisations](/docs/manage/surrealctl/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 and rewards when someone signs up through it.

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 to your SurrealDB account, and keep a recovery code for when it is unavailable.

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/manage/surrealctl/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 an email address, and sign in to manage organisations and instances.

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 [`surrealctl`](/docs/manage/surrealctl/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

Community help, paid support plans, and raising a ticket.

Get help from the community, or from a support plan with response-time commitments.

Community help is free, and open to everyone. A support plan is what lets you raise a ticket.

## Community support

The [SurrealDB Discord](https://discord.gg/surrealdb) answers most questions. `#surrealdb-cloud` covers managed instances, and `#help` or `#general` cover everything else. The SurrealDB team reads the server and escalates the issues that need it.

Use the community for questions that carry no confidential detail. Raise a ticket for anything that involves your data, your credentials, or your billing.

## Support plans

A support plan applies to the whole organisation.

Open **Support** in the organisation sidebar. Without a paid plan, the section shows **View plans**, which opens the [pricing page](/pricing). To add a plan, contact the support team.

<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." />

With a plan in place, the same section shows the plan and the tickets for the organisation.

<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." />

Response times depend on the plan. Check what your plan commits to before an incident needs it.

## Raise a ticket

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.

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.

## Account and billing questions

Account and billing questions go to [support@surrealdb.com](mailto:support@surrealdb.com), or through the **Support** section, and need no paid plan. Write from the address associated with your account. See [Billing](/docs/manage/organisations/billing.md).

## 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]
> All of the example commands in this tutorial assume a database running at `http://localhost:8000`, a root user named `root` with the password `secret`, a namespace `main` and a database `main`.
> As host `http://localhost:8000`, namespace `main` and database `main` are default values, only the necessary `--user root` and `--pass secret` will be shown alongside each command.
> To test these commands without `--user root` and `--pass secret`, authentication can be disabled by passing in the `--unauthenticated` flag when [starting the SurrealDB server](/docs/reference/cli/surrealdb-cli/commands/start.md).

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`).
- **Seeding:** apply `.surql` seed data on demand.
- **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.

## Installation

**cargo binstall** (recommended, no compilation required once [cargo binstall is installed](https://github.com/cargo-bins/cargo-binstall#installation)):

```bash
cargo binstall surrealkit
```

**Cargo from source:**

```bash
cargo install surrealkit
```

**Docker:**

```bash
docker pull ghcr.io/surrealdb/surrealkit:latest
```

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.

## 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` file in the working directory
4. Fallback `DATABASE_*` environment variables

| 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`, or `database`/`db` |

The project root (containing `schema/`, `rollouts/`, `snapshots/`, `seed/`, and `tests/`) defaults to `./database`. Override it with the global `--folder` flag or the `SURREALDB_FOLDER` environment variable.

Example connecting via CLI flags:

```bash
surrealkit --user root --pass secret sync
```

## 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
- [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/embed-schema-macro

# embed_schema! macro

The embed_schema! macro bakes your .surql schema files into the Rust binary at compile time, so schema is always in sync with the application that ships it.

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 = "0.7"
```

## 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?;
```

## 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.

## 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. Start a local SurrealDB instance

```bash
surreal start --user root --pass secret
```

## 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
SURREALDB_HOST=http://localhost:8000
SURREALDB_NAMESPACE=dev
SURREALDB_NAME=myapp
SURREALDB_USER=root
SURREALDB_PASSWORD=secret
```

With those set, `surrealkit sync` picks them up automatically.

## 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

---

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. This flag is a deliberate speed bump, not a routine option.

## 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 directly in a Rust application to connect to SurrealDB, sync schema at startup, run rollouts, and seed data without 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 = "0.7"
```

## 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("http://localhost:8000".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.

### 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`.

## 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 two 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) |

## 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
- [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 = "0.7"
surrealdb = "2"
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` row 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/rollouts

# Rollouts

Rollouts provide a controlled, phased migration workflow for shared and production SurrealDB databases, with review, staged execution, and rollback support.

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` row 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/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).

## 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`.

## 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 two 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. |

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({
            syncArgs: [],
        }),
    ],
});
```

On `vite dev`, the plugin:

1. Runs `surrealkit sync` once at startup.
2. Watches `database/schema/**/*.surql`.
3. Re-runs sync on any file change, debouncing concurrent runs.

## 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 |
| `--dry-run` | off | Print what would be applied without modifying the database |
| `--fail-fast` | off | Stop on the first error rather than continuing |

The schema root defaults to `./database`. Override it with the global `--folder` flag or the `SURREALDB_FOLDER` environment variable.

---

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 also holds the [`[typegen]` section](/docs/manage/schema-migration/typegen.md) when TypeScript generation is enabled.

## 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
```

**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
```

## 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"
```

## 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"
```

### `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"
```

### `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, produce machine-readable JSON output, and there is an official GitHub Action that handles installation and execution.

## 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:latest
    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.

## 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.

---

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`) 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`. 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.

## 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/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/surrealdb-cli/overview.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/manage/self-hosted/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, the `surreal fix` migration tool, 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.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.

Reliable backups are central to operating self-hosted SurrealDB. The CLI provides logical backups as SurrealQL using [`surreal export`](/docs/reference/cli/surrealdb-cli/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 import`](/docs/reference/cli/surrealdb-cli/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.

---

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 start`](/docs/reference/cli/surrealdb-cli/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/surrealdb-cli/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.

**Storage engines** are selected by the path on [`surreal start`](/docs/reference/cli/surrealdb-cli/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 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 (beta) | 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 (beta), 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. They remove the operational complexity of running clusters while providing production-ready deployments.

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 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 (beta)

For single-node and embedded workloads, [SurrealKV](https://github.com/surrealdb/surrealkv) is SurrealDB’s own LSM-backed storage engine, developed in concert with the database rather than as a third-party dependency. That co-development shows up in day-to-day operation: SurrealKV exposes a comparatively small [configuration surface](/docs/reference/cli/surrealdb-cli/commands/start.md#supported-parameters-for-surrealkv) and [environment variable set](/docs/reference/cli/surrealdb-cli/environment-variables.md#surrealkv-environment-variables) next to RocksDB’s extensive tuning knobs, and is aimed at embedded and local-first scenarios.

SurrealKV remains **beta**. For conservative production on-disk server deployments today, **prefer RocksDB**. For embedded deployments where smaller resident memory and in-process behaviour are priorities, SurrealKV is the path to evaluate first. It is nonetheless a serious storage path inside the project: 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 [SurrealMX](/docs/running/in-memory.md) and RocksDB where the engine supports them.

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 start`](/docs/reference/cli/surrealdb-cli/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 and support for 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

## Conclusion

SurrealDB’s architecture lets the same engine and query language run across embedded, single-node, distributed, and managed models. Whether you embed SurrealDB in a browser, run RocksDB on one server, scale horizontally across a multi-node cluster, or use a managed Start or Scale instance, you can match operational and scalability requirements without rewriting queries.

## 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/docker

# Docker

A tutorial to run SurrealDB from within Docker.

Use this tutorial to run SurrealDB from within Docker.

## Running the SurrealDB server using Docker

To get started using Docker, you can use the `latest` tag. To view all the available versions and tags, or to use a specific tag visit the [Docker Hub](https://hub.docker.com/r/surrealdb/surrealdb) page. To start a server use the [`start`](/docs/reference/cli/surrealdb-cli/commands/start.md) command. In Docker, SurrealDB listens on port `8000` in all interfaces by default so that the host can connect to the container in the default bridge networking mode.

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

> [!IMPORTANT]
> For local development, use the `latest-dev` image variant (i.e., docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest-dev start). This version includes a shell and package manager, allowing you to install tools and interact with the container's internals.

In order to persist data when the Docker instance is restarted or shut down, specify a Docker folder using the Docker `-v` command line argument, and use the on-disk storage engine in SurrealDB using the path prefix chosen as a Docker folder.

```bash
mkdir mydata # Create a directory to store the database, owned by the current user
docker run --rm --pull always -p 8000:8000 --user $(id -u) -v $(pwd)/mydata:/mydata surrealdb/surrealdb:latest start rocksdb:/mydata/mydatabase.db
```

The default logging level for the database server is `info`. To control the logging verbosity, specify the `--log` argument. The following command starts the database with `debug` level logging, resulting in more logs being output to the terminal. If extra verbosity is not needed, specify a lower level or simply remove the flag, which will default to the `info` level.

```bash
mkdir mydata # Create a directory to store the database, owned by the current user
docker run --rm --pull always -p 8000:8000 --user $(id -u) -v $(pwd)/mydata:/mydata surrealdb/surrealdb:latest start --log debug rocksdb:/mydata/mydatabase.db
```

### Configuring authentication

Authentication is enabled by default on SurrealDB, while the `--unauthenticated` flag can be used to opt out.

To set up access as an authenticated user, configure your initial root-level user by setting the `--user` and `--pass` command-line arguments.

The following command starts the database with a top-level user named `root` with a password set to `secret`. The root user will be persisted in storage, which means you don't have to include these two arguments the next time you start SurrealDB.

```bash
docker run --rm --pull always -p 80:8000 -v /mydata:/mydata surrealdb/surrealdb:latest start --user root --pass secret rocksdb:mydatabase.db
```

In order to change the default port that SurrealDB uses for web connections and from database clients you can use the Docker `-p` command line argument to tunnel the port to the internal SurrealDB port which SurrealDB is served on. The following command starts the database on port `80`.

```bash
docker run --rm --pull always -p 80:8000 -v /mydata:/mydata surrealdb/surrealdb:latest start --user root --pass secret rocksdb:/mydata/mydatabase.db
```

After running the above command, you should see the SurrealDB server start up successfully.

```bash
docker run --rm --pull always -p 80:8000 -v /local-dir:/container-dir surrealdb/surrealdb:latest start --user root --pass secret rocksdb:/container-dir/mydatabase.db

2025-08-30T15:06:34.788739Z  INFO surreal::dbs: ✅🔒 Authentication is enabled 🔒✅
2025-08-30T15:06:34.788821Z  INFO surrealdb::kvs::ds: Starting kvs store in rocksdb:/container-dir/mydatabase.db
2025-08-30T15:06:34.788859Z  INFO surrealdb::kvs::ds: Started kvs store in rocksdb:/container-dir/mydatabase.db
2025-08-30T15:06:34.789222Z  INFO surrealdb::kvs::ds: Initial credentials were provided and no existing root-level users were found: create the initial user 'root'.
2025-08-30T15:06:35.205123Z  INFO surrealdb::node: Started node agent
2025-08-30T15:06:35.205827Z  INFO surrealdb::net: Started web server on 0.0.0.0:8080
```

For details on the `start` command, and all of the available configuration options and arguments, view the [`start command documentation`](/docs/reference/cli/surrealdb-cli/commands/start.md).

## Using the command-line tools within Docker
The Docker container contains both the server, and the command line tools for importing, exporting, and querying a remote SurrealDB server.

```bash
docker run --rm --pull always surrealdb/surrealdb:latest help
```

The result should look similar to the output below, confirming that the SurrealDB command-line tool was installed successfully.

```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'


SurrealDB command-line interface and server

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://discord.gg/surrealdb).

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 [SUBCOMMAND]

OPTIONS:
	-h, --help    Print help information

SUBCOMMANDS:
	start      Start the database server
	import     Import a SQL script into an existing database
	export     Export an existing database into a SQL script
	version    Output the command-line tool version information
	sql        Start an SQL REPL in your terminal with pipe support
	help       Print this message or the help of the given subcommand(s)
```

For details on the different commands available, visit the [CLI tool documentation](/docs/reference/cli/surrealdb-cli/overview.md).

---

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/surrealdb-cli/overview.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 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 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 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/manage/self-hosted/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 dashboards travel cleanly 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, surreal fix for major versions, 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. When documentation requires it, run [`surreal fix`](/docs/reference/cli/surrealdb-cli/commands/fix.md) to migrate data between layouts, and follow [Migrating from older SurrealDB versions](/docs/build/migrating/from-old-surrealdb-versions/overview.md) plus any linked guides (for example between specific major lines).

**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 export`](/docs/reference/cli/surrealdb-cli/commands/export.md) for a logical copy and/or a storage snapshot - so you can revert if migration or client incompatibility surfaces after deploy.

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:

- **A downgrade is refused if the datastore has run a migration the older build does not ship.** The server reports the migration by name and stops. Reverting past a migration therefore needs [`surreal export`](/docs/reference/cli/surrealdb-cli/commands/export.md) and a reimport into a datastore created by the older version.
- **Do not run `DEFINE SEQUENCE` during a rolling upgrade to 3.3.0.** Sequence definitions move to a new key layout. Upgraded nodes still read the old layout, so existing sequences keep working throughout the rollout, but a sequence created on an upgraded node is not visible to a node still on the previous release. Wait until every node has been upgraded.

> [!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, 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. Upgrading to 3.3.0 repairs it.

---

Source: https://surrealdb.com/docs/manage/surrealctl

# surrealctl

What the surrealctl control-plane CLI does. When to reach for it instead of SurrealDB Studio or the surreal binary, plus a sixty-second quickstart.

`surrealctl` is the command-line interface for the SurrealDB Cloud control plane. Use it to sign in, create and list instances, scale them, pause and resume them, 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 tool 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 [`surrealctl` reference](/docs/reference/cli/surrealctl/overview.md).

## Control plane and data plane

SurrealDB ships two command-line tools, and they divide along a clear line.

| Tool | Plane | Answers |
| --- | --- | --- |
| `surrealctl` | Control plane | Which instances exist, what they run, what they cost, who may reach them |
| [`surreal`](/docs/reference/cli/surrealdb-cli/overview.md) | Data plane | Queries, imports, exports, and running a server yourself |

The two are siblings, not competitors. `surrealctl` never speaks SurrealQL. When you run `surrealctl instance sql`, it resolves the instance, mints a database token, and hands off to the `surreal` binary - the same binary you would run by hand, with the endpoint and credentials already filled in. The same applies to `surrealctl instance import` and `surrealctl instance export`.

That is the whole boundary. Three commands cross it; nothing else needs `surreal`.

## When to use which tool

- **[SurrealDB Studio](https://studio.surrealdb.com)** - sign-up, browsing data, billing pages, and anything you do once. The browser is the fastest route for one-off work.
- **`surrealctl`** - 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`** - schema, queries, and data. Also a local server for development.

`surrealctl` and Studio talk to the same API, so a change made in one shows up in the other.

## Sixty-second quickstart

Install the binary, then sign in and deploy something.

```bash title="First session"
surrealctl auth login
surrealctl whoami
surrealctl org list
surrealctl org use acme

surrealctl instance create api --type shared-1 --region aws-euw1
surrealctl instance sql api -- --ns app --db main
surrealctl instance pause api
surrealctl 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.

For installation instructions, see [Install](/docs/manage/surrealctl/install.md).

## Output that scripts can read

Every command that returns a document takes `--json`, and every command sends its data payload to stdout and everything else - progress, prompts, warnings, errors - to stderr.

```bash title="Machine-readable output"
surrealctl instance list --json | jq -r '.[] | select(.state == "ready") | .name'
```

So `| jq` always works, `--json > out.json` can still prompt you, and a failed command leaves stdout empty rather than half a document. Exit codes distinguish outcomes a pipeline needs to tell apart: `3` for an expired credential, `5` for a name that does not exist, `10` for a wait that gave up on an operation that is still running.

See [Scripting](/docs/manage/surrealctl/scripting.md) for the full contract.

## Command groups

| Group | What it manages |
| --- | --- |
| `auth` | Sign in, sign out, and inspect credentials |
| `org` | Organisations, roles, usage, and spend |
| `instance` | Instances: lifecycle, endpoints, logs, metrics, capabilities, backups |
| `team` | Organisation members |
| `invite` | Organisation invitations |
| `token` | Personal access tokens |
| `catalog` | Regions, instance types, and SurrealDB versions the platform offers |
| `spectron` | SurrealDB Agent Memory contexts, keys, and principals |
| `config` | Configuration stored on this machine |
| `context` | Profiles, and which one is in force |
| `api` | Call the API directly |
| `open` | Open a dashboard page in a browser |
| `status` | Check that everything is configured and reachable |

Nouns are singular and verbs are predictable: `list`, `get`, `create`, `update`, `delete`. The plural forms are aliases, so `surrealctl instances ls` works. Only two commands break the pattern, both because the house verb would mislead: `team remove` ends a membership rather than deleting a person, and `team invite` sends an invitation.

## Deliberate omissions

Three things `surrealctl` will not do, so you do not go looking for them.

- **It does not run queries.** `instance sql` hands off to `surreal`. A wrapper around SurrealQL would be wrong the first time the language gained a feature.
- **There is no sticky current instance.** Organisations persist with `org use`; instances are named on each command or picked interactively. A remembered instance means `instance delete` in a forgotten terminal tab deletes production.
- **It never accepts legal terms on your behalf.** `surrealctl open terms` prints the links and opens them; accepting is something a person does.

## Topics

- [Install](/docs/manage/surrealctl/install.md) - get the binary, sign in, and confirm the setup.
- [Authentication](/docs/manage/surrealctl/authentication.md) - login sessions, personal access tokens, and where credentials live.
- [Instances](/docs/manage/surrealctl/instances.md) - create, connect, scale, pause, back up, and delete.
- [Organisations](/docs/manage/surrealctl/organisations.md) - members, roles, invitations, tokens, usage, and spend.
- [Scripting](/docs/manage/surrealctl/scripting.md) - `--json`, exit codes, and unattended runs.
- [`surrealctl` reference](/docs/reference/cli/surrealctl/overview.md) - every command, flag, and default.

---

Source: https://surrealdb.com/docs/manage/surrealctl/authentication

# Authenticating surrealctl

Choose between a login session and a personal access token, store credentials safely, and authenticate an unattended run.

`surrealctl` 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 `surrealctl` into a pipeline. 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/surrealctl/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 | `surrealctl auth login` | Everyday work at a terminal | Yes |
| Personal access token | `surrealctl 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

`surrealctl auth login` signs in through your identity provider and stores a refresh token plus one access token per audience. See [Install](/docs/manage/surrealctl/install.md#sign-in-for-the-first-time) for the three sign-in flows and when each is used.

Four commands inspect and maintain the session.

```bash
surrealctl auth status          # which credential is in use, read from disk
surrealctl auth status --verify # the same, plus one request to check it
surrealctl auth refresh         # renew now, rather than at the next request
surrealctl 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 `surrealctl 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
surrealctl token scopes
surrealctl 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 about `token create` are worth knowing 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
surrealctl token list
surrealctl 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 `surrealctl 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:  surrealctl 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, each with an environment variable, and none of them writes the token to disk.

<OptionsTable
    title="Credential options"
    options={[
        {
            "name": "--token",
            "value": "<TOKEN>",
            "env": "SURREALCTL_TOKEN",
            "description": "Personal access token to authenticate with. Conflicts with `--token-file`."
        },
        {
            "name": "--token-file",
            "value": "<PATH>",
            "env": "SURREALCTL_TOKEN_FILE",
            "description": "Read the personal access token from a file, or `-` for stdin."
        }
    ]}
/>

`SURREALCTL_TOKEN` is the usual choice in CI, because the environment is where a secret store puts things.

```bash
export SURREALCTL_TOKEN="$(cat /run/secrets/surrealctl)"
surrealctl 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
surrealctl auth login --with-token --label laptop < ~/token.txt
```

## Scopes

Five scopes exist: `read:cloud`, `write:cloud-instances`, `write:cloud-organization`, `write:cloud-billing`, and `write:cloud-spectron`.

Two commands report on them, and they answer different questions.

- `surrealctl token scopes` - the catalogue of scopes a token can be granted. This one is the menu.
- `surrealctl 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
surrealctl context use staging          # switch the active profile
surrealctl --profile staging auth login # sign that profile in
surrealctl context list                 # every profile, and which is active
surrealctl context show                 # what this invocation resolved, and why
```

Each profile stores its own credential, so switching profiles switches identity. `surrealctl 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 described in [Install](/docs/manage/surrealctl/install.md#where-surrealctl-keeps-its-files). `config.toml` holds no secrets and is safe to commit; `credentials.json` is not.

Two behaviours are worth knowing at a terminal. `surrealctl` 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 `surrealctl` is updating credentials, retry in a moment.

The [authentication reference](/docs/reference/cli/surrealctl/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 | `surrealctl auth login`, or `surrealctl 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 `surrealctl 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 `surrealctl 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

- [Instances](/docs/manage/surrealctl/instances.md) - everyday instance work.
- [Scripting](/docs/manage/surrealctl/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/manage/surrealctl/install

# Install

Install surrealctl on macOS, Linux, or Windows, verify the download, sign in for the first time, and keep the binary up to date.

This page installs the `surrealctl` binary, signs you in to SurrealDB Cloud, and confirms that both worked. Pick one installation method. Everything after it applies whichever you chose.

`surrealctl` is a single executable with no runtime dependencies. It is published for macOS, Linux, and Windows, on both `arm64` and `amd64`.

> [!NOTE]
> The current release is a beta. Until the first stable release, the install script and `surrealctl upgrade` both follow the beta channel, and the version you get carries a `-beta` suffix.

## Install with the install script

On macOS and Linux, the install script detects your platform and CPU, downloads the matching asset, checks it against its published SHA-256, and only then puts it in place.

```bash
curl -fsSL https://download.surrealdb.com/surrealctl/install.sh | sh
```

The script is served as plain text, so you can open the same URL in a browser and read it before you run it.

It installs into the first writable directory of `~/.local/bin` and `/usr/local/bin`, creating `~/.local/bin` if neither exists. **It never uses `sudo`.** If the directory it picks is not on your `PATH`, the script tells you and does not edit your shell profile.

### Options

Pass options after `-s --`, which is how `sh` forwards arguments to a script it reads from a pipe.

```bash
curl -fsSL https://download.surrealdb.com/surrealctl/install.sh | sh -s -- --to ~/bin
```

| Option | Effect |
| --- | --- |
| `--version <VERSION>` | Install this exact version, written `1.0.0` or `v1.0.0` |
| `--beta` | Install the newest beta release |
| `--alpha` | Install the newest alpha release |
| `--to <DIR>` | Install into this directory |
| `--dry-run` | Print what would be downloaded and installed, then stop |
| `-h`, `--help` | Print the options and exit |

Two environment variables do the same job as options, which is easier to set in a Dockerfile or a CI step.

| Variable | Effect |
| --- | --- |
| `SURREALCTL_INSTALL_DIR` | Same as `--to` |
| `SURREALCTL_DOWNLOAD_ROOT` | Point the script at a different release host, for testing |

Use `--dry-run` to see the exact URL and destination before anything is written.

```bash title="Check what the script would do"
curl -fsSL https://download.surrealdb.com/surrealctl/install.sh | sh -s -- --dry-run
```

> [!NOTE]
> No musl build is published yet. On Alpine and other musl-based distributions the script stops and says so, rather than installing a glibc binary that cannot start. Build from source on those systems.

## Install on Windows

Download the executable, then put it somewhere on your `PATH`.

```powershell
$version = (Invoke-WebRequest -Uri https://download.surrealdb.com/surrealctl/beta.txt -UseBasicParsing).Content.Trim()
Invoke-WebRequest -Uri "https://download.surrealdb.com/surrealctl/$version/surrealctl-$version.windows-amd64.exe" -OutFile surrealctl.exe
```

Read `latest.txt` instead of `beta.txt` once a stable release is published.

## Download a binary

Every release is published under `https://download.surrealdb.com/surrealctl/`. Use this route for container images, air-gapped hosts, and anywhere you want the version pinned in source control.

Three text files name the current version of each channel. Each contains one line, such as `v1.0.0-beta.1`.

| File | Channel |
| --- | --- |
| `latest.txt` | Stable |
| `beta.txt` | Beta |
| `alpha.txt` | Alpha |

```bash title="Download and install the current beta"
version=$(curl -fsSL https://download.surrealdb.com/surrealctl/beta.txt)
curl -fsSL "https://download.surrealdb.com/surrealctl/${version}/surrealctl-${version}.darwin-arm64.tgz" \
  | tar -xz -C ~/.local/bin
```

Replace the asset name to match your platform.

| Platform | Asset |
| --- | --- |
| macOS, Apple silicon | `surrealctl-<version>.darwin-arm64.tgz` |
| macOS, Intel | `surrealctl-<version>.darwin-amd64.tgz` |
| Linux, ARM64 | `surrealctl-<version>.linux-arm64.tgz` |
| Linux, x86-64 | `surrealctl-<version>.linux-amd64.tgz` |
| Windows, x86-64 | `surrealctl-<version>.windows-amd64.exe` |

The archives hold a single file, `surrealctl`, at the root. Windows ships the executable directly, with no archive.

### Verify a download

Every asset has a `.txt` file beside it holding its SHA-256, and every version directory has a `SHA256SUMS` covering all of them. The install script and `surrealctl upgrade` both check this automatically; verify by hand when you download the asset yourself.

```bash title="Check one asset"
version=$(curl -fsSL https://download.surrealdb.com/surrealctl/beta.txt)
asset="surrealctl-${version}.linux-amd64.tgz"
base="https://download.surrealdb.com/surrealctl/${version}"

curl -fsSLO "${base}/${asset}"
curl -fsSL "${base}/${asset}.txt"
sha256sum "${asset}"
```

The two digests must match. On macOS, use `shasum -a 256` instead of `sha256sum`.

```bash title="Check every asset you downloaded"
curl -fsSLO "${base}/SHA256SUMS"
shasum -c --ignore-missing SHA256SUMS
```

## Build from source

Building needs a Rust toolchain. The repository pins the version it wants, so `rustup` fetches the right one on the first build.

```bash
git clone https://github.com/surrealdb/surrealctl
cd surrealctl
cargo install --path .
```

`cargo install` places the binary in `~/.cargo/bin`, so make sure that directory is on your `PATH`.

## Confirm the installation

```bash
surrealctl version
```

The command prints the version, the commit it was built from, the build date, and the target triple. If the shell reports that the command was not found, the install directory is not on your `PATH`.

`surrealctl --help` lists the command groups. Every group prints its own help, so `surrealctl instance --help` and `surrealctl instance create --help` both work.

## Sign in for the first time

```bash
surrealctl auth login
```

The default flow opens a browser, waits for you to approve the sign-in, and stores the result. Three flows are available and `surrealctl` picks the first one that can work here:

1. **Browser loopback** - opens a browser and listens on `127.0.0.1` for the redirect. The default when a browser is available.
2. **Device code** - prints a short code to type on another device. Used automatically when there is no browser, or on request with `--flow device`.
3. **Paste** - prints a URL and takes back the address the browser was redirected to. Used with `--flow paste`, or when neither of the others can work.

Over SSH, `surrealctl` skips the browser flow without being asked, because a browser on the far end of an SSH connection opens on the wrong machine.

To force a particular flow, name it. A flow you ask for is used or it fails; `surrealctl` does not substitute another one silently.

```bash
surrealctl auth login --flow device
surrealctl auth login --no-browser
```

If the profile already has a credential, `auth login` reports who is signed in and does nothing. Pass `--force` to sign in again.

### Verify with whoami

```bash
surrealctl whoami
```

`whoami` asks the API who you are, so it confirms that the stored credential is accepted and that the network path works. It is the counterpart to `surrealctl auth status`, which reads the local store and touches no network at all.

```bash
surrealctl auth status
```

For what the two credential kinds are and how to use a token in CI, see [Authentication](/docs/manage/surrealctl/authentication.md).

## Check the whole setup

`surrealctl status` - also spelled `surrealctl doctor` - runs eight checks in dependency order and prints one row for each. No check can abort the command, so you get the whole diagnosis in one run.

| Check | What it tells you |
| --- | --- |
| `credential` | Which credential is in use, and whether it is usable. A failure here is fatal |
| `api` | Whether the API base URL answers |
| `version` | The minimum client version the API advertises. Reported, never enforced |
| `cloud session` | Whether a Cloud session is held. Skipped for a personal access token |
| `organization` | Which organisation resolves, and which layer decided it |
| `surreal binary` | Which copy the handoff commands will use |
| `system message` | Any platform notice currently published |
| `release` | Whether a newer `surrealctl` release is available |

Because a failure low in the table is often the one above it restated, read from the top: the first failing row is usually the cause and the rest are symptoms.

This is the one command that still prints its table when it exits non-zero, because the table is the diagnosis.

## Keep surrealctl up to date

`surrealctl` replaces itself.

```bash
surrealctl upgrade
```

The command follows the channel this build is on, verifies the download against its published checksum, runs the new binary once to prove it works, and only then replaces the running one. Nothing is overwritten until every one of those steps has passed.

| Option | Effect |
| --- | --- |
| `--check` | Report whether a newer release exists, and install nothing |
| `--version <VERSION>` | Install this exact version instead of the newest |
| `--channel <CHANNEL>` | Follow `latest`, `beta`, or `alpha` instead of this build's channel |
| `--force` | Replace the binary without confirming |
| `-o`, `--output <PATH>` | Write the new binary here instead of replacing the running one |

```bash title="Ask without installing"
surrealctl upgrade --check
```

> [!IMPORTANT]
> If the binary was installed by a package manager, `upgrade` refuses and names that tool's own command instead. Replacing a file a package manager owns leaves its records wrong, and the next upgrade through that tool would silently put the old version back. Use `brew upgrade`, `cargo install --force`, or your Nix workflow instead.

### Be told when a release lands

`surrealctl status` reports the release check as one of its rows. To be told automatically, at most once a day:

```bash
surrealctl config set release_check true
```

That prints a single line on stderr after a command finishes, and only when a terminal is attached - never in a pipe, a CI job, or `--json` output. Set `SURREALCTL_RELEASE_CHECK` to override the setting for one invocation.

## Shell completion

`surrealctl completion` writes a completion script to stdout for `bash`, `elvish`, `fish`, `powershell`, or `zsh`.

```bash title="zsh"
surrealctl completion zsh > "${fpath[1]}/_surrealctl"
```

```bash title="bash"
surrealctl completion bash > /etc/bash_completion.d/surrealctl
```

## The `surreal` handoff

`instance sql`, `instance import`, and `instance export` hand off to the `surreal` binary. Install it the [usual way](/docs/running/installation.md) and `surrealctl` finds it on your `PATH`.

If it is missing when you run one of those three commands, `surrealctl` offers to fetch a copy.

```text
`surrealctl instance sql` hands off to the `surreal` binary, and it is not installed.
? Download it from https://download.surrealdb.com now? (y/N)
```

The download lands in `~/.config/surrealctl/bin`, never in `/usr/local/bin`, so it needs no `sudo`. The offer appears only when somebody is there to answer it: an unattended run gets an error naming the install command instead, and `--yes` does not turn the offer into a silent download.

`surrealctl` looks for the binary in this order, and the managed copy is last.

1. The `surreal_binary` key in the active profile
2. `SURREALCTL_SURREAL_BINARY`
3. `PATH`
4. `~/.config/surrealctl/bin/surreal`

Installing `surreal` properly later therefore takes over with nothing to undo. To point at a specific copy, name it.

```bash
surrealctl config set surreal_binary /opt/surrealdb/bin/surreal
```

A path you name explicitly and that does not resolve is an error, not a fallback - silently using a different binary than the one you asked for is how the wrong version gets blamed.

## Where surrealctl keeps its files

Four paths, all in one directory under `$XDG_CONFIG_HOME/surrealctl` or `~/.config/surrealctl`. XDG applies on macOS too, because this is a terminal tool that people symlink into a dotfiles repository.

| File | Contents |
| --- | --- |
| `config.toml` | Profiles, the remembered organisation, and other non-secrets. Safe to commit |
| `credentials.json` | Tokens, and the values that must be replaced in the same write as them. Mode `0600` |
| `credentials.lock` | An empty advisory lock file, so two concurrent runs cannot corrupt a refresh |
| `bin/surreal` | The managed `surreal` copy, when one was downloaded |

`surrealctl config path` prints the configuration file path. Set `SURREALCTL_CONFIG_DIR` to move the whole directory, or `--config` to point at one configuration file.

> [!WARNING]
> `surrealctl` refuses to read `credentials.json` if it is readable by group or other, and tells you to run `chmod 600` on it. The file holds a refresh token.

## Next steps

- [Authentication](/docs/manage/surrealctl/authentication.md) - login sessions, personal access tokens, and CI credentials.
- [Instances](/docs/manage/surrealctl/instances.md) - your first real workflow.
- [`surrealctl` reference](/docs/reference/cli/surrealctl/overview.md) - every command and flag.

---

Source: https://surrealdb.com/docs/manage/surrealctl/instances

# Instances

Deploy, find, connect to, scale, pause, back up, and delete instances with the surrealctl 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. For every flag on every verb, see the [`surrealctl` reference](/docs/reference/cli/surrealctl/overview.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
surrealctl org use acme
```

`--org` overrides it for a single command, and `SURREALCTL_ORG` overrides it for a shell. See [Organisations](/docs/manage/surrealctl/organisations.md#choosing-an-organisation) for the full order of precedence.

Two catalogues tell you what you can deploy.

```bash
surrealctl catalog regions
surrealctl catalog instance-types
surrealctl catalog instance-versions
surrealctl 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>
surrealctl instance create [OPTIONS] --type <SLUG> --region <SLUG> <NAME>
</Synopsis>

Price it first if the cost matters, then deploy.

```bash
surrealctl instance estimate --type shared-1 --region aws-euw1
surrealctl 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 `surrealctl catalog instance-types`."
        },
        {
            "name": "--region",
            "value": "<SLUG>",
            "required": true,
            "description": "The region to deploy into, by slug. See `surrealctl 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 `surrealctl 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. `surrealctl` never replays a create automatically for this reason - if a create fails without a clear answer, run `surrealctl 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
surrealctl instance get production
surrealctl instance get acme/production
surrealctl instance get prod-a1b2c3
```

Omit the name at a terminal and `surrealctl` 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
surrealctl instance list
surrealctl instance list --wide
surrealctl instance list --columns name,state,version --sort name
surrealctl 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
surrealctl instance endpoint production
surrealctl instance sql production -- --ns app --db 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 `$(surrealctl instance endpoint production)` safe to embed in another command.

`instance sql` resolves the instance, mints a database token, and becomes `surreal sql`. Everything after `--` is passed to `surreal` verbatim, so the full flag surface of the sibling CLI is available. The token travels in the child process's environment, never in `argv`, where `ps` and shell history can both read it.

Imports and exports work the same way, but both need a namespace and a database, because `surreal` requires them. The check happens before a token is minted.

```bash
surrealctl instance export production --namespace app --database main -- backup.surql
surrealctl instance import staging --namespace app --database main -- seed.surql
```

To authenticate your own client instead, mint a database token.

```bash
surrealctl instance token production > token.txt
surrealctl 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
surrealctl instance update production --compute-units 4
surrealctl instance update production --storage 100
surrealctl 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 `surrealctl` 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; `surrealctl` reports the cool-off and when the last change happened rather than sending a request that would be refused.

## Pause and resume

```bash
surrealctl instance pause staging
surrealctl 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
surrealctl instance watch production
surrealctl instance watch production --until paused
surrealctl 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 convoy. 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, `surrealctl` 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 `surrealctl instance watch <name>` picks the state back up.

## Read state, logs, and metrics

```bash
surrealctl instance status production
surrealctl instance logs production --level error --limit 100
surrealctl instance logs production --follow
surrealctl instance metrics production --metric cpu --from 2026-08-11T10:00:00Z
surrealctl 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 `surrealctl` 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
surrealctl instance backup list production
surrealctl instance backup create production
surrealctl instance backup policy get production
surrealctl 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=$(surrealctl instance backup list production --json \
  | jq -r 'sort_by(.started_at) | last | .snapshot_id')

surrealctl 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
surrealctl instance capabilities get production
surrealctl 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 `surrealctl` 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
surrealctl instance delete staging
surrealctl 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}"

surrealctl instance create "$name" --type shared-1 --region aws-euw1
surrealctl instance import "$name" --namespace app --database main -- fixtures/seed.surql
surrealctl 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

surrealctl instance list --json \
  | jq -r '.[] | select(.state == "ready" and .name != "production") | .name' \
  | while read -r name; do
        surrealctl 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=$(surrealctl instance backup list production --json \
  | jq -r 'sort_by(.started_at) | last | .snapshot_id')

surrealctl instance create scratch \
  --type shared-1 --region aws-euw1 \
  --restore-from "production/${snapshot}"

surrealctl instance endpoint scratch
```

## Next steps

- [Organisations](/docs/manage/surrealctl/organisations.md) - members, roles, invitations, usage, and spend.
- [Scripting](/docs/manage/surrealctl/scripting.md) - exit codes and the `--json` contract in full.
- [`surrealctl` reference](/docs/reference/cli/surrealctl/overview.md) - every instance flag and default.

---

Source: https://surrealdb.com/docs/manage/surrealctl/organisations

# Organisations

Select an organisation, manage members and invitations, check roles, and read usage and spend from the command line.

An organisation owns instances, members, and billing. This page covers how `surrealctl` 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. `surrealctl` works down a fixed order and stops at the first answer.

1. `--org` on the command line
2. `SURREALCTL_ORG` in the environment
3. `org` in this profile's section of `config.toml`
4. The organisation remembered by `surrealctl 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
surrealctl org list
surrealctl org use acme          # remember it for this profile
surrealctl --org acme instance list
surrealctl org use --clear       # forget it again
```

`surrealctl 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 `SURREALCTL_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]
> `SURREALCTL_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
surrealctl org list
surrealctl org list --all                 # include archived organisations
surrealctl org get acme
surrealctl org create acme-labs --use
surrealctl org update acme --name "Acme Ltd"
surrealctl 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
surrealctl team list
surrealctl team get ada
surrealctl team update ada --role admin
surrealctl 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
surrealctl org roles
surrealctl team invite ada@example.com --role member
surrealctl invite list
surrealctl 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 `surrealctl org roles` for the ones this organisation defines."
        }
    ]}
/>

`surrealctl team invite` and `surrealctl 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
surrealctl org roles        # the roles this organisation can assign
surrealctl 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
surrealctl token scopes
surrealctl token create "ci-read-only" --scope read:cloud | tail -1
surrealctl token list
surrealctl token delete ci-read-only
```

All four verbs need a login session, so a token cannot mint its own replacement. See [Authentication](/docs/manage/surrealctl/authentication.md#personal-access-tokens) for scopes, expiry, and how to capture the secret.

## Usage and spend

```bash
surrealctl org usage                  # consumption across every instance
surrealctl instance usage production  # one instance
surrealctl org spend                  # the current billing period
surrealctl org spend --period 03-2026 # a specific month
surrealctl org plans                  # the plans available to this organisation
```

`--period` is written month first, as `MM-YYYY`. If you write it the other way round, `surrealctl` 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"
surrealctl org spend --period 03-2026 --json | jq '[.[].amount] | add / 100'
```

```bash title="Line items as TSV"
surrealctl 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
surrealctl open org acme
surrealctl open billing acme
surrealctl open instance production
surrealctl 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. `surrealctl` 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"

surrealctl team invite "$email" --role member
surrealctl invite list --columns email,role,status
```

### Report which instances cost the most

```bash title="top-spend.sh"
#!/usr/bin/env bash
set -euo pipefail

surrealctl 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

surrealctl org list --json | jq -r '.[].id' | while read -r org; do
    echo "== ${org}"
    surrealctl --org "$org" team list --json | jq -r '.[] | [.username, .role] | @tsv'
done
```

## Next steps

- [Scripting](/docs/manage/surrealctl/scripting.md) - exit codes, `--json`, and unattended runs.
- [Instances](/docs/manage/surrealctl/instances.md) - the instance lifecycle from the command line.
- [Organisations](/docs/manage/organisations.md) - the same concepts in SurrealDB Studio.

---

Source: https://surrealdb.com/docs/manage/surrealctl/scripting

# Scripting

Drive surrealctl from CI and shell scripts: the JSON contract, exit codes for control flow, unattended runs, and waiting semantics.

This page covers running `surrealctl` 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

`surrealctl` splits its output by purpose, and the split is absolute.

- **stdout** carries the command's data payload. Nothing else, ever.
- **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: `surrealctl 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
surrealctl instance list --json | jq -r '.[] | select(.state == "ready") | .name'
surrealctl instance get production --json | jq -r '.version'
surrealctl 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 `surrealctl`, 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": "surrealctl 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 `surrealctl` 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 ! surrealctl instance pause api --json > /dev/null 2> error.json; then
    jq -r '"\(.kind): \(.message)"' < error.json >&2
fi
```

`not_available` is worth calling out: it means a feature is not enabled for this deployment. Nothing is broken.

## Exit codes

Error text is not a contract. These numbers are.

| 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 |
| `30` | An interactive sign-in did not complete |
| `130` | Interrupted |

`0`, `1`, and `2` keep their conventional meanings, so `if ! surrealctl …` and usage errors behave exactly as they do with the `surreal` CLI. Everything above `2` is additive.

`10` earns 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
surrealctl instance get api --json > instance.json 2> error.json || status=$?

case "$status" in
    0)  ;;
    5)  surrealctl 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": "SURREALCTL_JSON",
            "description": "Emit machine-readable JSON on stdout."
        },
        {
            "name": "--no-input",
            "env": "SURREALCTL_NO_INPUT",
            "description": "Never prompt for input; fail instead. Also spelled `--non-interactive`."
        },
        {
            "name": "--yes",
            "short": "-y",
            "env": "SURREALCTL_YES",
            "description": "Assume yes for every confirmation."
        },
        {
            "name": "--quiet",
            "short": "-q",
            "description": "Suppress progress and informational output."
        },
        {
            "name": "--plain",
            "env": "SURREALCTL_PLAIN",
            "description": "Disable tables, spinners, and relative times."
        },
        {
            "name": "--org",
            "value": "<ORG>",
            "env": "SURREALCTL_ORG",
            "description": "The organisation to operate on, by id or name."
        },
        {
            "name": "--token",
            "value": "<TOKEN>",
            "env": "SURREALCTL_TOKEN",
            "description": "Personal access token to authenticate with."
        },
        {
            "name": "--timeout",
            "value": "<DURATION>",
            "default": "30s",
            "env": "SURREALCTL_TIMEOUT",
            "description": "Maximum time to wait for a single API request."
        },
        {
            "name": "--retries",
            "value": "<N>",
            "default": "3",
            "env": "SURREALCTL_RETRIES",
            "description": "How many times to retry a failed request."
        },
        {
            "name": "--debug",
            "env": "SURREALCTL_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 SURREALCTL_NO_INPUT=1
surrealctl instance delete staging --force
```

**PowerShell**

```powershell
$env:SURREALCTL_NO_INPUT = "1"
surrealctl instance delete staging --force
```

## CI detection and output modes

`surrealctl` 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/manage/surrealctl/authentication.md#personal-access-tokens) for how to create one.

```bash
export SURREALCTL_TOKEN="$(cat /run/secrets/surrealctl)"
export SURREALCTL_ORG=67upifj5dt6p87ch3nh5t3a8
surrealctl instance list --json
```

Set `SURREALCTL_ORG` to the organisation **id**. An id is used as it stands; a name costs a lookup on every command.

```yaml title=".github/workflows/instance-report.yml"
name: instance-report

on:
  schedule:
    - cron: "0 6 * * *"

jobs:
  report:
    runs-on: ubuntu-latest
    env:
      SURREALCTL_TOKEN: ${{ secrets.SURREALCTL_TOKEN }}
      SURREALCTL_ORG: ${{ vars.SURREALCTL_ORG_ID }}
      SURREALCTL_NO_INPUT: "1"
    steps:
      - run: curl -fsSL https://download.surrealdb.com/surrealctl/install.sh | sh
      - run: surrealctl status
      - run: surrealctl instance list --json > instances.json
      - uses: actions/upload-artifact@v4
        with:
          name: instances
          path: instances.json
```

`surrealctl status` early in a job is worth the one second it costs: 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/manage/surrealctl/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
surrealctl 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
    surrealctl instance watch api
elif [ "$status" -ne 0 ]; then
    exit "$status"
fi
```

Interrupting a wait does not stop the operation. It continues server-side, and `surrealctl 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`.

That leaves a small amount for your script to know about re-runs.

| 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, `surrealctl api` sends the request with the credential, headers, and retry policy already handled.

```bash
surrealctl api get /api/cloud/v0/organizations
surrealctl api get /api/cloud/v0/organizations --include
surrealctl 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
surrealctl config list
surrealctl config set json true
surrealctl config set org acme
surrealctl 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 "$(surrealctl config get org)" ]` behaves.

## Checklist before shipping a pipeline

- Set `SURREALCTL_NO_INPUT=1` so nothing can block on a prompt.
- Set `SURREALCTL_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 `surrealctl status` first, so a misconfigured job says so in one line.

## Next steps

- [Authentication](/docs/manage/surrealctl/authentication.md) - credentials, scopes, and profiles.
- [Instances](/docs/manage/surrealctl/instances.md) - the workflows these scripts drive.
- [`surrealctl` reference](/docs/reference/cli/surrealctl/overview.md) - every command, flag, default, and environment variable.

---

Source: https://surrealdb.com/docs/reference/cli

# CLI Tools

Reference for surrealctl, surreal and surqlfmt. The control plane, the data plane, and formatting SurrealQL.

SurrealDB ships three command-line tools. They are installed separately and cover different jobs, so most workflows use more than one of them.

| Tool | Covers | Reference |
| --- | --- | --- |
| `surrealctl` | The control plane: organisations, instances, members, tokens, and billing. | [surrealctl](/docs/reference/cli/surrealctl/overview.md) |
| `surreal` | The data plane: run a server, query it, and move data in and out. | [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) |
| `surqlfmt` | Formatting `.surql` files to a consistent style. | [SurrealQL formatter](/docs/reference/cli/formatter/overview.md) |

## surrealctl

Every subcommand has its own page under [surrealctl commands](/docs/reference/cli/surrealctl/commands.md).

`surrealctl` manages SurrealDB Cloud from the command line: it signs you in, selects an organisation, and creates, scales, pauses, and deletes instances. Every command can emit JSON, so it is the scriptable path for CI pipelines and provisioning scripts.

```bash
surrealctl auth login
surrealctl instance create api --type shared-1 --region aws-euw1
```

`surrealctl` never replaces `surreal`. For SurrealQL work against a Cloud instance it resolves the endpoint and credentials, then hands off to the `surreal` binary.

See the [surrealctl reference](/docs/reference/cli/surrealctl/overview.md) for installation, authentication, global flags, and every command.

## SurrealDB CLI

The `surreal` binary is the data-plane tool. It starts a server, opens an interactive SurrealQL shell, imports and exports data, validates query files, and reports the version in use.

```bash
surreal start --user root --pass secret
surreal sql --username root --password secret --pretty
```

See the [SurrealDB CLI reference](/docs/reference/cli/surrealdb-cli/overview.md) for a walkthrough, the [command list](/docs/reference/cli/surrealdb-cli/commands.md) for each subcommand, and [environment variables](/docs/reference/cli/surrealdb-cli/environment-variables.md) for the `SURREAL_*` variables that mirror its flags.

## SurrealQL formatter

`surqlfmt` reformats `.surql` files to a consistent style. It is distributed as an npm package rather than as part of the `surreal` binary, 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/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 third command-line tool alongside [`surreal`](/docs/reference/cli/surrealdb-cli/overview.md) and [`surrealctl`](/docs/reference/cli/surrealctl/overview.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.

The `surreal` binary also has a built-in [`format`](/docs/reference/cli/surrealdb-cli/commands/format.md) command, which formats one file at a time and takes no style options. `surqlfmt` is the tool for sweeping a tree of `.surql` files or gating a CI job on formatting.

For the other command-line tools, see the [CLI tools overview](/docs/reference/cli.md).

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/authentication

# Authentication reference

How surrealctl 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.

`surrealctl` 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** | `surrealctl auth login` | Interactive use. Can do everything your role allows. |
| **Personal access token** | `surrealctl 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
surrealctl 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"
surrealctl 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"
surrealctl --profile staging auth login
```

### How the browser flow is hardened

Worth knowing 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 SURREALCTL_TOKEN=sdbp_...
surrealctl instance list
```

**PowerShell**

```powershell title="From the environment - the usual choice in CI"
$env:SURREALCTL_TOKEN = "sdbp_..."
surrealctl instance list
```

```bash title="From a file, or from stdin"
surrealctl --token-file /run/secrets/surrealctl instance list
cat /run/secrets/surrealctl | surrealctl --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 "$SURREALCTL_TOKEN" | surrealctl 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:  surrealctl 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`, `spectron key rotate` 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:

| 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 |
| `write:cloud-spectron` | Manage SurrealDB Agent Memory contexts and keys |

Two commands answer two different questions. [`token scopes`](/docs/reference/cli/surrealctl/commands/token.md#token-scopes) is the catalogue of what *can* be granted; [`auth scopes`](/docs/reference/cli/surrealctl/commands/auth.md#auth-scopes) is what the credential in hand *carries*. One is the menu, the other is the receipt.

`auth scopes` on a login session returns an empty list and points you at [`org permissions`](/docs/reference/cli/surrealctl/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/surrealctl/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 |
| `bin/surreal` | The managed copy of the `surreal` binary, when one was downloaded |

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 directory containing the path given to `--config`
2. `SURREALCTL_CONFIG_DIR`
3. `$XDG_CONFIG_HOME/surrealctl`, or `~/.config/surrealctl` when that variable is unset

On Windows the platform configuration directory is used instead - typically `%APPDATA%\surrealctl`.

XDG applies on macOS too, rather than `~/Library/Application Support`: this is a terminal tool people symlink into dotfiles repositories, and the macOS path contains a space that shells and documentation both handle badly. `gh` and `aws` make the same choice.

```bash title="Print the resolved path"
surrealctl 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/.config/surrealctl/credentials.json is mode 644 and holds a refresh token.
Fix it with:  chmod 600 /Users/ana/.config/surrealctl/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 *"surrealctl 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 surrealctl is updating credentials (waited 30s for /Users/ana/.config/surrealctl/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"
surrealctl 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"
surrealctl 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 |
| --- | --- |
| `SURREALCTL_CLOUD_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. |
| `SURREALCTL_AUTH_CLIENT_ID` | Override the OAuth client id, for a non-production tenant |
| `SURREALCTL_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/surrealctl/commands/auth.md) - every flag on every verb
- [`token` commands](/docs/reference/cli/surrealctl/commands/token.md) - creating and revoking tokens
- [Global flags](/docs/reference/cli/surrealctl/global-flags.md) - `--token`, `--token-file`, `--profile`
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - database credentials, which are a separate concern from these

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands

# surrealctl commands

A map of every surrealctl command group and leaf command, with links to the page documenting each one.

`surrealctl` 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/surrealctl/global-flags.md), and every command except [`token create`](/docs/reference/cli/surrealctl/commands/token.md#token-create) accepts `--json`.

| Group | Leaf commands |
| --- | --- |
| [`auth`](/docs/reference/cli/surrealctl/commands/auth.md) | `login`, `logout`, `status`, `refresh`, `scopes` |
| [`org`](/docs/reference/cli/surrealctl/commands/org.md) | `list`, `get`, `create`, `update`, `archive`, `use`, `roles`, `permissions`, `usage`, `spend`, `plans` |
| [`instance`](/docs/reference/cli/surrealctl/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/surrealctl/commands/team.md) | `list`, `get`, `invite`, `update`, `remove` |
| [`invite`](/docs/reference/cli/surrealctl/commands/invite.md) | `list`, `create`, `delete` |
| [`token`](/docs/reference/cli/surrealctl/commands/token.md) | `list`, `create`, `delete`, `scopes` |
| [`catalog`](/docs/reference/cli/surrealctl/commands/catalog.md) | `regions`, `instance-types`, `storage-types`, `instance-versions`, `billing-countries` |
| [`spectron`](/docs/reference/cli/surrealctl/commands/spectron.md) | `context`, `key`, `scoped-key`, `access-token`, `principal`, `package`, `scopes`, `verbs`, `providers`, `usage`, `config` |
| [`config`](/docs/reference/cli/surrealctl/commands/config.md) | `list`, `get`, `set`, `unset`, `path`, `edit` |
| [`context`](/docs/reference/cli/surrealctl/commands/context.md) | `show`, `use`, `list` |
| [Other commands](/docs/reference/cli/surrealctl/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/surrealctl/global-flags.md), accepted everywhere.
- The [list presentation flags](/docs/reference/cli/surrealctl/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/surrealctl/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 [`surreal` CLI](/docs/reference/cli/surrealdb-cli/overview.md) instead. See the [overview](/docs/reference/cli/surrealctl/overview.md) for where that boundary sits.

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/auth

# auth

Reference for surrealctl auth - signing in, signing out, inspecting the stored credential, renewing it, and listing what it is permitted to do.

`surrealctl 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>
surrealctl 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/surrealctl/authentication.md#what-each-auth-verb-does-when-nothing-is-signed-in) for why.

## surrealctl auth login {#auth-login}

Sign in to SurrealDB Cloud, storing a credential for the active profile.

<Synopsis>
surrealctl 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"
surrealctl auth login
```

```bash title="Sign in over SSH, where a local browser is no use"
surrealctl auth login --flow device
```

```bash title="Store a personal access token for a profile"
echo "$CI_TOKEN" | surrealctl --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:
  surrealctl 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 `SURREALCTL_TOKEN` are request-scoped and never written to disk.

## surrealctl auth logout {#auth-logout}

Remove the stored credentials for a profile.

<Synopsis>
surrealctl 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"
surrealctl auth logout
```

```bash title="Tear down a profile in a script"
surrealctl --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`.

## surrealctl auth status {#auth-status}

Show which credentials are in use, without touching the network.

<Synopsis>
surrealctl 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?"
surrealctl auth status
```

```bash title="Confirm the credential still works"
surrealctl 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 `SURREALCTL_CLOUD_TOKEN` is supplying a session token.

For the API's own view of your identity, use [`whoami`](/docs/reference/cli/surrealctl/commands/misc.md#whoami), which spends a request.

## surrealctl auth refresh {#auth-refresh}

Renew the stored credentials now, rather than waiting for them to expire.

<Synopsis>
surrealctl 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
surrealctl 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/surrealctl/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.

## surrealctl auth scopes {#auth-scopes}

Show what the current credential is permitted to do.

<Synopsis>
surrealctl 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?"
surrealctl 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/surrealctl/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/surrealctl/commands/token.md#token-scopes).

## Related pages

- [Authentication](/docs/reference/cli/surrealctl/authentication.md) - the credential model, login flows and credential storage
- [`token` commands](/docs/reference/cli/surrealctl/commands/token.md) - creating and revoking personal access tokens
- [`context` commands](/docs/reference/cli/surrealctl/commands/context.md) - switching between profiles
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - database-level authentication, which these credentials do not cover

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/catalog

# catalog

Reference for surrealctl catalog - the platform-wide lists of regions, instance types, storage types, SurrealDB versions and billing countries.

`surrealctl 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/surrealctl/commands/instance.md#instance-create) and [`instance update`](/docs/reference/cli/surrealctl/commands/instance.md#instance-update) expect.

<Synopsis>
surrealctl 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/surrealctl/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/surrealctl/commands/org.md#org-plans) is the organisation-scoped answer - the one to trust before a create.

## surrealctl catalog regions {#catalog-regions}

List the regions instances can be deployed in.

<Synopsis>
surrealctl 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
surrealctl 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"
surrealctl catalog regions --columns slug --no-header
```

The `slug` is what `--region` accepts.

## surrealctl catalog instance-types {#catalog-instance-types}

List the instance types available on the platform.

<Synopsis>
surrealctl 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
surrealctl catalog instance-types
```

```bash title="Cheapest first"
surrealctl 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/surrealctl/commands/org.md#org-plans), or price a specific combination with [`instance estimate`](/docs/reference/cli/surrealctl/commands/instance.md#instance-estimate).

## surrealctl catalog storage-types {#catalog-storage-types}

List the storage instance types available on the platform.

<Synopsis>
surrealctl 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
surrealctl 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.
```

## surrealctl catalog instance-versions {#catalog-instance-versions}

List the SurrealDB versions instances can run.

<Synopsis>
surrealctl catalog instance-versions [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own. The single column id is `version`.

```bash
surrealctl catalog instance-versions
```

```bash title="The version an update would move to"
surrealctl 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.
```

## surrealctl catalog billing-countries {#catalog-billing-countries}

List the countries billing details can be registered in.

<Synopsis>
surrealctl catalog billing-countries [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own. Column ids are `code` and `name`.

```bash
surrealctl catalog billing-countries
```

```bash title="Check one country is supported"
surrealctl catalog billing-countries --json | jq -e '.[] | select(.code == "IE")' > /dev/null
```

## Related pages

- [`instance create`](/docs/reference/cli/surrealctl/commands/instance.md#instance-create) - where these slugs are used
- [`org plans`](/docs/reference/cli/surrealctl/commands/org.md#org-plans) - the organisation-scoped subset
- [`instance estimate`](/docs/reference/cli/surrealctl/commands/instance.md#instance-estimate) - pricing a type and region before committing to it
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/config

# config

Reference for surrealctl config - listing, reading, setting and clearing configuration values, printing the file path, and opening it in an editor.

`surrealctl config` reads and writes the CLI's own configuration file. Nothing here sends a request: the file never leaves the machine.

<Synopsis>
surrealctl 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`, `SURREALCTL_PROFILE` and the file's own `active_profile` settle on - and each one says which profile it touched.

## The keys

Five 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/surrealctl/global-flags.md#the-precedence-chain).

| Key | Type | Description | Outranked by |
| --- | --- | --- | --- |
| `org` | Text | Default organisation for this profile | `SURREALCTL_ORG` |
| `api` | URL | Base URL of the SurrealDB API | `SURREALCTL_API` |
| `json` | Boolean | Always emit machine-readable JSON | `SURREALCTL_JSON` |
| `plain` | Boolean | Always disable tables, spinners and relative times | `SURREALCTL_PLAIN` |
| `surreal_binary` | Text | Path to the `surreal` binary for `sql`, `import` and `export` | `SURREALCTL_SURREAL_BINARY` |

The persisted context - what [`org use`](/docs/reference/cli/surrealctl/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.

## surrealctl config list {#config-list}

List every configuration key and its value.

<Synopsis>
surrealctl config list [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `key`, `value`, `set` and `description`, with `kind` under `--wide`.

```bash
surrealctl 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
surreal_binary           no   Path to the `surreal` binary for sql/import/export
```

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.

## surrealctl config get {#config-get}

Show one configuration value.

<Synopsis>
surrealctl 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 "$(surrealctl config get org)" ]` and `wc -l` agree with each other.

```bash
ORG=$(surrealctl config get org)
```

```bash title="Branch on whether a key is set"
if [ -z "$(surrealctl config get surreal_binary)" ]; then
    echo "using surreal from PATH"
fi
```

**Refusals.** An unrecognised key is a usage error, exit `2`, and lists the keys that exist.

## surrealctl config set {#config-set}

Set a configuration value.

<Synopsis>
surrealctl 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"
surrealctl config set json true
```

```bash title="Point at a specific surreal build"
surrealctl config set surreal_binary /opt/surrealdb/3.2.4/surreal
```

```bash title="Configure a second profile"
surrealctl --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: SURREALCTL_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`.

## surrealctl config unset {#config-unset}

Clear a configuration value.

<Synopsis>
surrealctl 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
surrealctl 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.

## surrealctl config path {#config-path}

Print the path to the configuration file.

<Synopsis>
surrealctl 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
surrealctl config path
```

```text title="Output"
/Users/ana/.config/surrealctl/config.toml
```

```bash title="Back the file up before editing it"
cp "$(surrealctl config path)" "$(surrealctl config path).bak"
```

`credentials.json` lives beside it in the same directory. See [credential storage](/docs/reference/cli/surrealctl/authentication.md#where-credentials-are-stored) for how that directory is chosen.

## surrealctl config edit {#config-edit}

Open the configuration file in your editor.

<Synopsis>
surrealctl config edit [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

```bash
surrealctl config edit
```

The file is opened with `$EDITOR`. Editing it by hand is supported - [`config list`](#config-list) will show whatever you wrote, and unknown keys survive - but a file written by a newer `surrealctl` is refused rather than migrated:

```text
was written by a newer surrealctl (file version 2, this build understands 1).
Refusing to touch it - upgrade surrealctl, or point --config elsewhere.
```

## Related pages

- [Global flags](/docs/reference/cli/surrealctl/global-flags.md) - the flags and environment variables these keys sit beneath
- [`context` commands](/docs/reference/cli/surrealctl/commands/context.md) - switching between profiles
- [`org use`](/docs/reference/cli/surrealctl/commands/org.md#org-use) - remembering an organisation within a profile
- [Authentication](/docs/reference/cli/surrealctl/authentication.md#where-credentials-are-stored) - the credential file beside this one
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/context

# context

Reference for surrealctl context - showing what an invocation resolved, switching between profiles, and listing the profiles that are configured.

`surrealctl 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>
surrealctl context <COMMAND> [OPTIONS]
surrealctl 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/surrealctl/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.

## surrealctl context show {#context-show}

Show the context this invocation resolved.

<Synopsis>
surrealctl context show [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

```bash
surrealctl context show
```

```text title="Output"
Profile         work
Organization    acme
Id              67upif0m8sh1cn1p2c8t
Source          persisted context (`org use`)
API             https://api.surrealdb.com
Config          /Users/ana/.config/surrealctl/config.toml
Store           /Users/ana/.config/surrealctl/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 genuinely what the next command will do, not an approximation of it.

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 `surrealctl org use <name>`.

```bash title="Which organisation would this command hit?"
surrealctl context show --json | jq -r '.organization.name + " via " + .source'
```

## surrealctl context use {#context-use}

Switch to a different profile.

<Synopsis>
surrealctl 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"
surrealctl context use staging
```

```bash title="Create a profile and sign into it"
surrealctl context use staging
surrealctl 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 `SURREALCTL_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
surrealctl --profile staging instance list
```

## surrealctl context list {#context-list}

List the configured profiles.

<Synopsis>
surrealctl context list [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `profile`, `active`, `org` and `api`, with `source` under `--wide`.

```bash
surrealctl 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"
surrealctl 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/surrealctl/global-flags.md#the-precedence-chain) - the chain `context show` reports on
- [`config` commands](/docs/reference/cli/surrealctl/commands/config.md) - the values a profile holds
- [`org use`](/docs/reference/cli/surrealctl/commands/org.md#org-use) - remembering an organisation within a profile
- [`auth` commands](/docs/reference/cli/surrealctl/commands/auth.md) - signing a profile in
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/instance

# instance

Reference for surrealctl instance - creating, scaling, pausing and deleting instances, reading logs, metrics and endpoints, minting tokens, and managing capabilities and backups.

`surrealctl instance` manages instances: the databases themselves. It is the largest group in the CLI, and covers provisioning, scaling, observability, credentials, capabilities and backups.

<Synopsis>
surrealctl instance <COMMAND> [OPTIONS]
surrealctl 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
surrealctl instance get production
surrealctl instance get production-6xk2
surrealctl instance get acme/production
surrealctl 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`. A sticky implicit target is fine for a noun whose verbs are mostly reads, and wrong for one whose verbs include `delete`.

## surrealctl instance list {#instance-list}

List the instances in an organisation.

<Synopsis>
surrealctl 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/surrealctl/global-flags.md#the-precedence-chain).

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/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`.

```bash
surrealctl 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"
surrealctl instance list --json | jq -r '.[] | select(.state == "ready") | .name'
```

## surrealctl instance get {#instance-get}

Show one instance.

<Synopsis>
surrealctl 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
surrealctl instance get production
```

```bash title="One field, for a script"
surrealctl instance get production --json | jq -r .state
```

## surrealctl instance create {#instance-create}

Create an instance.

<Synopsis>
surrealctl 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."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--type",
            "value": "<SLUG>",
            "required": true,
            "description": "The instance type, by slug. See `surrealctl catalog instance-types`."
        },
        {
            "name": "--region",
            "value": "<SLUG>",
            "required": true,
            "description": "The region to deploy into, by slug. See `surrealctl 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. 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."
        }
    ]}
/>

| `--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"
surrealctl instance create api --type shared-1 --region aws-euw1
```

```bash title="A production instance with room to grow"
surrealctl instance create production \
    --type production-2 \
    --region aws-euw1 \
    --compute-units 4 \
    --storage 100
```

```bash title="Clone from a snapshot"
surrealctl instance create staging \
    --type shared-1 \
    --region aws-euw1 \
    --restore-from production/rsnapshot-20260811-150405
```

With the wait, the command reports progress and then emits a fresh detail view. With `--no-wait` it emits the accepted request and hints at `surrealctl instance watch <name>`.

**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, or `--storage` below 1.
- 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 `surrealctl 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. `surrealctl` never retries it after a `502` for exactly that reason. If a create times out, run [`instance list`](#instance-list) before running it again.

## surrealctl instance update {#instance-update}

Change an instance's type, size, or version.

<Synopsis>
surrealctl 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."
        }
    ]}
/>

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"
surrealctl instance update production --compute-units 8
```

```bash title="Upgrade the SurrealDB version"
surrealctl instance update production --version 3.2.4
```

```bash title="Change type and grow storage in one go"
surrealctl instance update production --type production-4 --storage 250
```

**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` or `--storage` below 1.
- A storage cool-off still in force, naming the interval and, when known, when storage was last changed.
- `--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.
```

## surrealctl instance delete {#instance-delete}

Delete an instance and everything in it.

<Synopsis>
surrealctl 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"
surrealctl instance delete staging
```

```bash title="Delete from a teardown script"
surrealctl 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:

```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.

## surrealctl instance pause {#instance-pause}

Pause a running instance.

<Synopsis>
surrealctl 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
surrealctl instance pause staging
```

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.

## surrealctl instance resume {#instance-resume}

Resume a paused instance.

<Synopsis>
surrealctl 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
surrealctl instance resume staging
```

Resuming something already ready behaves like pausing something already paused: a note, a document, exit `0`.

## surrealctl instance watch {#instance-watch}

Watch an operation that was started elsewhere.

<Synopsis>
surrealctl 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.

| `--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"
surrealctl instance watch api
```

```bash title="Wait for a pause to complete"
surrealctl 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.

```bash title="Follow the transitions as they happen"
surrealctl instance watch api --json | jq -r '.event + " " + (.state // "")'
```

## surrealctl instance status {#instance-status}

Show an instance's deployment phase and restorable snapshots.

<Synopsis>
surrealctl 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/surrealctl/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
surrealctl 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.

## surrealctl instance endpoint {#instance-endpoint}

Print the endpoint a client connects to.

<Synopsis>
surrealctl 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=$(surrealctl instance endpoint production)
surreal sql --endpoint "$ENDPOINT" --namespace app --database main
```

```bash title="Under --json, the hosts come too"
surrealctl 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"
}
```

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.

## surrealctl instance token {#instance-token}

Mint a database token for an instance.

<Synopsis>
surrealctl 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/surrealctl/commands/token.md), which authenticates against the control plane.

```bash title="Copy it to the clipboard"
surrealctl instance token production | pbcopy
```

```bash title="Use it with the surreal CLI"
SURREAL_TOKEN=$(surrealctl instance token production) \
    surreal sql --endpoint "$(surrealctl instance endpoint production)" --namespace app --database main
```

**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:  surrealctl instance token production | pbcopy
Or pass --reveal if you meant to see it.
```

Under `--json` the answer is a one-field document carrying the instance id and the token.

## surrealctl instance jwks {#instance-jwks}

Fetch the key set that verifies an instance's tokens.

<Synopsis>
surrealctl 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/surrealctl/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"
surrealctl instance jwks production
```

```bash title="Pipe the whole key set into a verifier"
surrealctl 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.

## surrealctl instance sql {#instance-sql}

Open a SurrealQL session against an instance.

<Synopsis>
surrealctl instance sql [OPTIONS] [INSTANCE] [-- <ARGS>...]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        },
        {
            "name": "[ARGS]...",
            "description": "Arguments for `surreal 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 command does not speak SurrealQL. It resolves the instance, mints a database token, and becomes [`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md). Everything after `--` is forwarded to that command verbatim, because the sibling's flag surface is large and moves independently.

```bash title="Open a shell"
surrealctl instance sql production --namespace app --database main
```

```bash title="Pass flags through to surreal sql"
surrealctl instance sql production -- --ns app --db main --pretty
```

```bash title="Run a query from a pipeline"
echo "SELECT count() FROM person GROUP ALL;" \
    | surrealctl instance sql production --namespace app --database main
```

`--namespace` and `--database` are optional here, because `surreal sql` accepts a session without them. See [the `surreal` handoff](/docs/manage/surrealctl/install.md#the-surreal-handoff) for how the binary is located and what is passed in the child's environment.

**Warnings**, neither of which stops the handoff: a private-only instance, and an instance that is not in a ready phase.

## surrealctl instance import {#instance-import}

Import a file into an instance.

<Synopsis>
surrealctl instance import [OPTIONS] [INSTANCE] [-- <ARGS>...]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        },
        {
            "name": "[ARGS]...",
            "description": "Arguments for `surreal 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."
        },
        {
            "name": "--database",
            "value": "<DB>",
            "required": true,
            "description": "The database to import into."
        }
    ]}
/>

```bash
surrealctl instance import production --namespace app --database main -- ./seed.surql
```

**Refusals.** `surreal import` requires both a namespace and a database, so this command does too. The check fires before a token is minted:

```text
`surrealctl instance import` needs both --namespace and --database; `surreal import` requires them.
```

See [`surreal import`](/docs/reference/cli/surrealdb-cli/commands/import.md) for the flags available after the `--`.

## surrealctl instance export {#instance-export}

Export an instance to a file.

<Synopsis>
surrealctl instance export [OPTIONS] [INSTANCE] [-- <ARGS>...]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        },
        {
            "name": "[ARGS]...",
            "description": "Arguments for `surreal 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."
        },
        {
            "name": "--database",
            "value": "<DB>",
            "required": true,
            "description": "The database to export from."
        }
    ]}
/>

```bash title="Export to a file"
surrealctl instance export production --namespace app --database main -- ./backup.surql
```

```bash title="Export to stdout and compress"
surrealctl instance export production --namespace app --database main -- - | gzip > backup.surql.gz
```

Like `import`, this refuses a missing namespace or database before minting anything. See [`surreal export`](/docs/reference/cli/surrealdb-cli/commands/export.md) for the flags available after the `--`.

For a managed snapshot rather than a SurrealQL dump, use [`instance backup create`](#instance-backup-create).

## surrealctl instance metrics {#instance-metrics}

Show an instance's resource metrics.

<Synopsis>
surrealctl instance metrics [OPTIONS] --metric <METRIC> [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        }
    ]}
/>

<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/surrealctl/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"
surrealctl instance metrics production --metric cpu
```

```bash title="Every sample over a window"
surrealctl 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.

## surrealctl instance logs {#instance-logs}

Show an instance's logs.

<Synopsis>
surrealctl instance logs [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        }
    ]}
/>

<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/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `timestamp`, `level` and `message`, with `pod` under `--wide`.

```bash title="Recent lines"
surrealctl instance logs production
```

```bash title="Errors and warnings only"
surrealctl instance logs production --level error,warn
```

```bash title="Follow, and hand each line to jq"
surrealctl 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. `--json` still carries every line the API sent. `--limit` takes from the start of the window, matching its meaning elsewhere.

`--follow` polls with a moving start time at a fixed five-second interval - there is no streaming endpoint and no interval flag. 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.

## surrealctl instance usage {#instance-usage}

Show what an instance has consumed.

<Synopsis>
surrealctl instance usage [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        }
    ]}
/>

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/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
surrealctl instance usage production
```

For every instance in the organisation at once, use [`org usage`](/docs/reference/cli/surrealctl/commands/org.md#org-usage).

## surrealctl instance estimate {#instance-estimate}

Estimate what an instance would cost.

<Synopsis>
surrealctl 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"
surrealctl 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.

**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.

## surrealctl instance capabilities get {#instance-capabilities-get}

Show an instance's capability configuration.

<Synopsis>
surrealctl instance capabilities get [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        }
    ]}
/>

This command has no options of its own.

```bash
surrealctl instance capabilities get production
```

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 <(surrealctl instance capabilities get staging --json) \
     <(surrealctl instance capabilities get production --json)
```

## surrealctl instance capabilities set {#instance-capabilities-set}

Change an instance's capability configuration.

<Synopsis>
surrealctl 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. Defaults to the resolved one."
        }
    ]}
/>

<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"
surrealctl instance capabilities set production --deny-scripting
```

```bash title="Replace the allowed function list"
surrealctl instance capabilities set production --allow-funcs "array,string,time"
```

```bash title="Deny outbound HTTP from a script"
surrealctl instance capabilities set production --deny-funcs "http::*" --force
```

The diff is printed on stderr in **every** mode, `--json` included, then confirmed unless `--force` is given. Declining prints `Nothing was changed.` and exits `0`.

**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 `surrealctl 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.
```

## surrealctl instance backup list {#instance-backup-list}

List the snapshots an instance can be restored from.

<Synopsis>
surrealctl instance backup list [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        }
    ]}
/>

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/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
surrealctl instance backup list production
```

```bash title="The newest snapshot id"
surrealctl 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. [`instance status`](#instance-status) shows the same snapshots alongside the deployment phase.

## surrealctl instance backup create {#instance-backup-create}

Take a backup of an instance now.

<Synopsis>
surrealctl instance backup create [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        }
    ]}
/>

This command has no options of its own, and carries no `--wait` or `--no-wait`.

```bash
surrealctl 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 never retried automatically. Requesting a backup while one is already queued 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".

Poll [`instance backup list`](#instance-backup-list) to see the snapshot appear.

## surrealctl instance backup policy get {#instance-backup-policy-get}

Show the backup retention policy.

<Synopsis>
surrealctl instance backup policy get [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        }
    ]}
/>

This command has no options of its own.

```bash
surrealctl 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.

## surrealctl instance backup policy set {#instance-backup-policy-set}

Change the backup retention policy.

<Synopsis>
surrealctl instance backup policy set [OPTIONS] [INSTANCE]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[INSTANCE]",
            "description": "The instance, by id, slug or name. Defaults to the resolved one."
        }
    ]}
/>

<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"
surrealctl instance backup policy set production --daily 14
```

```bash title="Snapshot every six hours"
surrealctl instance backup policy set production --frequency-hours 6
```

```bash title="Reset the monthly tier to the organisation's own value"
surrealctl 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. Declining prints `The policy was not changed.` and exits `0`.

**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 `surrealctl 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/surrealctl/long-running-operations.md) - `--wait`, `--no-wait`, polling and exit code `10`
- [`org` commands](/docs/reference/cli/surrealctl/commands/org.md) - the organisation that owns these instances
- [`catalog` commands](/docs/reference/cli/surrealctl/commands/catalog.md) - the type, region and version slugs `create` and `update` expect
- [Installation](/docs/manage/surrealctl/install.md#the-surreal-handoff) - how `sql`, `import` and `export` find the `surreal` binary
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - the data plane: `surreal sql`, `surreal import`, `surreal export`

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/invite

# invite

Reference for surrealctl invite - listing an organisation's pending invitations, sending one, and withdrawing one.

`surrealctl 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/surrealctl/commands/team.md).

<Synopsis>
surrealctl invite <COMMAND> [OPTIONS]
surrealctl 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/surrealctl/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.

## surrealctl invite list {#invite-list}

List an organisation's pending invitations.

<Synopsis>
surrealctl invite list [OPTIONS]
</Synopsis>

This command takes no positional argument. The organisation comes from `--org` and the [precedence chain](/docs/reference/cli/surrealctl/global-flags.md#the-precedence-chain).

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/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
surrealctl 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"
surrealctl invite list --wide
```

An organisation with no pending invitations is a success: exit `0`, and an empty array under `--json`.

## surrealctl invite create {#invite-create}

Invite someone to an organisation.

<Synopsis>
surrealctl 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 `surrealctl org roles` for the ones this organisation defines."
        }
    ]}
/>

```bash
surrealctl 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
    surrealctl 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 `surrealctl 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/surrealctl/commands/org.md#org-roles).

## surrealctl invite delete {#invite-delete}

Withdraw an invitation.

<Synopsis>
surrealctl 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"
surrealctl invite delete dee@acme.example
```

```bash title="By code, without a prompt"
surrealctl invite delete 7f3a91c4 --force
```

**Refusals.** The confirmation names the address the invitation was sent to. Declining 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/surrealctl/commands/team.md#team-remove) for that.

## Related pages

- [`team` commands](/docs/reference/cli/surrealctl/commands/team.md) - members who have already joined
- [`org roles`](/docs/reference/cli/surrealctl/commands/org.md#org-roles) - the roles an invitation can grant
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/misc

# Other commands

Reference for the surrealctl 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 | |

## surrealctl whoami {#whoami}

Show who the API thinks you are.

<Synopsis>
surrealctl whoami [OPTIONS]
</Synopsis>

This command takes no arguments and no options of its own.

```bash
surrealctl 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/surrealctl/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.

## surrealctl api {#api}

Call the API directly.

<Synopsis>
surrealctl 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"
surrealctl api get /api/cloud/v0/organizations
```

```bash title="Query parameters and response headers"
surrealctl api get /api/cloud/v0/organizations --query limit=5 --include
```

```bash title="A write, from a file"
surrealctl api post /api/cloud/v0/organizations --data @org.json --force
```

```bash title="A write, from stdin"
echo '{"name":"acme"}' | surrealctl 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.

## surrealctl open {#open}

Open a dashboard page in a browser.

<Synopsis>
surrealctl open [OPTIONS] [SUBJECT] [REF]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[SUBJECT]",
            "default": "dashboard",
            "description": "What to open: `dashboard`, `org`, `instance`, `context`, `billing` or `terms`."
        },
        {
            "name": "[REF]",
            "description": "The organisation, instance or context, by id, slug or name."
        }
    ]}
/>

This command has no options of its own - only the [global flags](/docs/reference/cli/surrealctl/global-flags.md).

| `[SUBJECT]` | Opens |
| --- | --- |
| `dashboard` | The account's overview page |
| `org` | An organisation's overview |
| `instance` | One instance, by id, slug or name |
| `context` | One SurrealDB Agent Memory context, by id 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"
surrealctl open
```

```bash title="One instance"
surrealctl open instance production
```

```bash title="Billing for the resolved organisation"
surrealctl 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 `surrealctl open org <name>` or `surrealctl open instance <name>`.
```

- `instance` or `context` with no reference:

```text
`open instance` needs to know which one.

Try:  surrealctl open instance <name>
```

`open terms` links to the legal documents and lists the others as notes. This CLI never accepts terms on your behalf.

## surrealctl status {#status}

Check that everything is configured and reachable. Also spelled `surrealctl doctor`.

<Synopsis>
surrealctl status [OPTIONS]
surrealctl doctor [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `check`, `ok` and `detail`.

Seven 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 |
| `surreal binary` | Which `surreal` would be used. Purely local, so it runs offline. Never fatal |
| `system message` | A platform banner, when a session is available and the API has one. No verdict |

```bash
surrealctl status
```

```bash title="Just the failures"
surrealctl 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`.

The `surreal binary` check reports which copy is in use and does not offer to download one; a diagnostic should not fetch a binary. See [the `surreal` handoff](/docs/manage/surrealctl/install.md#the-surreal-handoff).

## surrealctl version {#version}

Output version information.

<Synopsis>
surrealctl 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
surrealctl version
```

```text title="Output"
surrealctl  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 two flag forms `surrealctl -V` and `surrealctl --version` print the version string alone.

For the API's own version and the client version floor it expects, use [`status`](#status).

## surrealctl completion {#completion}

Generate shell completions.

<Synopsis>
surrealctl 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"
surrealctl completion zsh > "${fpath[1]}/_surrealctl"
```

```bash title="bash"
surrealctl completion bash > /etc/bash_completion.d/surrealctl
```

```bash title="fish"
surrealctl completion fish > ~/.config/fish/completions/surrealctl.fish
```

```powershell title="PowerShell"
surrealctl 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 `surrealctl completion bash | head -1` exits `0` rather than failing on the closed pipe.

See [Installation](/docs/manage/surrealctl/install.md#shell-completion) for where each shell expects the file to live.

## Related pages

- [Output and exit codes](/docs/reference/cli/surrealctl/output-and-exit-codes.md) - the `--json` contract and the full exit-code table
- [Authentication](/docs/reference/cli/surrealctl/authentication.md) - what `whoami` and `status` are reporting on
- [Installation](/docs/manage/surrealctl/install.md) - completion setup and the `surreal` handoff
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - for queries, imports, exports and running a server

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/org

# org

Reference for surrealctl org - listing and creating organisations, renaming and archiving them, remembering a default, and reading roles, permissions, usage, spend and plans.

`surrealctl 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 reach for after [`auth`](/docs/reference/cli/surrealctl/commands/auth.md).

<Synopsis>
surrealctl org <COMMAND> [OPTIONS]
surrealctl 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 | |

Personal access tokens are read-only here: the mutating verbs answer 403, exit `4`. See [Authentication](/docs/reference/cli/surrealctl/authentication.md#what-a-personal-access-token-cannot-do).

## surrealctl org list {#org-list}

List the organisations you belong to.

<Synopsis>
surrealctl 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/surrealctl/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
surrealctl 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"
surrealctl org list --columns name --no-header
```

An empty list is a success: exit `0` and an empty array under `--json`.

## surrealctl org get {#org-get}

Show one organisation.

<Synopsis>
surrealctl 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
surrealctl org get acme
```

```bash title="Whichever organisation is in force"
surrealctl 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.

## surrealctl org create {#org-create}

Create an organisation.

<Synopsis>
surrealctl 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
surrealctl org create acme --use
```

**Refusals.** A name outside 1 to 30 characters is a usage error, exit `2`, raised before any request.

## surrealctl org update {#org-update}

Rename an organisation.

<Synopsis>
surrealctl 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
surrealctl org update acme --name "Acme Corporation"
```

Renaming does not change the organisation's id, so scripts that address it by id are unaffected.

## surrealctl org archive {#org-archive}

Archive an organisation.

<Synopsis>
surrealctl 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
surrealctl org archive contoso
```

**Refusals.** The command confirms first unless `--force` or the global `--yes` is given. In a non-interactive session with neither, 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`.

## surrealctl org use {#org-use}

Remember an organisation as the default for this profile.

<Synopsis>
surrealctl 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"
surrealctl org use acme
```

```bash title="Pick from a list"
surrealctl org use
```

```bash title="Forget it again"
surrealctl 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/surrealctl/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 `SURREALCTL_ORG` is set, because the variable outranks what it just wrote.

`org use` remembers an organisation *within* a profile. [`context use`](/docs/reference/cli/surrealctl/commands/context.md#context-use) switches the whole profile.

## surrealctl org roles {#org-roles}

List the roles an organisation can assign.

<Synopsis>
surrealctl 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/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `name` and `permissions`.

```bash
surrealctl org roles
```

The role vocabulary is per organisation, which is why [`team invite`](/docs/reference/cli/surrealctl/commands/team.md#team-invite) and [`team update`](/docs/reference/cli/surrealctl/commands/team.md#team-update) point here rather than validating a `--role` value locally.

## surrealctl org permissions {#org-permissions}

Show what you are permitted to do in an organisation.

<Synopsis>
surrealctl 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/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `resource` and `action`.

```bash
surrealctl org permissions --org acme
```

This is the authorisation model to consult when a command answers 403. [`auth scopes`](/docs/reference/cli/surrealctl/commands/auth.md#auth-scopes) reports the credential's own scopes, which are a different thing.

## surrealctl org usage {#org-usage}

Show resource usage across an organisation's instances.

<Synopsis>
surrealctl 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/surrealctl/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"
surrealctl org usage --sort compute_hours --reverse
```

For one instance rather than all of them, use [`instance usage`](/docs/reference/cli/surrealctl/commands/instance.md#instance-usage).

## surrealctl org spend {#org-spend}

Show an organisation's billed spend.

<Synopsis>
surrealctl 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/surrealctl/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"
surrealctl org spend --period 07-2026
```

```bash title="The ten largest lines"
surrealctl 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 in integer minor units. Formatting is a view concern.

## surrealctl org plans {#org-plans}

List the plans available to an organisation.

<Synopsis>
surrealctl 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/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `name`, `id`, `regions` and `instance_types`, with `description` under `--wide`.

```bash
surrealctl org plans
```

This is the organisation-scoped answer, and the one to trust before an [`instance create`](/docs/reference/cli/surrealctl/commands/instance.md#instance-create). The [`catalog`](/docs/reference/cli/surrealctl/commands/catalog.md) commands list what the platform offers globally, which is always the wider set.

## Related pages

- [`instance` commands](/docs/reference/cli/surrealctl/commands/instance.md) - the instances an organisation owns
- [`team` commands](/docs/reference/cli/surrealctl/commands/team.md) - its members
- [`catalog` commands](/docs/reference/cli/surrealctl/commands/catalog.md) - platform-wide regions, types and versions
- [Global flags](/docs/reference/cli/surrealctl/global-flags.md) - `--org` and the precedence chain
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/spectron

# spectron

Reference for surrealctl spectron - managing SurrealDB Agent Memory contexts, API keys, scoped keys, access tokens, principals and grants, packages, and the scope and provider catalogues.

`surrealctl spectron` manages [SurrealDB Agent Memory](/docs/agent-memory.md): contexts, the API keys and access tokens that reach them, and the principals and grants that decide what those credentials may do.

<Synopsis>
surrealctl spectron <COMMAND> [OPTIONS]
</Synopsis>

| Sub-command | Purpose |
| --- | --- |
| [`context`](#spectron-context-list) | Manage SurrealDB Agent Memory contexts |
| [`key`](#spectron-key-list) | Manage a context's API keys |
| [`scoped-key`](#spectron-scoped-key-create) | Mint keys attenuated below a principal |
| [`access-token`](#spectron-access-token-create) | Mint short-lived access tokens |
| [`principal`](#spectron-principal-list) | Manage principals and their grants |
| [`package`](#spectron-package-list) | Manage context packages |
| [`scopes`](#spectron-scopes) | List a context's scope tree |
| [`verbs`](#spectron-verbs) | List the verbs a grant can name |
| [`providers`](#spectron-providers) | List the model providers a context can use |
| [`usage`](#spectron-usage) | Show a context's token usage |
| [`config`](#spectron-config) | Show a context's configuration |

> [!NOTE]
> Every route in this group answers `501` on a deployment that does not have SurrealDB Agent Memory. That is reported as `not_available` and exits `9` - deliberately not a failure, because nothing is broken. It cannot be predicted locally, so the first SurrealDB Agent Memory command you run is how you find out.

## Naming a context

Every context-scoped route needs both an organisation and a context, and no route accepts a context by name - contexts have no slug, so a name costs one lookup. Commands whose noun lives *inside* a context take `--context <REF>`; the [`spectron context`](#spectron-context-list) verbs take a positional instead.

The inference chain is deliberately shorter than the organisation chain: there is no `spectron context use`, no server default and nothing persisted.

1. No contexts at all is an error naming `--context`.
2. Exactly one context is used silently.
3. Several, in a non-interactive session, is a usage error, exit `2`:

```text
Several Spectron contexts are available and none was chosen.
Pass --context.

Available:
  research
  production
```

4. Several, on a terminal, opens a picker.

[`spectron context list`](#spectron-context-list) is the one command in the group that needs no context of its own, so it is what to run when `--context` has just failed to resolve.

## Grant syntax

Grants are written `<noun>:<verb>=<pattern>[,<pattern>]` and `--grant` is repeatable. Repeats **merge** rather than overwrite, so `--grant memory:read=/a --grant memory:read=/b` grants both patterns.

```bash
--grant memory:read=/projects/acme
--grant memory:read=/projects/acme,/projects/shared
--grant memory:write=/projects/acme --grant memory:read=/
```

A malformed grant is a usage error, exit `2`, and the message repeats the expected shape. Run [`spectron verbs`](#spectron-verbs) for the verbs a grant may name and [`spectron scopes`](#spectron-scopes) for the paths a pattern may match.

## surrealctl spectron context list {#spectron-context-list}

List the SurrealDB Agent Memory contexts in an organisation.

<Synopsis>
surrealctl spectron context list [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `name`, `state`, `region` and `id`, with `host` and `organization_id` under `--wide`.

```bash
surrealctl spectron context list
```

```text title="Output"
NAME        STATE   REGION    ID
research    ready   aws-euw1  67upif0m8sh1cn1p2c8t
production  ready   aws-use1  6a2k9lqzt4v8bn3m1x5c
```

## surrealctl spectron context get {#spectron-context-get}

Show one SurrealDB Agent Memory context.

<Synopsis>
surrealctl spectron context get [OPTIONS] [CONTEXT]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[CONTEXT]",
            "description": "The context, by id or name. Defaults to the only one."
        }
    ]}
/>

This command has no options of its own.

```bash
surrealctl spectron context get research
```

## surrealctl spectron context create {#spectron-context-create}

Create a SurrealDB Agent Memory context.

<Synopsis>
surrealctl spectron context create [OPTIONS] --region <REGION> <NAME>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<NAME>",
            "required": true,
            "description": "A name for the context."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--region",
            "value": "<REGION>",
            "required": true,
            "description": "The region to create the context in."
        }
    ]}
/>

```bash
surrealctl spectron context create research --region aws-euw1
```

Region slugs come from [`catalog regions`](/docs/reference/cli/surrealctl/commands/catalog.md#catalog-regions).

## surrealctl spectron context update {#spectron-context-update}

Rename a SurrealDB Agent Memory context.

<Synopsis>
surrealctl spectron context update [OPTIONS] --name <NAME> [CONTEXT]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[CONTEXT]",
            "description": "The context, by id or name. Defaults to the only one."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--name",
            "value": "<NAME>",
            "required": true,
            "description": "The new name."
        }
    ]}
/>

```bash
surrealctl spectron context update research --name "Research and development"
```

## surrealctl spectron context delete {#spectron-context-delete}

Delete a SurrealDB Agent Memory context.

<Synopsis>
surrealctl spectron context delete [OPTIONS] [CONTEXT]
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "[CONTEXT]",
            "description": "The context, by id or name."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Delete without confirming."
        }
    ]}
/>

```bash
surrealctl spectron context delete research
```

Note that this positional does not fall back to "the only one" the way the read verbs do. A delete that infers its target is a delete in the wrong terminal tab.

**Refusals.** The confirmation names the context. Declining exits `0`. In a non-interactive session without `--force` or `--yes`, it exits `2` having sent nothing.

## surrealctl spectron key list {#spectron-key-list}

List a context's API keys.

<Synopsis>
surrealctl spectron key list [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `name` and `id`, with `spectron_context_id` and `organization_id` under `--wide`.

```bash
surrealctl spectron key list --context research
```

Only the name and the id are stored. The secret exists once, at creation.

## surrealctl spectron key create {#spectron-key-create}

Create an API key.

<Synopsis>
surrealctl spectron key create [OPTIONS] <NAME>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<NAME>",
            "required": true,
            "description": "A name for the key."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--reveal",
            "description": "Print the key even when stdout is a terminal."
        },
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

```bash title="Capture the key"
surrealctl spectron key create "agent runtime" --context research > /run/secrets/spectron-key
```

```bash title="Keep the id and name alongside the secret"
surrealctl spectron key create "agent runtime" --json > key.json
```

Output is the bare key on stdout in text modes, and the whole body under `--json` so the id and name survive for a later revoke. It is never a table - a secret in a table is a secret in a screenshot.

**Refusals.** The [secret guard](/docs/reference/cli/surrealctl/output-and-exit-codes.md#the-secret-guard) fires *before* the request, so a forgotten `--reveal` never costs a key nobody can recover.

## surrealctl spectron key delete {#spectron-key-delete}

Delete an API key.

<Synopsis>
surrealctl spectron key delete [OPTIONS] <KEY>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<KEY>",
            "required": true,
            "description": "The key, by id or name."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Delete without confirming."
        },
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

```bash
surrealctl spectron key delete "agent runtime" --context research --force
```

## surrealctl spectron key rotate {#spectron-key-rotate}

Replace an API key's secret, keeping its id and name.

<Synopsis>
surrealctl spectron key rotate [OPTIONS] <KEY>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<KEY>",
            "required": true,
            "description": "The key, by id or name."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--reveal",
            "description": "Print the new key even when stdout is a terminal."
        },
        {
            "name": "--force",
            "description": "Rotate without confirming."
        },
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

```bash
surrealctl spectron key rotate "agent runtime" --force > /run/secrets/spectron-key
```

Rotation invalidates the previous secret, so redeploy whatever holds it. Like `create`, the secret guard fires before the request.

## surrealctl spectron scoped-key create {#spectron-scoped-key-create}

Mint a scoped key for a principal - a key attenuated below what that principal itself may do.

<Synopsis>
surrealctl spectron scoped-key create [OPTIONS] --principal <REF> <NAME>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<NAME>",
            "required": true,
            "description": "A name for the key."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--principal",
            "value": "<REF>",
            "required": true,
            "description": "The principal to bind the key to, by id or name."
        },
        {
            "name": "--grant",
            "value": "<GRANT>",
            "description": "Attenuate to this grant, as `<noun>:<verb>=<pattern>[,<pattern>]`. Repeatable."
        },
        {
            "name": "--reveal",
            "description": "Print the key even when stdout is a terminal."
        },
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

```bash title="A read-only key below an agent principal"
surrealctl spectron scoped-key create "read-only worker" \
    --principal "research agent" \
    --grant memory:read=/projects/acme \
    --context research \
    > /run/secrets/worker-key
```

This group has one verb on purpose. A renamed command is a broken script, so `scoped-key` stays a group even with nothing to sit beside `create`.

## surrealctl spectron access-token create {#spectron-access-token-create}

Mint a short-lived access token.

<Synopsis>
surrealctl spectron access-token create [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--ttl",
            "value": "<DURATION>",
            "description": "How long the token should live, such as `15m` or `2h`. The server clamps it."
        },
        {
            "name": "--reveal",
            "description": "Print the token even when stdout is a terminal."
        },
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

```bash
TOKEN=$(surrealctl spectron access-token create --ttl 15m --context research)
```

`--ttl` uses the standard [duration syntax](/docs/reference/cli/surrealctl/global-flags.md#duration-syntax). The server clamps whatever you ask for to its own maximum, so read the expiry from the answer rather than assuming your value was taken.

## surrealctl spectron principal list {#spectron-principal-list}

List a context's principals.

<Synopsis>
surrealctl spectron principal list [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `display_name`, `kind`, `id` and `verbs`, with `grants` under `--wide`.

```bash
surrealctl spectron principal list --context research --wide
```

## surrealctl spectron principal create {#spectron-principal-create}

Create a principal.

<Synopsis>
surrealctl spectron principal create [OPTIONS] --kind <KIND> <NAME>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<NAME>",
            "required": true,
            "description": "A display name for the principal."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--kind",
            "value": "<KIND>",
            "required": true,
            "description": "What the principal is: `human`, `agent`, `service` or `unknown`."
        },
        {
            "name": "--grant",
            "value": "<GRANT>",
            "description": "Grant this authority, as `<noun>:<verb>=<pattern>[,<pattern>]`. Repeatable."
        },
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

```bash
surrealctl spectron principal create "research agent" \
    --kind agent \
    --grant memory:read=/projects \
    --grant memory:write=/projects/acme \
    --context research
```

`--kind` is a free-form string rather than a closed set, because the four values above are a convention and a closed set would need a new release before anyone could use a kind Cloud added.

## surrealctl spectron principal update {#spectron-principal-update}

Rename a principal or change its kind.

<Synopsis>
surrealctl spectron principal update [OPTIONS] <PRINCIPAL>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<PRINCIPAL>",
            "required": true,
            "description": "The principal, by id or name."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--name",
            "value": "<NAME>",
            "description": "A new display name."
        },
        {
            "name": "--kind",
            "value": "<KIND>",
            "description": "A new kind."
        },
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

```bash
surrealctl spectron principal update "research agent" --kind service
```

Grants are not changed here. Use [`principal grants set`](#spectron-principal-grants-set) for those.

## surrealctl spectron principal delete {#spectron-principal-delete}

Delete a principal.

<Synopsis>
surrealctl spectron principal delete [OPTIONS] <PRINCIPAL>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<PRINCIPAL>",
            "required": true,
            "description": "The principal, by id or name."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Delete without confirming."
        },
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

```bash
surrealctl spectron principal delete "research agent" --force
```

## surrealctl spectron principal grants set {#spectron-principal-grants-set}

Replace a principal's grants.

<Synopsis>
surrealctl spectron principal grants set [OPTIONS] <PRINCIPAL>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<PRINCIPAL>",
            "required": true,
            "description": "The principal, by id or name."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--grant",
            "value": "<GRANT>",
            "description": "Grant this authority, as `<noun>:<verb>=<pattern>[,<pattern>]`. Repeatable."
        },
        {
            "name": "--revoke",
            "value": "<VERB>",
            "description": "Remove every pattern granted for this verb. Repeatable."
        },
        {
            "name": "--clear",
            "description": "Start from nothing, so `--grant` describes the whole result. Conflicts with `--revoke`."
        },
        {
            "name": "--force",
            "description": "Replace without confirming."
        },
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

The grants route is a full replacement, so this command reads the current set, applies your flags, and confirms the result before writing.

```bash title="Add a pattern to an existing verb"
surrealctl spectron principal grants set "research agent" \
    --grant memory:read=/projects/contoso
```

```bash title="Drop write access entirely"
surrealctl spectron principal grants set "research agent" --revoke memory:write
```

```bash title="Declare the whole grant set, ignoring what was there"
surrealctl spectron principal grants set "research agent" \
    --clear \
    --grant memory:read=/projects \
    --force
```

`--clear` is what makes this command declarative: with it, the flags describe the complete result rather than a change to what exists. Without it, `--grant` merges and `--revoke` subtracts.

## surrealctl spectron package list {#spectron-package-list}

List every context package this deployment offers.

<Synopsis>
surrealctl spectron package list [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own - not even `--context`. The catalogue is a property of the deployment, so it answers even when nothing else is resolvable.

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `name`, `cost`, `billing_period`, `token_limit`, `storage_gb`, `contexts_limit` and `trial_days`, with `id`, `public` and `description` under `--wide`.

```bash
surrealctl spectron package list
```

## surrealctl spectron package org-list {#spectron-package-org-list}

List the packages an organisation has enabled.

<Synopsis>
surrealctl spectron package org-list [OPTIONS]
</Synopsis>

This command takes no positional arguments and no options of its own.

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `package_id`, `enabled_at`, `trial_ends_at` and `subscription_ends_at`, with `disabled_at` and `organization_id` under `--wide`.

```bash
surrealctl spectron package org-list --org acme
```

## surrealctl spectron package enable {#spectron-package-enable}

Enable a package for an organisation.

<Synopsis>
surrealctl spectron package enable [OPTIONS] <PACKAGE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<PACKAGE>",
            "required": true,
            "description": "The package, by id or name."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--coupon",
            "value": "<CODE>",
            "description": "A coupon code to apply to the subscription."
        }
    ]}
/>

```bash
surrealctl spectron package enable "scale" --coupon LAUNCH2026
```

Enabling a package starts a subscription and affects billing. Check [`package list`](#spectron-package-list) for what each one includes first.

## surrealctl spectron package cancel {#spectron-package-cancel}

Cancel an organisation's package subscription.

<Synopsis>
surrealctl spectron package cancel [OPTIONS] <PACKAGE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<PACKAGE>",
            "required": true,
            "description": "The package, by id or name."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--force",
            "description": "Cancel without confirming."
        }
    ]}
/>

```bash
surrealctl spectron package cancel "scale"
```

**Refusals.** The confirmation names the package. Declining exits `0`. In a non-interactive session without `--force` or `--yes`, it exits `2` having sent nothing.

## surrealctl spectron scopes {#spectron-scopes}

List a context's scope tree.

<Synopsis>
surrealctl spectron scopes [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `path`, `name`, `depth`, `children` and `value_policy`, with `created_at`, `parent` and `tombstoned_at` under `--wide`.

```bash
surrealctl spectron scopes --context research --sort path
```

These paths are what a [grant pattern](#grant-syntax) matches.

## surrealctl spectron verbs {#spectron-verbs}

List the verbs a grant can name.

<Synopsis>
surrealctl spectron verbs [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `name`, `noun` and `description`.

```bash
surrealctl spectron verbs --context research
```

The `noun` and `name` columns are the two halves of the `<noun>:<verb>` prefix in a grant.

## surrealctl spectron providers {#spectron-providers}

List the model providers a context can use.

<Synopsis>
surrealctl spectron providers [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `provider` and `models`, with `count` under `--wide`.

```bash
surrealctl spectron providers --context research
```

## surrealctl spectron usage {#spectron-usage}

Show a context's token usage.

<Synopsis>
surrealctl spectron usage [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

Also accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags). Column ids are `model`, `token_kind`, `origin` and `tokens`.

```bash title="Heaviest models first"
surrealctl spectron usage --context research --sort tokens --reverse
```

A one-line total is printed on stderr in text modes, computed before `--limit` is applied. A context with no ceiling gets its own sentence rather than a hole where the limit would be.

## surrealctl spectron config {#spectron-config}

Show a context's configuration.

<Synopsis>
surrealctl spectron config [OPTIONS]
</Synopsis>

This command takes no positional arguments.

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--context",
            "value": "<REF>",
            "description": "The SurrealDB Agent Memory context, by id or name. Defaults to the only one."
        }
    ]}
/>

This command carries no list presentation flags - its answer is a detail view, not a table.

```bash
surrealctl spectron config --context research
```

Not to be confused with [`surrealctl config`](/docs/reference/cli/surrealctl/commands/config.md), which reads and writes the CLI's own configuration file on this machine.

## Related pages

- [SurrealDB Agent Memory documentation](/docs/agent-memory.md) - what contexts, principals and grants are for
- [`org` commands](/docs/reference/cli/surrealctl/commands/org.md) - the organisation that owns a context
- [Output and exit codes](/docs/reference/cli/surrealctl/output-and-exit-codes.md#the-secret-guard) - the secret guard every minting command applies
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/team

# team

Reference for surrealctl team - listing organisation members, inspecting one, inviting someone, changing a role, and ending a membership.

`surrealctl team` manages organisation members: the people who already belong. Invitations that have been sent but not accepted live in [`invite`](/docs/reference/cli/surrealctl/commands/invite.md), and `team list` deliberately does not fold them in - a pending invitation is not a member.

<Synopsis>
surrealctl team <COMMAND> [OPTIONS]
surrealctl 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/surrealctl/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 id or username, with did-you-mean suggestions when nothing matches.

## surrealctl team list {#team-list}

List the members of an organisation.

<Synopsis>
surrealctl team list [OPTIONS]
</Synopsis>

This command takes no positional argument. The organisation comes from `--org` and the [precedence chain](/docs/reference/cli/surrealctl/global-flags.md#the-precedence-chain).

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/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
surrealctl 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"
surrealctl team list --json | jq -r '.[] | select(.role != "member") | .username'
```

## surrealctl team get {#team-get}

Show one member.

<Synopsis>
surrealctl 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
surrealctl team get bo@acme.example
```

A username that does not match any member exits `5`, and lists the near matches it found.

## surrealctl team invite {#team-invite}

Invite someone to an organisation.

<Synopsis>
surrealctl 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 `surrealctl org roles` for the ones this organisation defines."
        }
    ]}
/>

```bash
surrealctl 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/surrealctl/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 `surrealctl 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/surrealctl/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.

## surrealctl team update {#team-update}

Change a member's role.

<Synopsis>
surrealctl 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 `surrealctl org roles` for the ones this organisation defines."
        }
    ]}
/>

```bash
surrealctl team update cai@acme.example --role admin
```

## surrealctl team remove {#team-remove}

End someone's membership of an organisation.

<Synopsis>
surrealctl 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"
surrealctl team remove cai@acme.example
```

```bash title="From an offboarding script"
surrealctl 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. Declining exits `0`. In a non-interactive session without `--force` or `--yes`, it exits `2` having sent nothing.

## Related pages

- [`invite` commands](/docs/reference/cli/surrealctl/commands/invite.md) - pending invitations
- [`org roles`](/docs/reference/cli/surrealctl/commands/org.md#org-roles) - the roles this organisation defines
- [`org permissions`](/docs/reference/cli/surrealctl/commands/org.md#org-permissions) - what a role lets you do
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - database users, which are a separate concept from organisation members

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/commands/token

# token

Reference for surrealctl token - listing, creating and revoking personal access tokens, and browsing the scopes a token can be granted.

`surrealctl token` manages personal access tokens: the `sdbp_…` credentials that authenticate CI jobs and automation against the control plane.

<Synopsis>
surrealctl token <COMMAND> [OPTIONS]
surrealctl 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 and worth keeping: 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:  surrealctl auth login
```

Being signed out is a different answer with a different code: `3`, not `4`. Sign in with [`auth login`](/docs/reference/cli/surrealctl/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/surrealctl/commands/instance.md#instance-token).

## surrealctl token list {#token-list}

List your personal access tokens.

<Synopsis>
surrealctl token list [OPTIONS]
</Synopsis>

This command takes no positional arguments.

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/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
surrealctl 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="Tokens expiring within the week"
surrealctl token list --json | jq -r '.[] | .label + " " + .expires_at'
```

Only the label, the id, the scopes and the timestamps are stored - the secret itself exists once, at creation. A token with no expiry carries the `9999-12-31T23:59:59Z` sentinel under `--json`.

## surrealctl token create {#token-create}

Create a personal access token.

<Synopsis>
surrealctl 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>",
            "default": "30",
            "description": "Days until the token expires, between 1 and 365, or the literal `never`. Defaults to the API's 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=$(surrealctl token create "build runner" --scope read:cloud | tail -1)
```

```bash title="Several scopes, and a shorter life"
surrealctl token create "nightly export" \
    --scope read:cloud \
    --scope write:cloud-instances \
    --expires-in 90 \
    | tail -1 > /run/secrets/surrealctl
```

Output is a detail view with the secret stripped, then a success line, then **the bare secret as the last line on stdout** - so `| tail -1` is the whole capture recipe. The footer 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:
  surrealctl token create <label> | tail -1
```

**Refusals**, all exit `2` and all before the request:

- 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:  surrealctl token create <label> | tail -1
Or pass --reveal if you meant to see it.
```

- A label outside 1 to 120 characters.
- More than 32 scopes: `A token carries at most 32 scopes; 40 were given.`
- An `--expires-in` outside 1 to 365 days, unless it is the literal `never`.

**Warnings** before the request:

- `--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).

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.

## surrealctl token delete {#token-delete}

Revoke a personal access token.

<Synopsis>
surrealctl 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"
surrealctl token delete "build runner"
```

```bash title="By id, from a script"
surrealctl token delete 67upif0m8sh1cn1p2c8t --force --json
```

**Refusals.** The confirmation names the token. Declining exits `0`. 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. Treat revocation as the start of the process when responding to a leak, and rotate whatever the token could reach.

## surrealctl token scopes {#token-scopes}

List the scopes a token can be granted.

<Synopsis>
surrealctl token scopes [OPTIONS]
</Synopsis>

This command takes no positional arguments.

Accepts the [list presentation flags](/docs/reference/cli/surrealctl/output-and-exit-codes.md#list-presentation-flags) and nothing else. Column ids are `id`, `category` and `label`, with `description` under `--wide`.

```bash
surrealctl token scopes
```

```bash title="Just the ids, to paste into a create"
surrealctl token scopes --columns id --no-header
```

This is the menu. [`auth scopes`](/docs/reference/cli/surrealctl/commands/auth.md#auth-scopes) is the receipt: what the credential in hand actually carries.

## Related pages

- [Authentication](/docs/reference/cli/surrealctl/authentication.md#personal-access-tokens) - how a token is supplied, and what it cannot do
- [`auth` commands](/docs/reference/cli/surrealctl/commands/auth.md) - signing in with a login session
- [`instance token`](/docs/reference/cli/surrealctl/commands/instance.md#instance-token) - database tokens, which are a different credential
- [Output and exit codes](/docs/reference/cli/surrealctl/output-and-exit-codes.md#the-secret-guard) - the secret guard
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - for working with the data inside an instance

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/global-flags

# Global flags

The sixteen flags every surrealctl command accepts, the environment variable behind each one, the precedence chain that resolves them, and the duration syntax.

Sixteen flags are accepted by every `surrealctl` command. They may appear anywhere on the command line - before the group, between the group and the verb, or after the verb - so all three of these are the same invocation:

```bash
surrealctl --json instance list
surrealctl instance --json list
surrealctl 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": "SURREALCTL_PROFILE",
            "description": "The configuration profile to use. A profile bundles a credential, an API base URL and a set of configuration values."
        },
        {
            "name": "--config",
            "value": "<PATH>",
            "env": "SURREALCTL_CONFIG",
            "description": "Path to the configuration file. Its directory also becomes the credential directory."
        },
        {
            "name": "--org",
            "value": "<ORG>",
            "env": "SURREALCTL_ORG",
            "description": "The organisation to operate on, by id or name."
        }
    ]}
/>

## Authentication

<OptionsTable
    title="Authentication flags"
    options={[
        {
            "name": "--token",
            "value": "<TOKEN>",
            "env": "SURREALCTL_TOKEN",
            "description": "Personal access token to authenticate with. Conflicts with `--token-file`, and is never persisted."
        },
        {
            "name": "--token-file",
            "value": "<PATH>",
            "env": "SURREALCTL_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": "SURREALCTL_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/surrealctl/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": "SURREALCTL_JSON",
            "description": "Emit machine-readable JSON on stdout. See the output contract for what is guaranteed."
        },
        {
            "name": "--plain",
            "env": "SURREALCTL_PLAIN",
            "description": "Disable tables, spinners and relative times. Output becomes tab-separated and 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": "SURREALCTL_YES",
            "description": "Assume yes for every confirmation in this invocation."
        },
        {
            "name": "--no-input",
            "env": "SURREALCTL_NO_INPUT",
            "description": "Never prompt for input; fail instead. Also spelled `--non-interactive`."
        }
    ]}
/>

`--color` is the one flag with no `SURREALCTL_*` 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": "SURREALCTL_LOG",
            "description": "The logging level, or a full set of filter directives."
        },
        {
            "name": "--debug",
            "env": "SURREALCTL_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": "SURREALCTL_TIMEOUT",
            "description": "Maximum time to wait for a single API request."
        },
        {
            "name": "--retries",
            "value": "<N>",
            "default": "3",
            "env": "SURREALCTL_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/surrealctl/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 SURREALCTL_ORG, or run `surrealctl org use <name>`.

Available:
  acme
  contoso
```

`--debug` prints which layer won each value, and [`context show`](/docs/reference/cli/surrealctl/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`, `--wait-timeout` and `spectron access-token create --ttl` all 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
surrealctl --timeout 90 instance list
surrealctl instance create api --type shared-1 --region aws-euw1 --wait-timeout 5m
surrealctl spectron access-token create --ttl 2h
```

## The `SURREALCTL_` prefix

Every environment variable this CLI reads is prefixed `SURREALCTL_`, and never `SURREAL_`.

Reusing the sibling's prefix would be actively harmful rather than merely untidy. `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 `surrealctl` *sets* for the `surreal` child process during [the handoff](/docs/manage/surrealctl/install.md#the-surreal-handoff): `SURREAL_TOKEN`, `SURREAL_NAMESPACE` and `SURREAL_DATABASE`.

### Variables that mirror a flag

| Variable | Flag |
| --- | --- |
| `SURREALCTL_PROFILE` | `--profile` |
| `SURREALCTL_CONFIG` | `--config` |
| `SURREALCTL_ORG` | `--org` |
| `SURREALCTL_TOKEN` | `--token` |
| `SURREALCTL_TOKEN_FILE` | `--token-file` |
| `SURREALCTL_API` | `--api` |
| `SURREALCTL_JSON` | `--json` |
| `SURREALCTL_PLAIN` | `--plain` |
| `SURREALCTL_YES` | `--yes` |
| `SURREALCTL_NO_INPUT` | `--no-input` |
| `SURREALCTL_LOG` | `--log` |
| `SURREALCTL_DEBUG` | `--debug` |
| `SURREALCTL_TIMEOUT` | `--timeout` |
| `SURREALCTL_RETRIES` | `--retries` |

### Variables with no flag

| Variable | Effect |
| --- | --- |
| `SURREALCTL_CONFIG_DIR` | Overrides the configuration and credential directory |
| `SURREALCTL_SURREAL_BINARY` | Path to the `surreal` binary used by `instance sql`, `import` and `export` |
| `SURREALCTL_AGENT` | `1` or `0`, forcing agent detection on or off |
| `SURREALCTL_CLOUD_TOKEN` | Supplies an already-minted Cloud session token, for support reproduction |
| `SURREALCTL_AUTH_CLIENT_ID` | Overrides the OAuth client id, for a non-production tenant |
| `SURREALCTL_AUTH_ISSUER` | Overrides the OAuth issuer, for a non-production tenant |

### 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 |
| `XDG_CONFIG_HOME`, `HOME` | Locate the configuration directory |
| `HTTPS_PROXY`, `NO_PROXY` | Proxy the API connection |
| `EDITOR` | Used by [`config edit`](/docs/reference/cli/surrealctl/commands/config.md#config-edit) |

## Configuration file equivalents

Five values can be persisted per profile with [`config set`](/docs/reference/cli/surrealctl/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` | `SURREALCTL_ORG` |
| `api` | `SURREALCTL_API` |
| `json` | `SURREALCTL_JSON` |
| `plain` | `SURREALCTL_PLAIN` |
| `surreal_binary` | `SURREALCTL_SURREAL_BINARY` |

## Related pages

- [Output and exit codes](/docs/reference/cli/surrealctl/output-and-exit-codes.md) - what `--json`, `--plain` and `--quiet` actually produce
- [`config` commands](/docs/reference/cli/surrealctl/commands/config.md) - reading and writing the configuration file
- [`context` commands](/docs/reference/cli/surrealctl/commands/context.md) - switching profiles
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - the data plane, and its own [environment variables](/docs/reference/cli/surrealdb-cli/environment-variables.md): the `SURREAL_*` set this CLI never reads

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/long-running-operations

# Long-running operations

How surrealctl 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.

`surrealctl` 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/surrealctl/commands/instance.md#instance-create), [`instance update`](/docs/reference/cli/surrealctl/commands/instance.md#instance-update), [`instance delete`](/docs/reference/cli/surrealctl/commands/instance.md#instance-delete), [`instance pause`](/docs/reference/cli/surrealctl/commands/instance.md#instance-pause) and [`instance resume`](/docs/reference/cli/surrealctl/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/surrealctl/commands/instance.md#instance-backup-create) carries no wait flags, and [`instance watch`](/docs/reference/cli/surrealctl/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"
surrealctl instance create api --type shared-1 --region aws-euw1
```

```bash title="Fire and forget, then pick the wait up later"
surrealctl instance create api --type shared-1 --region aws-euw1 --no-wait
surrealctl instance watch api
```

```bash title="Give up sooner than fifteen minutes"
surrealctl 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. `surrealctl` 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/surrealctl/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'"
surrealctl 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/surrealctl/commands/instance.md#instance-get) for a snapshot, or [`instance watch`](/docs/reference/cli/surrealctl/commands/instance.md#instance-watch) to resume waiting.

```bash
surrealctl 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/surrealctl/commands/instance.md#instance-list) before running it again.

## Related pages

- [`instance` commands](/docs/reference/cli/surrealctl/commands/instance.md) - every command that waits
- [Output and exit codes](/docs/reference/cli/surrealctl/output-and-exit-codes.md) - the NDJSON progress stream and the full exit-code table
- [Global flags](/docs/reference/cli/surrealctl/global-flags.md) - `--timeout` and `--retries`, which govern a single request rather than a wait
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference
- [SurrealDB CLI](/docs/reference/cli/surrealdb-cli/overview.md) - for connecting to the instance once it is ready

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/output-and-exit-codes

# Output and exit codes

The surrealctl --json contract, the stdout and stderr split, NDJSON streams, the shared list presentation flags, the secret and confirmation guards, and every exit code.

`surrealctl` 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 framing rule is short. **stderr text is not a contract. The exit codes are.**

## 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 `surrealctl instance list | head -3` behaves as you expect.

```bash title="Capture the data, keep the diagnostics"
surrealctl 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
surrealctl 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": "surrealctl 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 worth calling out: it is HTTP 501, and it means nothing is broken - the feature is simply not enabled here. Every [SurrealDB Agent Memory](/docs/reference/cli/surrealctl/commands/spectron.md) route answers this way on a deployment without it.

### 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/surrealctl/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/surrealctl/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`, `spectron scopes`/`verbs`/`providers`/`usage`, `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`, `spectron config` | The wire body |
| **Scalar** | `instance endpoint`, `config get`, `config path` | The single value |
| **Secret** | `instance token`, `token create`, `spectron key create`/`rotate`, `spectron scoped-key create`, `spectron access-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=$(surrealctl instance endpoint production)
```

`config get` on an unset key writes zero bytes, so `[ -z "$(surrealctl 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/surrealctl/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/surrealctl/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"
surrealctl instance list --columns name,state,version --sort name
```

```bash title="Every column, for a spreadsheet"
surrealctl 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/surrealctl/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 `surrealctl 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 `SURREALCTL_JSON`
2. `--plain` or `SURREALCTL_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"
surrealctl instance token production | pbcopy
```

```bash title="Or ask for it on screen"
surrealctl 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 |
| `30` | An interactive login flow did not complete |
| `130` | Interrupted |

`0`, `1` and `2` keep their conventional meanings, so `if ! surrealctl …` behaves exactly as it does with the [`surreal` CLI](/docs/reference/cli/surrealdb-cli/overview.md), and usage errors exit `2` as every other command-line tool does. Everything above `2` is additive.

`10` earns its own code because *still running* is a genuinely different answer from *failed*, and a pipeline may reasonably poll again rather than roll back. See [Long-running operations](/docs/reference/cli/surrealctl/long-running-operations.md).

```bash title="Branch on the outcome"
if surrealctl 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/surrealctl/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/surrealctl/global-flags.md) - `--json`, `--plain`, `--quiet`, `--color`, `--yes`
- [Long-running operations](/docs/reference/cli/surrealctl/long-running-operations.md) - waiting, polling and exit code `10`
- [Overview](/docs/reference/cli/surrealctl/overview.md) - the rest of the reference

---

Source: https://surrealdb.com/docs/reference/cli/surrealctl/overview

# surrealctl reference

What surrealctl is, where the control plane ends and the SurrealDB CLI begins, the command grammar, and how this reference is organised.

`surrealctl` manages SurrealDB Cloud from the command line. It creates and scales instances, reads logs and metrics, manages organisation members and invitations, mints credentials, and drives SurrealDB Agent Memory contexts - 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.

## The control plane and the data plane

`surrealctl` owns the **control plane**: the resources around a database - instances, organisations, members, tokens, capabilities, backups. The [`surreal` CLI](/docs/reference/cli/surrealdb-cli/overview.md) owns the **data plane**: queries, imports, exports, and running a server. The two are siblings, and `surrealctl` follows the same conventions so you can move between them without relearning anything.

| Task | Tool |
| --- | --- |
| Create, scale, pause or delete a Cloud instance | `surrealctl instance` |
| Read an instance's logs, metrics or usage | `surrealctl instance` |
| Invite a colleague, change their role | `surrealctl team`, `surrealctl invite` |
| Mint a database token or fetch a key set | `surrealctl instance token`, `surrealctl instance jwks` |
| Run a SurrealQL query | [`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) |
| Run a local server | [`surreal start`](/docs/reference/cli/surrealdb-cli/commands/start.md) |

Three commands cross the boundary. [`instance sql`](/docs/reference/cli/surrealctl/commands/instance.md#instance-sql), [`instance import`](/docs/reference/cli/surrealctl/commands/instance.md#instance-import) and [`instance export`](/docs/reference/cli/surrealctl/commands/instance.md#instance-export) resolve the instance, mint a database token, and hand off to the `surreal` binary rather than reimplementing SurrealQL. Everything after a `--` separator is forwarded to `surreal` verbatim. See [the `surreal` handoff](/docs/manage/surrealctl/install.md#the-surreal-handoff) for how the binary is located.

## Before you start

This reference documents commands and flags. For installing `surrealctl`, signing in, and a first walkthrough, see the [surrealctl guide](/docs/manage/surrealctl.md).

| Task | Page |
| --- | --- |
| Install the binary and sign in | [Install](/docs/manage/surrealctl/install.md) |
| Choose between a login session and a token | [Authentication](/docs/manage/surrealctl/authentication.md) |
| Work through a first instance | [Instances](/docs/manage/surrealctl/instances.md) |

## Command grammar

The grammar is regular, and worth learning 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
surrealctl instance
surrealctl 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/surrealctl/authentication.md) | Login sessions, personal access tokens, and where credentials are stored |
| [Global flags](/docs/reference/cli/surrealctl/global-flags.md) | The 16 flags every command accepts, their environment variables, and the precedence chain |
| [Output and exit codes](/docs/reference/cli/surrealctl/output-and-exit-codes.md) | The `--json` contract, the stream split, list presentation flags, and every exit code |
| [Long-running operations](/docs/reference/cli/surrealctl/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:

| Group | Purpose |
| --- | --- |
| [`auth`](/docs/reference/cli/surrealctl/commands/auth.md) | Sign in, sign out, and inspect credentials |
| [`org`](/docs/reference/cli/surrealctl/commands/org.md) | Manage organisations, roles, usage and spend |
| [`instance`](/docs/reference/cli/surrealctl/commands/instance.md) | Manage instances, capabilities and backups |
| [`team`](/docs/reference/cli/surrealctl/commands/team.md) | Manage organisation members |
| [`invite`](/docs/reference/cli/surrealctl/commands/invite.md) | Manage organisation invitations |
| [`token`](/docs/reference/cli/surrealctl/commands/token.md) | Manage personal access tokens |
| [`catalog`](/docs/reference/cli/surrealctl/commands/catalog.md) | Browse the platform catalogues |
| [`spectron`](/docs/reference/cli/surrealctl/commands/spectron.md) | Manage SurrealDB Agent Memory contexts, keys and principals |
| [`config`](/docs/reference/cli/surrealctl/commands/config.md) | Read and write configuration |
| [`context`](/docs/reference/cli/surrealctl/commands/context.md) | Inspect and switch profiles |
| [`whoami`, `api`, `open`, `status`, `version`, `completion`](/docs/reference/cli/surrealctl/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
surrealctl instance get production
surrealctl instance get acme/production
surrealctl instance get 67upif0m8sh1cn1p2c8t
```

---

Source: https://surrealdb.com/docs/reference/cli/surrealdb-cli/commands

# SurrealDB CLI commands

How the SurrealDB CLI is organised into subcommands, with links to each command’s flags and examples.

The `surreal` executable exposes a single entry point with **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 [OPTIONS] <COMMAND>
</Synopsis>

| Subcommand | Purpose |
| --- | --- |
| [`start`](/docs/reference/cli/surrealdb-cli/commands/start.md) | Run a SurrealDB server (in memory, on disk, or clustered). |
| [`sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) | Open an interactive SurrealQL shell or run queries from scripts. |
| [`export`](/docs/reference/cli/surrealdb-cli/commands/export.md) | Dump a database to SurrealQL. |
| [`fix`](/docs/reference/cli/surrealdb-cli/commands/fix.md) | Apply data or schema fixes offline. |
| [`format`](/docs/reference/cli/surrealdb-cli/commands/format.md) _(since v3.3.0)_ | Reformat a SurrealQL file to a consistent style. |
| [`help`](/docs/reference/cli/surrealdb-cli/commands/help.md) | Show help for the CLI or a subcommand. |
| [`import`](/docs/reference/cli/surrealdb-cli/commands/import.md) | Load SurrealQL from a file into a database. |
| [`isready`](/docs/reference/cli/surrealdb-cli/commands/isready.md) | Health check for readiness probes. |
| [`mcp`](/docs/reference/cli/surrealdb-cli/commands/mcp.md) _(since v3.1.0)_ | Start the Model Context Protocol server on stdio for IDE integrations. |
| [`ml`](/docs/reference/cli/surrealdb-cli/commands/ml.md) | Work with machine-learning features from the CLI. |
| [`module`](/docs/reference/cli/surrealdb-cli/commands/module.md) | Build and manage WASM modules (including Surrealism). |
| [`upgrade`](/docs/reference/cli/surrealdb-cli/commands/upgrade.md) | Upgrade between SurrealDB versions. |
| [`validate`](/docs/reference/cli/surrealdb-cli/commands/validate.md) | Validate SurrealQL or configuration. |
| [`version`](/docs/reference/cli/surrealdb-cli/commands/version.md) | Print CLI and server version information. |

**Sidebar order:** [`start`](/docs/reference/cli/surrealdb-cli/commands/start.md) and [`sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) first, then remaining subcommands alphabetically.

## Shared options

Every 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/surrealdb-cli/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 the control plane, handled by [`surrealctl instance`](/docs/reference/cli/surrealctl/overview.md) instead. `surrealctl` calls out to `surreal` for SQL, imports, and exports, so the pages here still describe what happens once a Cloud instance has been resolved.

For installation and a minimal end-to-end example, see the [SurrealDB CLI overview](/docs/reference/cli/surrealdb-cli/overview.md).

---

Source: https://surrealdb.com/docs/reference/cli/surrealdb-cli/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.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal 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. Alias: `--conn`."
        },
        {
            "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`."
        },
        {
            "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": "Whether SurrealKV versioned records should be exported. Possible values: `true`, `false`."
        },
        {
            "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 export` command with the required arguments.

```bash
surreal export --conn http://localhost:8000 --user root --pass secret --ns main --db main export.surql
```

Using token-based authentication:

```bash
surreal export --conn http://localhost:8000 --token <token> --ns main --db main export.surql
```

## Using environment variables

> [!IMPORTANT]
> Most of the flags above have a corresponding [environment variable](/docs/reference/cli/surrealdb-cli/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 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/surrealdb-cli/environment-variables.md#command-environment-variables) page.

## Managing a Cloud instance

To export from a SurrealDB Cloud instance by name, [`surrealctl`](/docs/reference/cli/surrealctl/overview.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 import`](/docs/reference/cli/surrealdb-cli/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 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 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/surrealdb-cli/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.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal 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 fix` command in a terminal with the path to the stored data.

```bash
surreal fix surrealkv://mydatabase.db

surreal fix rocksdb:somedatabase
```

## Using environment variables

> [!IMPORTANT]
> Most of the flags above have a corresponding [environment variable](/docs/reference/cli/surrealdb-cli/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 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/surrealdb-cli/environment-variables.md#command-environment-variables) page.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal 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 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/surrealdb-cli/commands/format

# format

A command to reformat a SurrealQL file to a consistent style, printing the result or writing it back in place.

_(since v3.3.0)_

Reformat a SurrealQL file, or a query read from standard input, without connecting to a server. The command parses each statement and prints it back out from the parsed syntax tree, so the result reflects what the statements are rather than how they were typed.

> [!NOTE]
> **Before you start** - make sure you've [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal format [OPTIONS] <FILE>
</Synopsis>

`fmt` is an alias for `format`, so `surreal fmt query.surql` runs the same command.

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<FILE>",
            "required": true,
            "description": "Path to the SurrealQL file to format. Use `-` to read from standard input."
        }
    ]}
/>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--write",
            "short": "-w",
            "description": "Overwrite the file with the formatted output instead of printing it to standard output."
        },
        {
            "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

Take a file written in lowercase, with double-quoted strings and a run of blank lines in the middle:

```surql title="person.surql"
-- Set up the person table
define table person schemafull;
define field name on person type string;
define field age on person type int;


create person:tobie set name="Tobie", age=35;
select name, age from person where age>18 order by name;
```

Passing the path to `surreal format` prints the formatted file to standard output and leaves the file on disk untouched.

```bash
surreal format person.surql
```

```surql title="Output"
-- Set up the person table
DEFINE TABLE person TYPE NORMAL SCHEMAFULL
	PERMISSIONS NONE;
DEFINE FIELD name ON person TYPE string
PERMISSIONS FULL;
DEFINE FIELD age ON person TYPE int
PERMISSIONS FULL;

CREATE person:tobie SET name = 'Tobie', age = 35;
SELECT name, age FROM person WHERE age > 18 ORDER BY name;
```

Each statement comes back with its keywords in uppercase, single-quoted strings, spaces around operators, and the clauses that were left implicit written out in full. `DEFINE TABLE person SCHEMAFULL` becomes `DEFINE TABLE person TYPE NORMAL SCHEMAFULL PERMISSIONS NONE`, which is the same definition the server stores and the same one [`INFO FOR DB`](/docs/reference/query-language/statements/info.md) reports.

## Writing the result back to the file

The `--write` flag replaces the contents of the file with the formatted output and prints nothing.

```bash
surreal format --write person.surql
```

Running the command a second time on an already formatted file leaves it unchanged, so `--write` is safe to run repeatedly.

## Formatting input from stdin

Pass `-` as the file to read the query from standard input and write the result to standard output.

**Bash**

```bash
echo 'select * from person where age>18 order by name desc limit 5;' | surreal format -
```

```surql title="Output"
SELECT * FROM person WHERE age > 18 ORDER BY name DESC LIMIT 5;
```

**PowerShell**

```powershell
'select * from person where age>18 order by name desc limit 5;' | surreal format -
```

```surql title="Output"
SELECT * FROM person WHERE age > 18 ORDER BY name DESC LIMIT 5;
```

`--write` has no file to write back to in this mode, so combining it with `-` reports `The --write flag cannot be used when reading from stdin.` and formats nothing.

## What the formatter changes

- **Keywords** are uppercased, and clauses left implicit in the source are written out.
- **Strings** are rewritten with single quotes, and identifiers that collide with a keyword are escaped in backticks.
- **Operators** get a space on each side, so `age>18` becomes `age > 18`.
- **Nested values** such as a multi-element array are broken across lines and indented with tabs.
- **Single-line comments** keep their text and are rewritten with a leading `#`, whichever of `#`, `--` or `//` the source used. Block comments written as `/* ... */` pass through unchanged.
- **Blank lines** between statements collapse to a single blank line.

Line structure between statements comes from the source. A statement spread over several lines is joined onto one, and two statements written on one line stay on that line, so the formatter does not impose one statement per line.

> [!WARNING]
> The formatter parses one statement at a time and treats the semicolon between statements as optional, so it accepts input that is not a valid query and rewrites it instead of reporting an error. `CREATE person WHERE;` is rejected by [`surreal validate`](/docs/reference/cli/surrealdb-cli/commands/validate.md), but `surreal format` turns it into ``CREATE person;`WHERE`;`` - two statements, with `WHERE` escaped as an identifier. Run `surreal validate` on a file before formatting it, and read the diff before committing a `--write`.

## Comparison with surqlfmt

[`surqlfmt`](/docs/reference/cli/formatter/overview.md) also formats SurrealQL, and the two tools are shipped and driven differently.

| | `surreal format` | `surqlfmt` |
| --- | --- | --- |
| Distribution | Part of the `surreal` binary | npm package `@surrealdb/surql-fmt` |
| Input | One file, or `-` for standard input | Paths and glob patterns, or `--stdin` |
| Format in place | `--write` | `--write` |
| Check without changing | | `--check` |
| Style options | | `--indent`, `--indent-char`, `--max-line-length` |

Reach for `surreal format` for a single file on a machine that already has the `surreal` binary, and for `surqlfmt` to sweep a tree of `.surql` files or to gate a CI job on formatting.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal format --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `format` command.

```bash
surreal format --help
```

The output of the above command:

```text
Format SurrealQL query

Usage: surreal format [OPTIONS] <FILE>

Arguments:
  <FILE>  Path to the SurrealQL file to format. Use dash - to read from stdin.

Options:
  -w, --write  Overwrite the file with the formatted output instead of printing to stdout.
  -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/surrealdb-cli/commands/help

# help

A command to display all possible top-level commands and arguments used in the SurrealDB binary.

Print the help information for the `surreal` binary, or for one of its subcommands.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal help
surreal <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 command-line tool."
        },
        {
            "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 command-line tool, in a terminal run the `surreal help` command without any further arguments. This command gives general information on the other functionality which can be run with the command-line tool.

```bash
surreal 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
  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
  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]
```

> [!NOTE]
> The pasted output above predates SurrealDB 3.1.0 and so does not list [`surreal mcp`](/docs/reference/cli/surrealdb-cli/commands/mcp.md). For the current list of subcommands, see [CLI commands](/docs/reference/cli/surrealdb-cli/commands.md).

## Getting help on individual commands

For individual commands, such as `surreal start` and `surreal 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 start --help
surreal start --user root --pass secret --log debug --help
```

---

Source: https://surrealdb.com/docs/reference/cli/surrealdb-cli/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.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal 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. Alias: `--conn`."
        },
        {
            "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 import` command with the required arguments.

```bash
surreal import --endpoint http://localhost:8000 --user root --pass secret \
  --ns main --db main downloads/surreal_deal_v1.surql
```

Using token-based authentication:

```bash
surreal 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.

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/surrealdb-cli/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 validate`](/docs/reference/cli/surrealdb-cli/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.

## Using environment variables

When using the `surreal 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/surrealdb-cli/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/surrealdb-cli/environment-variables.md#command-environment-variables) page.

## Managing a Cloud instance

To import into a SurrealDB Cloud instance by name, [`surrealctl`](/docs/reference/cli/surrealctl/overview.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.

## 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.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal 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 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/surrealdb-cli/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. The command is named `is-ready` and also accepts the alias `isready`.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal 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. Alias: `--conn`."
        },
        {
            "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 isready` command against its endpoint.

```bash
surreal isready --conn 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 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 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/surrealdb-cli/commands/mcp

# mcp

Start SurrealDB's Model Context Protocol server over stdio for local IDE and agent integrations.

_(since v3.1.0)_

The `surreal 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 start`](/docs/reference/cli/surrealdb-cli/commands/start.md) and connect to the **`/mcp`** endpoint instead. See [When to use `surreal mcp` vs `surreal start`](/docs/build/ai-agents/mcp/embedded.md#when-to-use-surreal-mcp-vs-surreal-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 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 start` are also available via the shared `dbs` option group (see `surreal mcp --help`).

## Usage

```bash
surreal mcp --ns main --db main memory
```

Optional root credentials apply only when no root user exists yet (same semantics as `surreal start`):

```bash
surreal 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/surrealdb-cli/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/surrealdb-cli/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 ml [OPTIONS] <COMMAND>
</Synopsis>

| Subcommand | Purpose |
| --- | --- |
| [`surreal ml import`](#ml-import) | Import a SurrealML model into an existing database. |
| [`surreal ml export`](#ml-export) | Export a SurrealML model from an existing database. |
| `surreal 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 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 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 ml export {#ml-export}

The ML export command is used to export an existing machine learning model from SurrealDB.

<Synopsis>
surreal 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. Alias: `--conn`."
        },
        {
            "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 ml export` command with the required arguments.

```bash
surreal ml export --conn 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 ml export --conn 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 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 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 ml import {#ml-import}

The ML import command is used to import a new machine learning model into SurrealDB.

<Synopsis>
surreal 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. Alias: `--conn`."
        },
        {
            "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 ml import` command with the required arguments.

```bash
surreal ml import --conn http://localhost:8000 --user root --pass secret \
  --ns main --db main my-surrealml-model.surml
```

Using token-based authentication:

```bash
surreal ml import --conn 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 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 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/surrealdb-cli/commands/module

# module

A command used to compile, manage and execute Surrealism plugin modules.

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.

_(since v3.0.0)_

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal module [OPTIONS] <COMMAND>
</Synopsis>

| Subcommand | Purpose |
| --- | --- |
| [`surreal module init`](#module-init) | Initialise a new Surrealism module project. |
| [`surreal module build`](#module-build) | Build a WASM module. |
| [`surreal module run`](#module-run) | Run a function with arguments. |
| [`surreal module sig`](#module-sig) | Show the function signature. |
| [`surreal module info`](#module-info) | Show the module information. |
| `surreal module help` | Print this message or the help of the given subcommand. |

## surreal module init {#module-init}

Scaffold a new Surrealism module project (Rust crate, `surrealism.toml`, `.cargo/Config.toml`, and starter `src/lib.rs`).

<Synopsis>
surreal 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 module init
```

`surreal module init` writes the WASI build flags Surrealism needs into `.cargo/config.toml`. `surreal 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 module init --headless --org surrealdb --name my_module ./path/to/crate
```

## surreal module build {#module-build}

Build a WASM binary from the Rust source code. A binary must have the `.surli` file extension.

<Synopsis>
surreal 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 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 module build --debug -o demo-debug.surli .
```

## surreal module run {#module-run}

Run a single function from a compiled module, including functions that require arguments to be passed in.

<Synopsis>
surreal 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 module run --fnc can_drive --arg 17    demo.surli
surreal module run --fnc can_drive --arg 18    demo.surli
surreal module run --fnc result    --arg false demo.surli
surreal module run --fnc result    --arg true  demo.surli
surreal module run --fnc test_kv               demo.surli
surreal module run --fnc test_io               demo.surli
surreal module run --fnc test_none_value       demo.surli
```

## surreal module sig {#module-sig}

Show the signature for a single function in a compiled module.

<Synopsis>
surreal 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 module sig --fnc can_drive demo.surli
```

## surreal module info {#module-info}

Show the information held by a compiled WASM binary.

<Synopsis>
surreal module info [OPTIONS] <FILE>
</Synopsis>

<OptionsTable
    title="Arguments"
    options={[
        {
            "name": "<FILE>",
            "required": true,
            "description": "Path to the compiled `.surli` module."
        }
    ]}
/>

```bash
surreal module info demo.surli
```

## Command help

To see the help information and usage instructions, in a terminal run the `surreal module --help` command without any further arguments. This output lists subcommands, global options, and shared logging flags for the `module` command.

```bash
surreal 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/surrealdb-cli/commands/sql

# sql

A command that starts a command-line REPL to make SurrealQL to a local or remote SurrealDB database server.

Start a SurrealQL REPL in your terminal, with pipe support for one-shot queries.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal 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`. Alias: `--conn`."
        },
        {
            "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 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).

## Using environment variables

> [!IMPORTANT]
> Most of the flags above have a corresponding [environment variable](/docs/reference/cli/surrealdb-cli/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 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/surrealdb-cli/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 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 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 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 sql --endpoint http://localhost:8000 --namespace main --database main --token <token>
```

## Capabilities and remote connections

Both `surreal sql` and [`surreal start`](/docs/reference/cli/surrealdb-cli/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 start` |
| **Embedded engine** | `memory`, `rocksdb://…`, `surrealkv://…` (no separate server) | On the **`surreal 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 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 sql` against a **running instance**, flags on the REPL command do **not** change what the server allows at execution time. Configure [`surreal start`](/docs/reference/cli/surrealdb-cli/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 sql` still matter in two cases:

- **Embedded storage** - there is no separate server; pass the flags on `surreal 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.

## Experimental capabilities

_(since v2.2.1)_

> [!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/surrealdb-cli/environment-variables.md) to the experimental capability you want to allow.

For **embedded** `surreal sql` (for example `surreal sql memory`), set the variable or flag on the REPL command. For a **remote** server, set it on [`surreal start`](/docs/reference/cli/surrealdb-cli/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 sql ...
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "surrealism"
surreal sql ...
```

or, using the `--allow-experimental` flag:

```bash
surreal 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 sql ...

-- OR

surreal sql -e [CONNECTION_STRING] --allow-experimental surrealism,files
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "surrealism,files"
surreal sql ...

-- OR

surreal 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](/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 start`](/docs/reference/cli/surrealdb-cli/commands/start.md#experimental-capabilities) (or the server's `SURREAL_CAPS_ALLOW_EXPERIMENTAL`). Flags on `surreal sql` only affect [embedded connections](#capabilities-and-remote-connections) or REPL parse validation. From 3.3.0, [ISO GQL](/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.

### Default namespace and database

_(since v3.0.0)_

As the `surreal start` command defaults to defining a namespace and database by the name `main` upon starting up, the `surreal sql` command also connects to these two by default. As such, a command like `surreal sql --user root --pass secret` or even just `surreal sql` (for an instance with authentication disabled) is all that is needed to connect and begin using an instance via the CLI.

## Example usage

To start an interactive REPL, run the `surreal 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 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 sql`. This is only for a small number of statements; for larger scripts use the [import command](/docs/reference/cli/surrealdb-cli/commands/import.md).

**Bash**

```bash
echo 'INFO FOR DB;' | surreal sql --endpoint http://localhost:8000 --username root --password secret --namespace main --database main --pretty --hide-welcome
```

**PowerShell**

```powershell
'INFO FOR DB;' | surreal 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 sql --endpoint http://localhost:8000 --username root --password secret --namespace main --database main
```

## Connecting to a Cloud instance

`surrealctl instance sql <name>` wraps this command: it resolves the endpoint and credentials of a SurrealDB Cloud instance and then hands off to `surreal sql`, passing through any flags given after `--`. Everything on this page applies to that session. See the [surrealctl reference](/docs/reference/cli/surrealctl/overview.md) for the control-plane side.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal 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 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/surrealdb-cli/commands/start

# start

A command that begins a running instance of a SurrealDB server with arguments to set the storage backend, authentication and more.

The start command starts a SurrealDB server in memory, on disk, or in a distributed setup.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal 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": "--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`."
        },
        {
            "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`."
        }
    ]}
/>

`surreal 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/surrealdb-cli/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). The `Forwarded` value for `--client-ip` reads the RFC 7239 `Forwarded` header. _(since v3.1.0)_

## Positional argument

> [!WARNING]
> FoundationDB support is deprecated in SurrealDB `3.0`. Please plan to migrate to a supported storage backend.

In the `surreal 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. 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 [`surrealctl`](/docs/reference/cli/surrealctl/overview.md).

## Getting started

This example will show how to host a SurrealDB server with the `surreal start` command, and then access the Surreal DB server using the [`surreal sql` command](/docs/reference/cli/surrealdb-cli/commands/sql.md).

To start a SurrealDB server, run the `surreal 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 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 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 start` command described above. This is done using a separate [`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) command. Ensure that the hosting location indicated by the output of the `surreal start` command is passed to the `--endpoint` argument, and that you specify the same `--username` and `--password` as in the `surreal start` command.

A particular namespace and database can be specified at this point, as seen below.

```bash
surreal sql --endpoint http://127.0.0.1:8000 --namespace my_namespace \
  --database my_database --username my_username --password my_password
```

## Using environment variables

> [!IMPORTANT]
> Most of the flags above have a corresponding [environment variable](/docs/reference/cli/surrealdb-cli/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 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/surrealdb-cli/environment-variables.md#command-environment-variables) page.

## 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 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;
```

## 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 start --user root --pass secret "surrealkv://path/to/db?versioned=true&sync=every&retention=30d"
surreal start --user root --pass secret "mem://tmp/data?versioned=true&aol=sync&snapshot=60s&sync=5s"
surreal start --user root --pass secret "mem://?versioned=true"
```

Each database store supports different parameters.

### Supported parameters for Memory (SurrealMX)

- `versioned` (`true` or `false`)
- `retention` (a duration string, e.g. `30d`, `24h`, `30m`)
- `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
- `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 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 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 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 start --user root --password secret
```

## Enabling capabilities

> [!NOTE]
> If using SurrealDB Cloud, capabilities can be set from [SurrealDB Studio](/docs/manage/instances/configure.md#capabilities) or with [`surrealctl`](/docs/reference/cli/surrealctl/overview.md).

Capabilities arguments such as `allow-scripting` or `deny-net` can also be passed into the `surreal 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 start` command that begins with the `--deny-all` flag and only thereafter sets which capabilities will be allowed:

```bash
surreal start --deny-all --allow-funcs "array, string, crypto::argon2, http::get" --allow-net api.example.com:443
```

## 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 start --unauthenticated
```

## 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 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 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.

## Experimental capabilities

_(since v2.2.0)_

> [!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/surrealdb-cli/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 start

# Allow experimental via a flag
surreal start --allow-experimental surrealism
```

**PowerShell**

```powershell
# Allow experimental via an env var
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "surrealism"
surreal start

# Allow experimental via a flag
surreal 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 start
surreal start --allow-experimental surrealism,files
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_EXPERIMENTAL = "surrealism,files"
surreal start
surreal start --allow-experimental surrealism,files
```

> [!NOTE]
> Experimental capabilities are enforced on the **server** for remote clients. If you use [`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) against `ws://` or `http://`, configure flags here - not only on the REPL. See [Capabilities and remote connections](/docs/reference/cli/surrealdb-cli/commands/sql.md#capabilities-and-remote-connections).

> [!NOTE]
> From **3.3.0**, [ISO GQL](/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` |

## Further examples

As `surreal 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 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 start --log debug rocksdb:mydatabase.db
```

An instance with all capabilities denied except a few functions and a single endpoint:

```bash
surreal 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 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 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 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]

      --readiness-heartbeat-max-age <READINESS_HEARTBEAT_MAX_AGE>
          How stale this node's cluster heartbeat may get before /ready reports it unhealthy (defaults to three refresh intervals)

          [env: SURREAL_READINESS_HEARTBEAT_MAX_AGE=]

      --changefeed-gc-interval <CHANGEFEED_GC_INTERVAL>
          The interval at which to perform changefeed garbage collection

          [env: SURREAL_CHANGEFEED_GC_INTERVAL=]
          [default: 30s]

      --query-timeout <QUERY_TIMEOUT>
          The maximum duration that a set 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]
```

---

Source: https://surrealdb.com/docs/reference/cli/surrealdb-cli/commands/upgrade

# upgrade

A command to change the current version of SurrealDB to another one, including the latest version, specified version, or nightly.

Replace the installed `surreal` executable with another version: the latest stable release, a specific version, or a pre-release build.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal upgrade [OPTIONS]
</Synopsis>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--nightly",
            "description": "Install the latest nightly version of SurrealDB."
        },
        {
            "name": "--alpha",
            "description": "Install the latest alpha version of SurrealDB."
        },
        {
            "name": "--beta",
            "description": "Install the latest beta version of SurrealDB."
        },
        {
            "name": "--version",
            "value": "<VERSION>",
            "description": "Install a specific version of SurrealDB."
        },
        {
            "name": "--dry-run",
            "description": "Report what would happen without replacing the currently installed executable."
        },
        {
            "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

This example shows how you can use the upgrade command to upgrade to the latest version of SurrealDB.

```bash
surreal upgrade
```

## Global install

If SurrealDB is installed globally on your system, you might need to run the upgrade command with elevated permissions, such as `sudo` on Unix-based systems or with administrator privileges in Windows.

```bash
sudo surreal upgrade
```

## Install a specific version

Use the `--version` flag to upgrade to a specific version of SurrealDB.

```bash
surreal upgrade --version [VERSION]

# For example
surreal upgrade --version 2.6.0
```

For a list of available versions and their release notes, see the [releases](/releases) page.

## Install the alpha release

Use the `--alpha` flag to upgrade to the latest alpha version of SurrealDB.

```bash
surreal upgrade --alpha
```

## Install the beta release

Use the `--beta` flag to upgrade to the latest beta version of SurrealDB.

```bash
surreal upgrade --beta
```

## Install the nightly release

Use the `--nightly` flag to upgrade to the latest nightly version of SurrealDB.

```bash
surreal upgrade --nightly
```

## Copies installed by surrealctl

[`surrealctl`](/docs/reference/cli/surrealctl/overview.md) can download a copy of the `surreal` binary for you when a command such as `surrealctl instance sql` needs it. That copy lives in `~/.config/surrealctl/bin/` and is only used when no `surreal` binary is found on `PATH`, so a version installed normally always takes precedence.

## Command help

To see the help information and usage instructions, in a terminal run the `surreal upgrade --help` command without any further arguments. This command gives general information on the arguments, inputs, and additional options for the `upgrade` command.

```bash
surreal upgrade --help
```

The output of the above command:

```text
Upgrade to the latest stable version

Usage: surreal upgrade [OPTIONS]

Options:
      --nightly            Install the latest nightly version
      --alpha              Install the latest alpha version
      --beta               Install the latest beta version
      --version <VERSION>  Install a specific version
      --dry-run            Don't actually replace the executable
  -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/surrealdb-cli/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, without connecting to a server.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal 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 validate` command with the required argument.

Using the command on its own will validate all the `.surql` files in the current directory.

```bash
surreal validate
```

```text
user1.surql: OK
user2.surql: OK
user3.surql: OK
```

You can perform validation on a single file, regardless of extension.

```bash
surreal validate user.surql
surreal validate user.txt
```

You can also perform validation on multiple files using a single glob pattern:

```bash
# equivalent to "surreal validate"
surreal validate **/*.surql
```

Finally, you can also perform validation on multiple files using multiple paths/patterns:

```bash
surreal validate index.surql schemas/*.surql queries/*.surql
surreal validate *.(txt|surql)
```

If any files are invalid, the command will abort at this point and return an error.

```bash
surreal 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 validate --stdin
```

```text
<stdin>: OK
```

An example of an invalid query:

```bash
echo "CREATE WHERE value = ''" | surreal 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 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 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/surrealdb-cli/commands/version

# version

A command to output the current version of the SurrealDB binary along with the machine architecture.

Output the command-line tool version, or the version of a remote server.

> [!NOTE]
> **Before you start** - make sure you’ve [installed SurrealDB](/docs/running/installation.md).

<Synopsis>
surreal version [OPTIONS]
</Synopsis>

<OptionsTable
    title="Options"
    options={[
        {
            "name": "--endpoint",
            "short": "-e",
            "value": "<ENDPOINT>",
            "description": "Remote database server URL to connect to. Alias: `--conn`. 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 current command-line tool version, along with the platform and architecture, in a terminal run the `surreal version` command without any further arguments.

```bash
surreal version
```

Sample output:

```text
2.2.1 for macos on aarch64
```

If an endpoint is specified, only the version number will be displayed.

```bash
surreal version --endpoint http://localhost:8000
```

Output:

```text
2.2.1
```

### Check version with CLI flags

```bash
surreal -V
```

```bash
surreal --version
```

Sample output:

```text
SurrealDB command-line interface and server 2.2.1 for macos on aarch64
```

## Command help

To see the help information and usage instructions, in a terminal run the `surreal 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 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/surrealdb-cli/environment-variables

# Environment variables

A list of the available environment variables used when running SurrealDB.

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 start`, equivalent to `surreal start --allow-all`.
* **Storage backend environment variables**: environment variables that pertain to a certain storage backend. Example: `SURREAL_SURREALKV_MAX_SEGMENT_SIZE`.
* **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 `surreal` binary and starts with `SURREAL_`. The [`surrealctl`](/docs/reference/cli/surrealctl/overview.md) control-plane tool reads `SURREALCTL_*` variables instead, and the two prefixes are kept apart deliberately: a database credential such as `SURREAL_TOKEN` is not a control-plane 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.</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">10,000</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.</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.</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">10,000</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>
  </tbody>
</table>

### File config

Server-side filesystem access for features that read paths from disk (notably the `mapper()` filter 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 dictionary 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_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.</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_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.</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_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 start`, `surreal mcp` on stdio). Stdio namespace/database selection uses `SURREAL_MCP_NS` and `SURREAL_MCP_DB` on the [`mcp`](/docs/reference/cli/surrealdb-cli/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](/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/surrealdb-cli/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_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_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.</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_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 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 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_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.</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.</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>
  </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>

### 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.</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.</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_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>
  </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 memory and surrealkv engines.</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. Used by memory and surrealkv engines.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_MEMORY_THRESHOLD</code></td>
      <td scope="row" data-label="Default">0</td>
      <td scope="row" data-label="Allowed values">A usize or suffixed integer</td>
      <td scope="row" data-label="Notes">Configuring the memory threshold which can be used across the programme to check if the amount of memory available to the programme is lower than required. 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). If the environment variable is not specified, then the threshold is not used, and no memory limit is enabled.</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/surrealdb-cli/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 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 start --user root --pass secret
```

**PowerShell**

```powershell
$env:SURREAL_CAPS_ALLOW_NET
$env:SURREAL_CAPS_DENY_FUNC="crypto::md5, http::post, http::delete"
surreal 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 start

# Same, but implicitly shown
SURREAL_CAPS_ALLOW_ALL=false surreal start

# Set to true
SURREAL_CAPS_ALLOW_ALL=true surreal start

# Set to true
surreal start --allow-all

# Error: only --allow-all needed to set to true
surreal start --allow-all true
```

**PowerShell**

```powershell title="SURREAL_CAPS_ALLOW_ALL example"
# set to default false
surreal start

# Same, but implicitly shown
$env:SURREAL_CAPS_ALLOW_ALL = "false"
surreal start

# Set to true
$env:SURREAL_CAPS_ALLOW_ALL = "true"
surreal start

# Set to true
surreal start --allow-all

# Error: only --allow-all needed to set to true
surreal 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-processing-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/surrealdb-cli/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 sql</code>, affects embedded engines and REPL parse validation only. See <a href="/docs/reference/cli/surrealdb-cli/commands/start.md#experimental-capabilities">experimental capabilities</a>. From 3.3.0, ISO GQL 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">true</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">true</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. Prevents closures from being stored, which eliminates a potential attack surface. For version 2.5.0, this can still be allowed by using this capability.</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/surrealdb-cli/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_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.</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 set of statements can run for.</td>
    </tr>
    <tr>
      <td scope="row" data-label="Env var"><code>SURREAL_READINESS_HEARTBEAT_MAX_AGE</code>_(since v3.3.0)_</td>
      <td scope="row" data-label="Command arg"><code>readiness-heartbeat-max-age</code></td>
      <td scope="row" data-label="Command">`start`</td>
      <td scope="row" data-label="Default">Three times <code>node-membership-refresh-interval</code> (9s)</td>
      <td scope="row" data-label="Allowed values">A duration</td>
      <td scope="row" data-label="Notes">How stale this node's cluster heartbeat may get before <code>/ready</code> reports the node unhealthy. When unset, it is derived as three times <code>node-membership-refresh-interval</code>. Startup warns if the configured value reaches 30s - the interval after which peers archive an unresponsive node and collect its live queries - because a node reported ready after the cluster has written it off keeps taking traffic. The value is not clamped.</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.</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.</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>_(since v3.3.0)_</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/surrealdb-cli/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.</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.</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_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_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_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_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_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_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_GROUPED_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 enable grouped commit when sync is enabled. When enabled, multiple transaction commits are batched together and flushed to disk with a single fsync operation, improving throughput. When disabled, each transaction is committed and synced individually, which may provide lower latency for single transactions at the cost of reduced throughput under high load. Only used when SURREAL_SYNC_DATA is enabled and SURREAL_ROCKSDB_BACKGROUND_FLUSH is disabled.</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_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_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_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_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_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_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_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.</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_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_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_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_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>
    <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_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_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>
  </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>
  </tbody>
</table>

### FoundationDB environment variables

> [!WARNING]
> FoundationDB support is deprecated in SurrealDB `3.0`. Please plan to migrate to a supported storage backend.

<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 [`surrealctl`](/docs/reference/cli/surrealctl/overview.md).

---

Source: https://surrealdb.com/docs/reference/cli/surrealdb-cli/overview

# SurrealDB CLI

The SurrealDB command-line tool 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.

The `surreal` binary is the data-plane command-line tool for SurrealDB. 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 [OPTIONS] <COMMAND>
</Synopsis>

> [!IMPORTANT]
> Before using the CLI, you will need to [install SurrealDB](/docs/running/installation.md). 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 and surrealctl

`surreal` and [`surrealctl`](/docs/reference/cli/surrealctl/overview.md) are separate binaries that co-exist, and they serve different purposes.

- `surreal` owns the **data plane**: running a server, querying it, and importing or exporting data.
- `surrealctl` owns the **control plane**: organisations, instances, members, tokens, and billing on SurrealDB Cloud.

Neither replaces the other. `surrealctl instance sql`, `surrealctl instance import`, and `surrealctl instance export` resolve the endpoint and credentials of a Cloud instance and then hand off to the `surreal` binary, so the flags documented here still apply once the handoff happens. Environment variables are kept apart as well: `surreal` reads `SURREAL_*`, and `surrealctl` reads `SURREALCTL_*` only.

## Getting started

The CLI allows you to use the `surreal` command from your terminal or command prompt. 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/surrealdb-cli/commands.md).

For a quickstart, [`surreal start`](/docs/reference/cli/surrealdb-cli/commands/start.md) and [`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) will be enough to get you started.

```bash
surreal 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 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 sql --username root --password secret --pretty

# Connect to a different namespace and database
surreal 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/surrealdb-cli/commands.md) - every subcommand, with its arguments and options.
- [Environment variables](/docs/reference/cli/surrealdb-cli/environment-variables.md) - the `SURREAL_*` variables that mirror the flags on this page.
- [surrealctl](/docs/reference/cli/surrealctl/overview.md) - managing organisations and Cloud instances from the command line.

---

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.2.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.

In this section, we will go over 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.

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.

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.2.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>.

In general, it is known as a best 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 is 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 explicitely call this method.
>Just note that in some contexts, calling this method before hand 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 support Dependency Injection to ease the use of <code>SurrealDbClient</code> and <code>SurrealDbSession</code> in your application.

## Create a new project

Let's 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.
Notice that all we have to do is one line of code to configure the SurrealDB client with the previously set Connection String.

> [!NOTE]
> By default, this function will register both <code>ISurrealDbSession</code> and <code>SurrealDbSession</code> using the <code>Scoped</code> service lifetime. This mean 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 is an important part of any application to understand what is happening. 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}

Listen 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 it's 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 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; }
}
```

The default type of an `Id` in SurrealDB being a `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 can be extremely 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.
This guide will walk you through the process of installing and importing the SDK into your project.

## 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 it's 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.

Listen 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.2.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]
> New to CGO? 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.2.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/surrealdb-cli/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/surrealdb-cli/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/surrealdb-cli/commands/start.md) for server configuration and storage backends
- [SurrealDB CLI import](/docs/reference/cli/surrealdb-cli/commands/import.md) and [export](/docs/reference/cli/surrealdb-cli/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.0.8`.
> The SDK works with SurrealDB versions `v2.0.0` and later, including the current release, `v3.2.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 or on the server through WebAssembly

## 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** (coming soon)

- **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.

Reference documentation for 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 for better development experience.

```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. 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 for better developer experience:

```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 canceling, 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 unrecognized `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

### `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
```

---

### `.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. Off by default; 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. Off by default; 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. Off by default; 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 specialized 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`)

## 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.

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. Leverage 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. Off by default; 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. Off by default; 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 });
```

---

### `.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. Off by default; 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. Off by default; 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 comprehensive 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. Modeled 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

Leverage 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 defense 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. Leverage 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) - Type-safe table references
  - `new Table<T>(name)` - Create typed table reference
  - 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, essential for financial calculations and 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 essential for 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. 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 Categorization** - Categorize 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/sdk/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.)

## 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&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');
```

## Properties

### `table` {#table}

The table name component, returned as a `Table` instance.

**Type:** `Table<Tb>`

```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.

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&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'),
));
```

## 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

Type-safe table references for query operations.

The `Table` class provides type-safe references to database tables, enabling TypeScript type checking for query results.

**Import:**
```ts
import { Table } from 'surrealdb';
```

**Source:** [value/table.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/value/table.ts)

## Type parameters

- `T` - The record type for this table (optional, defaults to `string`)

## Constructor

### `new Table<T>(name)` {#constructor}

Create a new table reference.

```ts title="Syntax"
new Table(name)
new Table<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');

// Type-safe table reference
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);
```

## Properties

### `name` {#name}

The table name.

**Type:** `string`

```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 type-safe 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
async function getAllFromTable<T>(
    db: Surreal,
    table: Table<T>
): Promise<T[]> {
    return db.select(table);
}

async function deleteAllFromTable<T>(
    db: Surreal,
    table: Table<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. Use type parameters

```ts
// Good: Type-safe
const users = new Table<User>('users');
const results: User[] = await db.select(users);

// Avoid: No type safety
const users = new Table('users');
const results = await db.select(users); // any[]
```

### 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 'any' 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<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: 'surrealdb',
});
```

**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: 'surrealdb',
});
```

**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: 'surrealdb',
});
```

**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.

## 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,
});
```

## 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: 'surrealdb',
    },
});
```

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.

## 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.

## 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');
}
```

## 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();
```

## 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. Reach for 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 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.
- **HTTP** connections are 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.

## 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://`.

## 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
```

### 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
```

### 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 it is worth knowing which one a failure comes from.

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 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]);
```

## 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);
    }
}
```

Results arrive in batches as the server produces them, so the first rows are available before the last ones have been read.

> [!NOTE]
> The embedded engines - `@surrealdb/node-native` and `@surrealdb/wasm-native` - stream from SurrealDB 3.3.0. On earlier versions they held the whole result in memory and returned nothing until the last row was read, so `.stream()` gave no benefit over awaiting the query. Connections over WebSocket stream on earlier versions too.

## 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);
    }
}
```

## 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 |

## 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;
}
```

## 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/surrealdb-cli/environment-variables.md#websocket-config) or [`SURREAL_MAX_TRANSACTIONS_PER_SESSION`](/docs/reference/cli/surrealdb-cli/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)
```

Handy 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. 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/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:

**npm**

```bash
npm install --save @surrealdb/node
```

**yarn**

```bash
yarn add @surrealdb/node
```

**pnpm**

```bash
pnpm install @surrealdb/node
```

## 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:

**npm**

```bash
npm install --save @surrealdb/wasm
```

**yarn**

```bash
yarn add @surrealdb/wasm
```

**pnpm**

```bash
pnpm install @surrealdb/wasm
```

## 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 modern JavaScript framework for building responsive and high-performing 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.

In this section, you will learn how to install the JavaScript SDK in your project.

## Install the SDK

First, install the [SurrealDB SDK](https://npmjs.com/package/surrealdb) using your favorite 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. Depending on your setup and environment, we supported multiple options.

**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, check out the quick start guide to build your 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)
- [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/surrealdb-cli/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. Under the hood 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.2.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://`) unlocks 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 under the hood. 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.

In this section, you will learn how to install the SurrealDB Mojo SDK and add 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.

It does not replace Laravel's SQL database. SurrealDB runs alongside 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.

The core idea is small: describe your SurrealDB tables with models and attributes, then build queries 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 rows.

```php
$name = User::selectValue(fn ($user) => $user->name)
    ->where(fn ($user) => $user->age->gte(18))
    ->first();
```

`SELECT VALUE` rows 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 rows |
| `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 rows 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()` under the hood, 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 quering to 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 to 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 convert a 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 column names and values.

```php
$person = $db->create("person:tobie", [
	"name" => "Tobie",
	"lastname" => "Morgan Hitchcock",
	"age" => 30,
	"hobbies" => ["reading", "coding"]
]);
```

## Selecting records

After when you created a record, you can now 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 column names and values. Updating a record can be done if 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 columns 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 column 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 wether 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 is in good shape. `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.
This 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

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including 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.2.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 rows* 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.

## 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"))
		```

When selecting a table, the method returns a list. When selecting a specific record, it returns a single value or `None` 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 it is worth knowing which one a failure comes from.

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`. Reach for 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).

**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 newly created session does not inherit the namespace, database, or authentication state of the parent connection. You must configure these explicitly on the session.

## 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.

**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.

In this section, you will learn how to install the Python SDK in your 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 an explanation, essentially revealing the execution plan to provide transparency and understanding of the query performance.

## Syntax

```syntax title="Clause Syntax"
@query EXPLAIN [FULL]
```

Using the `EXPLAIN` clause in addition to the `FULL` keyword is expeciallly 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
}
```

### 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).

---

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.

Overall, the `FETCH` clause in SurrealQL is a powerful tool for optimising data retrieval and simplifying query logic when working with related data.

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;
```

## 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 targets 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 columns.

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 row into a single aggregate.

_(since v3.2.5)_

A projection made entirely of bare zero-argument [`count()`](/docs/reference/query-language/functions/database-functions/count.md) implies `GROUP ALL`, so `SELECT count() FROM person` and `SELECT count() FROM person GROUP ALL` are equivalent. See [Bare `count()` implies `GROUP ALL`](/docs/reference/query-language/statements/select.md#bare-count-implies-group-all) for the cases that stay per-row (`count(field)`, `*`, `SELECT VALUE`, `SPLIT`, and so on).

## 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'
		]
	}
]
```

## 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` columns and calculates the total sales amount for each group. The `AS` clause is used to rename the calculated column 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 set where each row 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;
```

## 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. It is important to note that 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;
    }
}
```

---

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.

It is also worth noting that `COLLATE` can 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'.

_(since v3.2.5)_

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;
```

`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 the results into a set of subqueries.
- [`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 row in the result set. 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 a set 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 rows 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.

## 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 optimizations 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">
				Funcions 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 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)

## 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 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 are 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="Output"
{
	body: NONE,
    context: {},
	headers: {
		the_time_is_now: '2025-12-25T11:49:30.732Z'
	},
	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="Output"
{
	body: b"7B202261223A2074727565207D",
    context: {},
	headers: {
		the_time_is_now: '2025-12-25T11:51:18.910Z'
	},
	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="Output"
{ 
    body: {some: 'data'}, 
    context: {}, 
    headers: {}, 
    status: 200 
}
```

Setting an invalid HTTP status will result in an error.

```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="Output"
'Invalid HTTP status code: 99. Must be between 100 and 599'
```

## 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 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="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]);

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.

```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, 10 ]
```

```surql
array::range(1..=5);

[ 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. The following example shows a reduce operation performed on an array, but only up to index 2. For any further indexes, 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 > 2 { $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 examples 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 contain 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="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);

[ 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, 9, 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");

[ "something", 4, 3, 3, 2, 1, 1, 9, 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, 9, 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 row, 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.

_(since v3.3.0)_

When every field in the projection is a bare zero-argument `count()` (optionally aliased), SurrealDB [implies `GROUP ALL`](/docs/reference/query-language/statements/select.md#bare-count-implies-group-all). Before this change, a bare `count()` returned the constant `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 rows 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. From 3.3.0, a projection made only of bare `count()` [implies `GROUP ALL`](/docs/reference/query-language/statements/select.md#bare-count-implies-group-all); the examples keep the explicit form.

```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 />

## 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. From 3.3.0, a bare `count()` projection [implies `GROUP ALL`](/docs/reference/query-language/statements/select.md#bare-count-implies-group-all); prefer the explicit form in examples and production queries.

```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=4096,t=3,p=1$pbZ6yJ2rPJKk4pyEMVwslQ$jHzpsiB+3S/H+kwFXEcr10vmOiDkBkydVCSMfRxV7CA";
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");

"$argon2id$v=19$m=4096,t=3,p=1$pbZ6yJ2rPJKk4pyEMVwslQ$jHzpsiB+3S/H+kwFXEcr10vmOiDkBkydVCSMfRxV7CA"
```

<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 a bytes to base64 with optionally padded output.

**API DEFINITION (before 2.3.0)**

```surql
encoding::base64::encode(bytes) -> string
```

**API DEFINITION (after 2.3.0)**

```surql
encoding::base64::encode(bytes, $pad_output: option<bool>) -> 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 tha 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 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](/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 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](/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 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)` | Rows 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 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 start` + `surreal sql`)

When the REPL connects over `ws://`, `http://`, or similar, pass capability flags on **`surreal start`** only. The same flags on `surreal sql` do not enable or disable `eval` at runtime.

```bash
# Terminal 1 - start the server with eval enabled for system users
surreal start --user root --pass secret --allow-eval-query

# Terminal 2 - no --allow-eval-query needed on the client
surreal 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 sql` only)

When you open the REPL against embedded storage (`memory`, `rocksdb://…`, …) without a separate `surreal start` process, configure capabilities on **`surreal sql`** instead:

```bash
surreal sql --user root --pass secret --allow-eval-query
```

See [Capabilities and remote connections](/docs/reference/cli/surrealdb-cli/commands/sql.md#capabilities-and-remote-connections) for the full remote versus embedded model.

See [`SURREAL_CAPS_ALLOW_EVAL_QUERY`](/docs/reference/cli/surrealdb-cli/environment-variables.md) and [`SURREAL_CAPS_DENY_EVAL_QUERY`](/docs/reference/cli/surrealdb-cli/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 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.

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/surrealdb-cli/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>

_(since v3.0.0)_

## `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" />

<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 add the following constraint:
each `Point` coordinates are 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 pullinng 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 a set 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
```

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`

_(since v2.2.0)_

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'
```

_(since v2.2.0)_

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"
```

_(since v2.3.0)_

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 reach for 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);

'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 outut 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 only, 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 set 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) statement 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:50000| 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: {
        initial: 0,
        pending: 0,
        status: 'indexing', 
        updated: 0
    }
}

-------- Query 2 --------
{ 
    building: {
        initial: 100,
        pending: 20,
        status: 'indexing', 
        updated: 0
    }
}

-------- Query 3 --------
{ 
    building: {
        initial: 100,
        pending: 4,
        status: 'indexing', 
        updated: 16
    }
}

-------- Query 4 --------
{
    building: {
        status: 'ready'
    }
}
```

## 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.

**Before 2.3**

```surql title="API DEFINITION"
string::replace(string, $from: string, $to: string) -> string
```

**After 2.3**

```surql title="API DEFINITION"
string::replace(string, $from: string|regex, $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 examples 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 examples 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 examples 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 examples 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 examples 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 examples 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 examples 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 examples 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 in. 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 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/>

_(since v3.0.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/>

_(since v3.0.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 a 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 assertation 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 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.

A collection of essential vector operations that provide foundational functionality for numerical computation, machine learning, and data analysis. These operations include distance measurements, similarity coefficients, and other basic and complex operations related to vectors. Through understanding and implementing these functions, we can perform a wide variety of tasks ranging from data processing to advanced statistical analyses.

<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, the model can be called with inputs resulting in a calculation
from the trained ml model. We can do a basic raw computation with the following call:

```surql title="API DEFINITION"
ml::house-price-prediction<0.0.1>(500.0, 1.0);
```
In the above example, the model we are calling is called `house-price-prediction` with the version `0.0.1`. We
then pass in a raw vector of `[ [500.0, 1.0] ]` Depending on the model, the name and version of the model will vary as well
as the inputs. The name and version of the model will be defined in the `.surml` file which will defined when uploading the
model to the database. We can also perform a "buffered compute" with the code below:

```surql title="API DEFINITION"
ml::house-price-prediction<0.0.1>({squarefoot: 500.0, num_floors: 1.0});
```
Here, we are using the key mappings in the header of the `.surml` file uploaded to the database 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
```

Seeing as the ML is integrated into our surql, we can infer entire columns using the ml function. We can demonstrate this with a simple
example of house prices. We can define some basic table with the following surql:

```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;
```

We can then get all the rows with the imputed price prediction with the surql below:

```surql
SELECT 
	*, 
	ml::house-price-prediction<0.0.1>({ squarefoot: squarefoot_col, num_floors: num_floors_col }) AS price_prediction 
FROM house_listing;
```

This would statement 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
	},
	{
		"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;
-- 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"];
-- Output:
[42, 314, 271, 137, 141]
```

```surql
<array<string>> [42, 314, 271, 137, 141];
-- 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";
-- Output:
true
```

```surql
<bool>"false";
-- Output:
false
```

## `<datetime>`

The `<datetime>` casting function converts a value into a datetime.

```surql
<datetime>"2025-06-07";
-- 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";
-- 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;
-- Output:
13.572948467293847f
```

```surql
<float>"13.572948467293847293841093845679289";
-- Output:
13.572948467293847
```

## `<int>`

The `<int>` casting function converts a value into an integer.

```surql
<int>53;
-- Output:
53
```

## `<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;
-- Output:
'true'
```

```surql
<string>1.3463;
-- Output:
'1.3463f'
```

```surql
<string>false;
-- Output:
"false"
```

## `<regex>`

The `<regex>` casting function converts a value into a regular expression.

```surql
<regex> "a|b" = "a";
-- Output:
true
```

```surql
<regex> "a|b" = "c";
-- 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 orderd.
            </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]
```

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 opearation 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 is used when a `WHERE` clause is included.

However, if a `WHERE` clause is unavoidable, performance can still be improved by simplifying the portion after the clause as much as possible. As a boolean is the simplest possible datatype, having a boolean field that can be used in a `WHERE` clause can significantly improve performance compared to a more complex operation.

```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;
```

## 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.

## Casting from strings

Bytes can be created by casting from a string, and are displayed using hexidecimal 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 hexidecimal 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;
```

One powerful feature available in SurrealDB is the ability 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.

**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"];
```

**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"];
```

## 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);
```

## Conclusion

These anonymous functions provide a flexible way to define small, reusable pieces of logic that can be used throughout your queries. By leveraging them, you can write more modular and maintainable SurrealQL code.

---

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. SurrealDB automatically parses and understands datetimes which are written as strings in the SurrealQL language. Times must also be formatted in [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) format.

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";

-- 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
        };
```

## 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 makes working with GeoJSON easy, with support for `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 specificies 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 of a population of at least 1000. The queries below create a `city` record from each entry that includes their name, location, and name. Next, it uses the [`geo::distance`](/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'
}
```

### 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 especially useful when checking for a number that must be within a certain range. Using a range carries two main benefits. One is that it produces shorter code that is easier to read and maintain.

```surql
SELECT * FROM person WHERE age >= 18 AND age <= 65;
SELECT * FROM person WHERE age IN 18..=65;
```

Another benefit is performance. The following code should show a modest but measurable improvement in performance between the first and second `SELECT` statement, as only one condition needs to be checked instead of two.

```surql
CREATE |person:20000| SET age = (rand::float() * 120).round() RETURN NONE;

-- Assign output to a parameter so the SELECT output is not displayed
LET $_ = SELECT * FROM person WHERE age > 18 AND age < 65;
LET $_ = SELECT * FROM person WHERE age in 18..=65;
```

## 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 become 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.

```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.

```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`
	}
]
```
## 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 ];
```
## 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 all temperature records with IDs less than a maximum value
SELECT * FROM temperature:..['London', '2022-08-29T08:09:31'];

-- Select all temperature records with IDs greater than a minimum value
SELECT * FROM temperature:['London', '2022-08-29T08:03:39']..;

-- 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'];
```
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;
```
## Limitations

At present, the `VALUE` clause cannot be used inside a [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statement.

```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="Output"
[
	{
		id: user:9ixn3oei6o532c2qyixa
	}
]
```
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);
```

## 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)");
```

---

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

An 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.

---

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",
    || > 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
    ];
```

---

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 provide a powerful and flexible way to access and manipulate data within records using paths. They allow you to navigate through nested data structures, access fields, array elements, call methods, and perform complex queries with ease. 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 usecase is in data retrival 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, let's say we have 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.

## 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.

## 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, let's create a new `person` record and then call `uppercase()` on its name field.

```surql
CREATE person CONTENT {
    name: "John Doe",
    age: 30,
    address: {
      city: "New York",
      country: "USA"
    }
};

SELECT *, name.uppercase() FROM person;
```

```surql title="Output"
[
  {
    "person": {
      "name": "John Doe",
      "age": 30,
      "address": {
        "city": "New York",
        "country": "USA"
      }
    }
  }
]
```

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.

## 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
		}
	]
}
```

## 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.

## 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;
```

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.
	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 are a good way to understand how the syntax inside the structure of the query.

- 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| SET name = id.id() 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

_(since v2.2.0)_

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;
```

#### +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 seamlessly.

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**: Leverage 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

Idioms in SurrealQL are a powerful tool for navigating and manipulating data within your database. By understanding and effectively using idiom parts, you can write expressive and efficient queries that handle complex data structures with ease. Whether you're accessing nested fields, filtering arrays, or traversing graph relationships, idioms provide the flexibility you need to interact with your data seamlessly.

---

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
SELECT * FROM 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
SELECT * FROM 0 OR false OR 10;
```

```surql title="Output"
10
```

<br />

## `!`
The `not` operator reverses the truthiness of a value.

```surql
SELECT * FROM !(TRUE OR FALSE);
//- false

SELECT * FROM !"Has a value";
//- false
```

<br />

## `!!`
The `not not` operator is simply an application of the `!` operator twice. It can be used to determines the truthiness of a value.

```surql
SELECT * FROM !!"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
SELECT * FROM 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
SELECT * FROM NULL ?: 0 ?: false ?: 10;
```

```surql title="Output"
10
```

<br />

## `=` or `IS`
The `equal` operator checks whether two values are equal.

```surql
SELECT * FROM true = "true";
```

```surql title="Output"
false
```

```surql
SELECT * FROM 10 = "10";
```

```surql title="Output"
false
```

```surql
SELECT * FROM 10 = 10.00;
```

```surql title="Output"
true
```
```surql
SELECT * FROM 10 = "10.3";
```

```surql title="Output"
false
```

```surql
SELECT * FROM [1, 2, 3] = [1, 2, 3];
```

```surql title="Output"
true
```

```surql
SELECT * FROM [1, 2, 3] = [1, 2, 3, 4];
```

```surql title="Output"
false
```

```surql
SELECT * FROM { this: "object" } = { this: "object" };
```

```surql title="Output"
true
```

```surql
SELECT * FROM { this: "object" } = { another: "object" };
```

```surql title="Output"
false
```

<br />

## `!=` or `IS NOT`
The `not equal` operator checks whether two values are not equal.

```surql
SELECT * FROM 10 != "15";
```

```surql title="Output"
true
```

```surql
SELECT * FROM 10 != "test";
```

```surql title="Output"
true
```

```surql
SELECT * FROM [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
SELECT * FROM 10 == 10;
```

```surql title="Output"
true
```

```surql
SELECT * FROM 10 == "10";
```

```surql title="Output"
false
```

```surql
SELECT * FROM true == "true";
```

```surql title="Output"
false
```

<br />

## `?=`
The `any equal` operator checks whether any value in an array equals another value.

```surql
SELECT * FROM [10, 15, 20] ?= 10;
```

```surql title="Output"
true
```

<br />

## `*=`
The `all equal` operator checks whether all values in an array equals another value.

```surql
SELECT * FROM [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
SELECT * FROM 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
SELECT * FROM 10 <= 15;
```

```surql title="Output"
true
```

<br />

## `>`
The `greater than` operator checks whether a value is less than another value.

```surql
SELECT * FROM 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
SELECT * FROM 15 >= 10;
```

```surql title="Output"
true
```

<br />

## `+`
The `add` operator adds two values together.

```surql
SELECT * FROM 10 + 10;
```

```surql title="Output"
20
```

```surql
SELECT * FROM "test" + " " + "this";
```

```surql title="Output"
"test this"
```

```surql
SELECT * FROM 13h + 30m;
```

```surql title="Output"
"13h30m"
```

<br />

## `-`
The `subtract` operator subtracts a value from another value.

```surql
SELECT * FROM 20 - 10;
```

```surql title="Output"
10
```

```surql
SELECT * FROM 2m - 1m;
```

```surql title="Output"
1m
```

<br />

## `*` or `×`
The `multiply` operator multiplies a value by another value.

```surql
SELECT * FROM 20 * 2;
```

```surql title="Output"
40
```

<br />

## `/` or `÷`
The `divide` operator divides a value by another value.

```surql
SELECT * FROM 20 / 2;
```

```surql title="Output"
10
```

<br />

## `**`
The `power` operator raises a base value by another value.

```surql
SELECT * FROM 20 ** 3;
```

```surql title="Output"
8000
```

<br />

## `CONTAINS` or `∋`
The `contains` operator checks whether a value contains another value.

```surql
SELECT * FROM [10, 20, 30] CONTAINS 10;
```

```surql title="Output"
true
```

```surql
SELECT * FROM "this is some text" CONTAINS "text";
```

```surql title="Output"
true
```

```surql
SELECT * FROM {
	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
SELECT * FROM [10, 20, 30] CONTAINSNOT 15;
```

```surql title="Output"
true
```

```surql
SELECT * FROM "this is some text" CONTAINSNOT "other";
```

```surql title="Output"
true
```

```surql
SELECT * FROM {
	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
SELECT * FROM [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
SELECT * FROM [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
SELECT * FROM [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
SELECT * FROM 10 INSIDE [10, 20, 30];
```

```surql title="Output"
true
```

```surql
SELECT * FROM "text" INSIDE "this is some text";
```

```surql title="Output"
true
```

```surql
SELECT * FROM (-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]
	]]
};

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
SELECT * FROM 15 NOTINSIDE [10, 20, 30];
```

```surql title="Output"
true
```

```surql
SELECT * FROM "other" NOTINSIDE "this is some text";
```

```surql title="Output"
true
```

```surql
SELECT * FROM (-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
SELECT * FROM [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
SELECT * FROM [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
SELECT * FROM [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
SELECT * FROM (-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
SELECT * FROM {
	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'
	}
]
```

_(since v3.0.0)_

### `AND`, `OR`, and numeric operators inside `@@`

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";
```

## `KNN`

K-Nearest Neighbors (KNN) is a fundamental algorithm used for classifying or regressing based on the closest data points in the feature space, with its performance and scalability critical in applications involving large datasets.

In practice, the efficiency and scalability of the KNN algorithm are crucial, especially 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 DIST EUCLIDEAN EFC 150 M 12;
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 defined on a string value can be used via the operators `CONTAINSANY`, `ALLINSIDE`, or `ANYINSIDE`. The operator `CONTAINS`, however, will not use a defined index as `CONTAINS` is used for substring matches between strings themselves as opposed to an index lookup.

```surql
DEFINE FIELD name ON account TYPE string;
DEFINE INDEX name_index ON account FIELDS name;

CREATE account:billy SET name = "Billy McConnell";

-- Both return the user Billy McConnell
SELECT * FROM account WHERE name CONTAINS "Billy McConnell";
SELECT * FROM account WHERE name CONTAINSANY ["Billy McConnell"];

-- However, CONTAINS does not use the index
SELECT * FROM account WHERE name CONTAINS "Billy McConnell" EXPLAIN FULL;
-- CONTAINSANY + putting the value inside an array will use the index
SELECT * FROM account
  WHERE name CONTAINSANY ["Billy McConnell"] EXPLAIN FULL;
```

## 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 show 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"],
});
```

## 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;
```

### $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"
            ]
        }
    }
]
```

### $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()
);
```

### $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 SET name = "User1", member_of = "group1";
CREATE user SET name = "User2", member_of = "group1";
CREATE user 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",
            "User3",
            "User2"
        ],
        "name": "User1"
    }
]
```

```surql
INSERT INTO person (name) VALUES ("John Doe"),
  ("John Doe"),
  ("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:hwffcckiv61ylwiw43yf",
        "name": "John Doe",
        "people_with_same_name": [
            "person:hwffcckiv61ylwiw43yf",
            "person:tmscoy7bjj20xki0fld5"
        ]
    },
    {
        "id": "person:tmscoy7bjj20xki0fld5",
        "name": "John Doe",
        "people_with_same_name": [
            "person:hwffcckiv61ylwiw43yf",
            "person:tmscoy7bjj20xki0fld5"
        ]
    },
    {
        "id": "person:y7mdf3912rf5gynvxc7q",
        "name": "Jane Doe",
        "people_with_same_name": [
            "person:y7mdf3912rf5gynvxc7q"
        ]
    }
]
```

### $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'
);
```

## 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.

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, 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];
```

## 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"] ]
					]
				}
			}
		}
	}
]
```

---

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.

_(since v2.2.0)_

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'
	}
]
```

## 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 SET author = person:one, text = "5/10 for this blog post. The problems I have with it are...";
CREATE comment 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:8msvp0egg8cdlyu4vvn9,
		text: 'WOW! I never knew you could cut a rope with an arrow.'
	},
	{
		author: person:one,
		id: comment:i72qfjy59vbn81hk6lrm,
		text: '5/10 for this blog post. The problems I have with it are...'
	}
]

-------- Query --------

[]

-------- Query --------

[]
```

### 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 parameters 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 row, or run a `SELECT` that returns rows to your application.

Statements are not all the same kind 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 a set of 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 a set of 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
	}
]
```

## 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 />

---

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 ran in. The this context of every embedded function is automatically set to the current document on every invocation. This allows the function to access the properties and fields of the current record being accessed / modified.

```surql
CREATE film SET
	ratings = [
		{ rating: 6, user: user:bt8e39uh1ouhfm8ko8s0 },
		{ rating: 8, user: user:bsilfhu88j04rgs0ga70 },
	],
	featured = function() {
		return this.ratings.filter(
			({ rating }) => rating >= 7
		).map(({ rating, ...data }) => {
			return {
				...data,
				rating: rating * 10
			};
		});
	}
;
```

---

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 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
		]
	}
]
```

## 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.

_(since v2.2.0)_

> [!CAUTION]
> Currently, the `ACCESS` statement is an experimental feature intended to be used for validating its suitability and security. As such, it may be subject to breaking changes and may present unidentified security issues. Do not rely on this feature in production applications.

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 logs are identified by the `surrealdb_core::sql::statements::access` prefix.

## 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
ACCESS api PURGE EXPIRED;
```

### Example: Purging grants that have been revoked for more than 90 days

```surql
ACCESS api PURGE REVOKED FOR 90d;
```

### Example: Purging all grants that have been invalid for more than a year

```surql
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 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 ]
  [ 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` clause, 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 ] ON [ TABLE ] @table 
[ 
    DROP TYPE |
    DROP FLEXIBLE |
    DROP READONLY |
    DROP VALUE |
    DROP ASSERT |
    DROP DEFAULT |
    DROP COMMENT |
    DROP REFERENCE |
    FLEXIBLE |
    READONLY |
    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 along with the `EXPLAIN` clause 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 onPerforms 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, after which `ALTER SEQUENCE` can be used to modify the timeout to make it available.

```surql
DEFINE SEQUENCE mySeq3 BATCH 1000 START 100 TIMEOUT 0ns;
INFO FOR DB.sequences;
sequence::nextval('mySeq3');

ALTER SEQUENCE mySeq3 TIMEOUT 100ms;
sequence::nextval('mySeq3');
```

```surql title="Output"
-------- Query --------
{ mySeq3: 'DEFINE SEQUENCE mySeq3 BATCH 1000 START 100 TIMEOUT 0ns' },

-------- Query --------
'Thrown error: The query was not executed because it exceeded the timeout: 0ns'

-------- Query --------
100
```

## See also

* [`DEFINE SEQUENCE`](/docs/reference/query-language/statements/define/sequence.md)

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/system

# ALTER SYSTEM

The ALTER SYSTEM statement is used to alter the entire datastore.

_(since v3.0.0)_

The `ALTER SYSTEM` statement is used to alter the entire datastore. It can be used to compact the system, or to set or drop a systemwide query timeout.

This statement is the only `ALTER` statement that does not have a corresponding `DEFINE` statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER SYSTEM 
    COMPACT |
    QUERY_TIMEOUT |
    DROP QUERY_TIMEOUT
```

## `QUERY_TIMEOUT` clause

A query timeout can be set for the system as a whole. The minimum possible timeout is one millisecond, below which the value will be set as `NONE`.

```surql
ALTER SYSTEM QUERY_TIMEOUT 100ns;
INFO FOR ROOT.config;
```

```surql title="Output"
{ QUERY_TIMEOUT: NONE }
```

Any value above `1ms` will set the timeout, beyond which no query that takes any longer than this will succeed.

```surql
ALTER SYSTEM QUERY_TIMEOUT 1ms;
FOR $_ IN 0..1000 {
    FOR $_ IN 0..1000 {
        CREATE |person:1000|;
    }
};
```

```surql title="Output"
'The query was not executed because it exceeded the timeout: 1ms'
```

## COMPACT clause

Compacts the entire datastore. To compact other resources, use [ALTER NAMESPACE](/docs/reference/query-language/statements/alter/namespace.md) to compact the current namespace keyspace, [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 SYSTEM COMPACT;
```

```surql title="Output"
NONE
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/table

# ALTER TABLE

The ALTER TABLE statement is used to alter a defined table.

The `ALTER TABLE` statement is used to alter a defined table.

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER TABLE [
	[ IF EXISTS ] @name
		[ DROP COMMENT ]
        [ DROP CHANGEFEED ]
        [ COMPACT ]
		[ SCHEMAFULL | SCHEMALESS ]
		[ PERMISSIONS [ NONE | FULL
			| FOR select @expression
			| FOR create @expression
			| FOR update @expression
			| FOR delete @expression
		] ]
    [ CHANGEFEED @duration ]
    [ COMMENT @string ] 
    [ CHANGEFEED ]
]
```

## COMPACT

_(since v3.0.0)_

Performs storage compaction on a specific table 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 DATABASE](/docs/reference/query-language/statements/alter/database.md) to compact the current database 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 TABLE user COMPACT;
```

```surql title="Output"
NONE
```

## See also

* [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md)

---

Source: https://surrealdb.com/docs/reference/query-language/statements/alter/user

# ALTER USER

The ALTER USER statement can be used to modify an existing defined database user.

_(since v3.0.5)_

The `ALTER USER` statement can be used to modify an existing defined database [user](/docs/reference/query-language/statements/define/user.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
ALTER USER [ IF EXISTS ] @name
  ON [ ROOT | NAMESPACE | DATABASE ]
  [ PASSWORD @pass | PASSHASH @hash ]
  [ ROLES @role, ... ]
  [ DURATION FOR TOKEN [ @duration | NONE ] ]
  [ DURATION FOR SESSION [ @duration | NONE ] ]
  [ COMMENT @string | DROP COMMENT ]
```

## Example usage

```surql
-- Define a user with viewer role
DEFINE USER billy ON DATABASE PASSWORD "example" ROLES VIEWER;

-- Congrats on your promotion billy,
-- be sure to use this power for good
ALTER USER billy ON DATABASE ROLES EDITOR;
```

_(since v3.3.0)_

Changing a user's password with **`ALTER USER … PASSWORD`** regenerates the [SCRAM-SHA-256 verifier material](/docs/reference/query-language/statements/define/user.md#scram-credentials-for-postgres-clients) used by the Postgres wire protocol, in addition to updating the Argon2 hash. **`ALTER USER … PASSHASH`** updates the hash and clears any stored SCRAM verifier.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/begin

# BEGIN

The BEGIN statement starts a single transaction in which run multiple statements can be run, either succeeding as a whole, or failing.

Each statement within SurrealDB is run within its own transaction by default. The `BEGIN` statement can be used to modify this behaviour by running a group of statements inside a single transaction, either succeeding as a whole, or failing. Once all of the statements within a transaction succeed, then all of the data modifications can be made permanent by finalizing the transaction with a [COMMIT](/docs/reference/query-language/statements/commit.md) statement at the end. If any statement within a transaction encounters an error or the transaction is manually cancelled ([CANCEL](/docs/reference/query-language/statements/cancel.md)), then any data modification made within the transaction is rolled back, and will not become a permanent part of the database.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
BEGIN [ TRANSACTION ];
```

## Example usage

The following query shows example usage of this statement.

```surql
-- Start a new database transaction. Transactions are a way to ensure multiple operations
-- either all succeed or all fail, maintaining data integrity.
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;

-- 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;

-- Finalize 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;
```

## Returning early from a transaction

While all transactions require a final `COMMIT` or `CANCEL` statement in order to run, an early return can take place via the following:

* An error inside one of the statements inside the transaction,
* A `THROW` statement to return early with an error,
* A `RETURN` statement to return early. This is often used to customise the output of a transaction.

An example of the above:

```surql
BEGIN;

CREATE account:one SET balance = 135605.16;
CREATE account:two SET balance = 91031.31, wants_to_send_money = true;

-- Read the field into a parameter before testing it. As of SurrealDB
-- 3.2.x, reading a record field directly inside an IF condition fails
-- with "Specify a database to use".
LET $wants_to_send = account:two.wants_to_send_money;

IF !$wants_to_send {
    THROW "Customer doesn't want to send any money!";
};

LET $first = UPDATE ONLY account:one SET balance += 300.00;
LET $second = UPDATE ONLY account:two SET balance -= 300.00;

RETURN "Money sent! Status:\n" + <string>$first + '\n' +
  <string>$second;

COMMIT;
```

```surql title="Output"
'Money sent! Status:
{ balance: 135905.16f, id: account:one }
{ balance: 90731.31f, id: account:two, wants_to_send_money: true }'
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/break

# BREAK

The BREAK statement can be used to break out of a loop.

The BREAK statement can be used to break out of a loop, such as inside one created by the [FOR statement](/docs/reference/query-language/statements/for.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
BREAK
```

## Example usage

The following queries shows example usage of this statement.

Creating a person for everyone in the array where the number is less than or equal to 5:

```surql
LET $numbers = [1,2,3,4,5,6,7,8,9];

FOR $num IN $numbers {
    IF $num > 5 {
        BREAK;

    } ELSE IF $num < 5 {
        CREATE type::record(
            'person', $num
        ) CONTENT {
            name: "Person number " + <string>$num
        };
    };
};
```

Breaking out of a loop once unwanted data is encountered:

```surql
-- Data retrieved from somewhere which contains many NONE values
LET $weather = [
	{
		city: 'London',
		temperature: 22.2,
		timestamp: 1722565566389
	},
	NONE,
	{
		city: 'London',
		temperature: 20.1,
		timestamp: 1722652002699
	},
    {
        city: 'Phoenix',
        temperature: 45.1,
        timestamp: 1722565642160
    },
    NONE,
    NONE,
    {
        city: 'Phoenix',
        temperature: 45.1,
        timestamp: 1722652070372
    },
];

-- Sort the data to move the NONE values to the end
-- and break once the first NONE is reached
FOR $data IN array::sort::desc($weather) {
    IF $data IS NONE {
        BREAK;
    } ELSE {
        CREATE weather CONTENT $data;
    };
};
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/cancel

# CANCEL

The CANCEL 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.

Each statement within SurrealDB is run within its own transaction. If a set of changes need to be made together, then groups of statements can be run together as a single transaction, either succeeding as a whole, or failing without leaving any residual data modifications. While a transaction will fail if any of its statements encounters an error, the `CANCEL` statement can also be used to cancel a transaction manually.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
CANCEL [ TRANSACTION ];
```

## Example usage

The following query shows example usage of this statement.

```surql
BEGIN TRANSACTION;

-- Setup accounts
CREATE account:one SET balance = 135605.16;
CREATE account:two SET balance = 91031.31;

-- Move money
UPDATE account:one SET balance += 300.00;
UPDATE account:two SET balance -= 300.00;

-- Rollback all changes
CANCEL TRANSACTION;
```

`CANCEL` is not used to automatically cancel a transaction based on a condition such as inside an [IF..ELSE](/docs/reference/query-language/statements/if-else.md) block. Instead, a [THROW](/docs/reference/query-language/statements/throw.md) statement is used. THROW can be followed by any value, usually a string containing context behind the error.

```surql
BEGIN TRANSACTION;

-- Setup accounts
CREATE account:one SET balance = 135605.16;
CREATE account:two SET balance = 200.31;

-- Move money
UPDATE account:one SET balance += 300.00;
UPDATE account:two SET balance -= 300.00;

IF account:two.balance < 0 {
    THROW "Not enough funds";
};

COMMIT TRANSACTION;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/commit

# COMMIT

The COMMIT statement is used to commit a set of statements within a transaction, ensuring that all data modifications become a permanent part of the database.

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, either succeeding as a whole, or failing without leaving any residual data modifications. A `COMMIT` statement is used at the end of such a transaction to make the data modifications a permanent part of the database.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
COMMIT [ TRANSACTION ];
```

## Example usage

The following query shows example usage of this statement.

```surql
BEGIN TRANSACTION;

-- Setup accounts
CREATE account:one SET balance = 135605.16;
CREATE account:two SET balance = 91031.31;

-- Move money
UPDATE account:one SET balance += 300.00;
UPDATE account:two SET balance -= 300.00;

-- Finalise all changes
COMMIT TRANSACTION;
```

The following two options can be used at any point if a transaction must be cancelled without commiting the changes:

* [CANCEL](/docs/reference/query-language/statements/cancel.md) to manually cancel the transaction.
* [THROW](/docs/reference/query-language/statements/throw.md) to cancel a transaction with an optional error message. THROW is the only way to cancel a transaction based on a condition, such as inside an [IF..ELSE](/docs/reference/query-language/statements/if-else.md) block.

In addition, a `RETURN` statement can be used to return early from a successful transaction. This is often used in order to return a customised output.

```surql
BEGIN;

CREATE account:one SET balance = 135605.16;
CREATE account:two SET balance = 91031.31, wants_to_send_money = true;

-- Read the field into a parameter before testing it. As of SurrealDB
-- 3.2.x, reading a record field directly inside an IF condition fails
-- with "Specify a database to use".
LET $wants_to_send = account:two.wants_to_send_money;

IF !$wants_to_send {
    THROW "Customer doesn't want to send any money!";
};

LET $first = UPDATE ONLY account:one SET balance += 300.00;
LET $second = UPDATE ONLY account:two SET balance -= 300.00;

RETURN "Money sent! Status:\n" + <string>$first + '\n' + <string>$second;

COMMIT;
```

```surql title="Output"
'Money sent! Status:
{ balance: 135905.16f, id: account:one }
{ balance: 90731.31f, id: account:two, wants_to_send_money: true }'
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/continue

# CONTINUE

The CONTINUE statement can be used to skip an iteration of a loop, like within the FOR statement

The CONTINUE statement can be used to skip an iteration of a loop, like within the [FOR statement](/docs/reference/query-language/statements/for.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
CONTINUE
```

## Example usage

The following queries shows example usage of this statement.

Skipping an iteration of a loop unless a certain condition is met:

```surql
-- Set can_vote to true for every person over 18 years old.
FOR $person IN (SELECT id, age FROM person) {
	IF ($person.age < 18) {
		CONTINUE;
	};

	UPDATE $person.id SET can_vote = true;
};
```

Skipping an iteration of a loop when bad data is encountered:

```surql
-- Data retrieved from somewhere which contains many NONE values
LET $weather = [
	{
		city: 'London',
		temperature: 22.2,
		timestamp: 1722565566389
	},
	NONE,
	{
		city: 'London',
		temperature: 20.1,
		timestamp: 1722652002699
	},
    {
        city: 'Phoenix',
        temperature: 45.1,
        timestamp: 1722565642160
    },
    NONE,
    NONE,
    {
        city: 'Phoenix',
        temperature: 45.1,
        timestamp: 1722652070372
    },
];

FOR $data IN $weather {
    IF $data IS NONE {
        CONTINUE;
    };

	CREATE weather CONTENT $data;
};
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/create

# CREATE

The CREATE statement can be used to add a record to the database if it does not already exist.

The `CREATE` statement can be used to add a record to the database. If the record already exists, the statement will give an error.

> [!NOTE]
> This statement can not be used to create graph relationships. For that, use the [`RELATE`](/docs/reference/query-language/statements/relate.md) statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
CREATE [ ONLY ] @targets
	[ CONTENT @value
	  | SET @field = @value ...
	]
	[ RETURN NONE | RETURN BEFORE | RETURN AFTER | RETURN DIFF | RETURN @statement_param, ... | RETURN VALUE @statement_param ]
	[ TIMEOUT @duration ]
;
```

## Creating a table record

`CREATE` can be used with just a table name, in which case its ID will be generated randomly.

```surql
-- Create a new record
CREATE person;
```

```surql title="Output"
[
    {
        "id": "person:2vvgzt6m24s952yiy7x8"
    }
]
```

To specify a specific ID for a table instead, use `:` followed by a value.

```surql
CREATE person:one;
```

```surql title="Output"
[
	{
		id: person:one
	}
]
```

The table name and ID together form the full [record ID](/docs/reference/query-language/language-primitives/data-types/record-ids.md) which can be used to query the created data or by using the [`SELECT`](/docs/reference/query-language/statements/select.md) statement. See the [record ID](/docs/reference/query-language/language-primitives/data-types/record-ids.md) page to learn more about what counts as a valid record identifier.

The default random ID can be generated in different ways (such as a ULID) using the [built-in ID generation functions](/docs/reference/query-language/language-primitives/data-types/record-ids.md#types-of-record-ids).

It is also possible to specify the ID of the record you want to create using a string or any of the supported formats for [record IDs](/docs/reference/query-language/language-primitives/data-types/record-ids.md).

```surql
-- Use the type::record() function to provide a record's table and id separately
CREATE type::record("person", "one");
```

## Adding record data

When creating a record, you can specify the record data using the `CONTENT` clause, or the `SET` clause. The `CONTENT` clause is used to specify record data using a SurrealQL object, while the `SET` clause is used to specify record data one field at a time. The `CONTENT` clause is useful when the record data is already in the form of a SurrealQL or JSON object, while the `SET` clause reads well when each field is computed on its own line.

Specifying record data using the `CONTENT` keyword:

```surql
-- Create a new record with a numeric id
CREATE person:100 CONTENT {
	name: 'Tobie',
	company: 'SurrealDB',
	skills: ['Rust', 'Go', 'JavaScript'],
};
```

Specifying the same record data one field at a time using the `SET` clause:

```surql
-- Create a new record with a text id
CREATE person:tobie SET
    name = 'Tobie',
    company = 'SurrealDB',
    skills = ['Rust', 'Go', 'JavaScript'];
```

The above will create a new record with the ID `person:tobie` and the specified data.

```surql title="Output"
[
	{
		"id": "person:tobie",
		"name": "Tobie",
		"company": "SurrealDB",
		"skills": ["Rust", "Go", "JavaScript"]
	}
]
```

## Options and clauses

### Creating multiple records

Multiple records or even multiple record types can be created by separating table names by commas.

```surql
-- Note: record::tb(id) returns just the table name portion of a record ID.
-- The id is not yet bound while CREATE is still evaluating its fields, so
-- the records are created first and named in a following UPDATE.
CREATE townsperson, cat, dog SET
    created_at = time::now();
UPDATE townsperson, cat, dog SET
    name = "Just a " + record::tb(id);
```

```surql title="Output"
[
    {
        "created_at": "2024-03-19T03:12:05.079Z",
        "id": "townsperson:p37ha2lngckp3v8tvf2j",
        "name": "Just a townsperson"
    },
    {
        "created_at": "2024-03-19T03:12:05.080Z",
        "id": "cat:p1pwbjaq96nhhnuohjtc",
        "name": "Just a cat"
    },
    {
        "created_at": "2024-03-19T03:12:05.080Z",
        "id": "dog:01vcxgdpuctdk354hzkp",
        "name": "Just a dog"
    }
]
```

The `| |` syntax is another way to create multiple records in a single execution. This syntax can be used in two ways.

One is by including a table name, a `:` (a colon), and then a number. This will create a quantity of records equal to the number after the table name. The records created will have random IDs.

```surql
-- Creates three townperson records with a random ID
CREATE |townsperson:3|;
```

```surql title="Output"
[
	{
		id: townsperson:hzkt0piy3f72xo5dl2jf
	},
	{
		id: townsperson:k0mujrohm8qe2txz5pnz
	},
	{
		id: townsperson:pwumqelrsi1qt0jmihwh
	}
]
```

The other method is by using the `..` range syntax after the `:` instead of a single number. This will create records with specific IDs that span across the range indicated.

```surql
-- Note: 1..4 used to be inclusive until SurrealDB 3.0.0
-- Now creates 1 up to but not including 4
CREATE |townsperson:1..4|;
```

```surql title="Output"
[
	{
		id: townsperson:1
	},
	{
		id: townsperson:2
	},
	{
		id: townsperson:3
	}
]
```

All of these methods can be combined to create multiple records at the same time.

```surql
CREATE dog, |cat:2|, |townsperson:1..3| SET
    created_at = time::now(),
    name = "Just a " + record::tb(id);
```

```surql title="Output"
[
	{
		created_at: '2024-08-13T04:14:44.135Z',
		id: dog:u3fzmqvg3yq9mo3o6z2s,
		name: 'Just a dog'
	},
	{
		created_at: '2024-08-13T04:14:44.137Z',
		id: cat:n6x3caiiazucslfs7rpm,
		name: 'Just a cat'
	},
	{
		created_at: '2024-08-13T04:14:44.137Z',
		id: cat:rnvhxgjhsbea5u58s0wu,
		name: 'Just a cat'
	},
	{
		created_at: '2024-08-13T04:14:44.137Z',
		id: townsperson:1,
		name: 'Just a townsperson'
	},
	{
		created_at: '2024-08-13T04:14:44.137Z',
		id: townsperson:2,
		name: 'Just a townsperson'
	},
	{
		created_at: '2024-08-13T04:14:44.137Z',
		id: townsperson:3,
		name: 'Just a townsperson'
	}
]
```

### ONLY

When creating a single record, the `ONLY` clause can be used to return the record object on its own instead of inside an array.

```surql
-- Returns an array with a single record inside
CREATE person:tobie SET
    name = 'Tobie',
    company = 'SurrealDB',
    skills = ['Rust', 'Go', 'JavaScript'];

-- Returns just a single record
CREATE ONLY person:tobie SET
    name = 'Tobie',
    company = 'SurrealDB',
    skills = ['Rust', 'Go', 'JavaScript'];
```

```surql title="Output"
-------- Query --------

[
	{
		company: 'SurrealDB',
		id: person:tobie,
		name: 'Tobie',
		skills: [
			'Rust',
			'Go',
			'JavaScript'
		]
	}
]

-------- Query --------

{
	company: 'SurrealDB',
	id: person:tobieagain,
	name: 'Tobie',
	skills: [
		'Rust',
		'Go',
		'JavaScript'
	]
}
```

### Return values

By default, the create statement returns the record once it has been created. To change what is returned, we can use the `RETURN` clause, specifying either `NONE`, `BEFORE`, `AFTER`, `DIFF`, or a comma-separated list of specific fields to return.

`RETURN NONE` can be useful to avoid excess output:

```surql
-- Create 10000 records but don't show any of them
CREATE |person:10000| SET age = 46, username = "john-smith" RETURN NONE;
```

`RETURN DIFF` returns the changeset diff:

```surql
CREATE person SET age = 46, username = "john-smith" RETURN DIFF;
```

```surql title="Output"
[
	[
		{
			op: 'replace',
			path: '/',
			value: {
				age: 46,
				id: person:h84x4k5kh2m6cjf1vvza,
				username: 'john-smith'
			}
		}
	]
]
```

`RETURN BEFORE` inside a `CREATE` statement is essentially a synonym for `RETURN NONE`, while `RETURN AFTER` is the default behaviour for create.

```surql
-- Will always return NONE
CREATE person SET age = 46, username = "john-smith" RETURN BEFORE;
```

```surql
-- Return the record after creation
CREATE person SET age = 46, username = "john-smith" RETURN AFTER;
```

You can also return specific fields from a created record, the value of a single field using `VALUE`, as well as ad-hoc fields to modify the output as needed.

```surql
CREATE person
    SET age = 46,
    username = "john-smith",
    interests = ['skiing', 'music']
RETURN
    age,
    interests,
    age + 1 AS age_next_year;

CREATE |person:5|
    SET age = 20
RETURN VALUE age;
```

```surql title="Output"
-------- Query --------

[
	{
		age: 46,
		age_next_year: 47,
		interests: [
			'skiing',
			'music'
		]
	}
]

-------- Query --------

[
	20,
	20,
	20,
	20,
	20
]
```

### Timeout

The `TIMEOUT` clause can be used to specify the maximum time the statement should take to execute. This is useful when you want more control such as controlling compute costs or making sure queries succeed or fail within tight latency boundaries to not have a big query queue forming.

The value for `TIMEOUT` is specified in seconds or milliseconds.

```surql
-- Query attempting to create half a million `person` records
CREATE |person:500000| SET age = 46, username = "john-smith" TIMEOUT 500ms;
```

## Implicit statement behaviour

While a number of definitions need to be in place for a `CREATE` statement to happen, SurrealDB will handle them automatically by default. This behaviour is best seen by starting a new database.

While a connection to SurrealDB via SurrealDB Studio or the [surreal sql](/docs/reference/cli/surrealdb-cli/commands/sql.md) command can include a defined namespace and database, the namespace and database names do not exist upon creation. At this point, they are only held inside the pre-defined [$session](/docs/reference/query-language/language-primitives/parameters.md#session) parameter. This can be seen through the [INFO](/docs/reference/query-language/statements/info.md) statements, which will show no definitions at all inside a new database.

```surql
INFO FOR ROOT;
INFO FOR NS;
INFO FOR DB;
RETURN $session;
```

```surql title="Output"
-------- Query --------

{
	accesses: {},
	namespaces: {},
	nodes: {},
	system: {
		available_parallelism: 0,
		cpu_usage: 0,
		load_average: [
			0,
			0,
			0
		],
		memory_allocated: 0,
		memory_usage: 0,
		physical_cores: 0,
		threads: 0
	},
	users: {}
}

-------- Query --------

{
	accesses: {},
	analyzers: {},
	apis: {},
	configs: {},
	functions: {},
	models: {},
	params: {},
	tables: {},
	users: {}
}

-------- Query --------

{
	analyzers: {},
	functions: {},
	models: {},
	params: {},
	scopes: {},
	tables: {},
	tokens: {},
	users: {}
}

-------- Query --------

{
	ac: NONE,
	db: 'sandbox',
	exp: NONE,
	id: NONE,
	ip: NONE,
	ns: 'sandbox',
	or: NONE,
	rd: NONE,
	tk: NONE
}
```

This is to allow the chance to [define](/docs/reference/query-language/statements/define/database.md) them manually, such as by including a comment.

```surql
DEFINE DATABASE my_database COMMENT "Some important info that I prefer to add manually";
```

However, once the first record is created or inserted, SurrealDB will access the session data to execute a number of definition statements for the namespace, database, and then add a definition for the desired table name in order to allow the operation to proceed.

```surql
-- Three DEFINE statements will happen to allow this operation
CREATE person;

INFO FOR ROOT;
INFO FOR NS;
INFO FOR DB;
```

```surql title="Output"
-------- Query --------

{
	accesses: {},
	namespaces: {
		sandbox: 'DEFINE NAMESPACE sandbox'
	},
	nodes: {},
	system: {
		available_parallelism: 0,
		cpu_usage: 0,
		load_average: [
			0,
			0,
			0
		],
		memory_allocated: 0,
		memory_usage: 0,
		physical_cores: 0,
		threads: 0
	},
	users: {}
}

-------- Query --------

{
	accesses: {},
	databases: {
		sandbox: 'DEFINE DATABASE sandbox'
	},
	users: {}
}

-------- Query 7 --------

{
	accesses: {},
	analyzers: {},
	apis: {},
	configs: {},
	functions: {},
	models: {},
	params: {},
	tables: {
		person: 'DEFINE TABLE person TYPE ANY SCHEMALESS PERMISSIONS NONE'
	},
	users: {}
}
```

To disallow this behaviour, you can [define a database](/docs/reference/query-language/statements/define/database.md) using the `STRICT` keyword. In strict mode, any resource must first be explicitly defined before it can be used.

```surql
ns/db> CREATE person;
[[{ id: person:epgvviec00l4pnmhtt5n }]]

ns/db> DEFINE DB strict_db STRICT;
[NONE]

ns/db> USE DB strict_db;
[NONE]

ns/strict_db> CREATE person;
["Thrown error: The table 'person' does not exist"]

ns/strict_db> DEFINE TABLE person;
[NONE]

ns/strict_db> CREATE person;
[[{ id: person:c76lfw6n4yb1z2dj9xaj }]]
```

## Learn more

To learn more about SurrealDB, check out the following resources:
- [Getting started guide](/docs)
- [Select statement](/docs/reference/query-language/statements/select.md)
- [Update statement](/docs/reference/query-language/statements/update.md)
- [Insert statement](/docs/reference/query-language/statements/insert.md)
- [Delete statement](/docs/reference/query-language/statements/delete.md)
- [Relate statement](/docs/reference/query-language/statements/relate.md)

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/access

# DEFINE ACCESS

Defining an access method allows SurrealDB to grant access to resources using different kinds of credentials.

Defining an access method allows SurrealDB to grant access to resources using different kinds of credentials.

## Requirements

- You must be authenticated as a [system user](/docs/learn/security/authentication/users.md#system-users) at the same level or higher than the level on which access is defined.

## Statement syntax

```syntax title="SurrealQL Syntax"
DEFINE ACCESS [ OVERWRITE | IF NOT EXISTS ] @name
  ON [ ROOT | NAMESPACE | DATABASE ]
  TYPE [
    JWT [ ALGORITHM @algorithm KEY @key | URL @url ]
    | RECORD
      [ SIGNUP @expression ]
      [ SIGNIN @expression ]
      [ WITH JWT
        [ ALGORITHM @algorithm KEY @key | URL @url ]
        [ WITH ISSUER KEY @key ]
      ]
      [ WITH REFRESH ]
    | BEARER FOR [ USER | RECORD ]
  [ AUTHENTICATE @expression ]
  [ DURATION
    [ FOR GRANT @duration ]
    [ FOR TOKEN @duration ]
    [ FOR SESSION @duration ]
  ]
  [ COMMENT @string ]
```

## JSON Web Token (JWT) access

A JWT access method allows accessing SurrealDB with a token signed by a trusted issuer. The contents of the token will be trusted by SurrealDB as long as it has been signed with a trusted credential.

Learn more about [JWT access method in the documentation](/docs/reference/query-language/statements/define/access/jwt.md).

## Record access

A record access method allows accessing SurrealDB as a [record user](/docs/learn/security/authentication/users.md#record-users). Record users allow SurrealDB to operate as a web database by offering mechanisms to define custom signin and signup logic as well as custom table and field permissions.

Learn more about [record access method in the documentation](/docs/reference/query-language/statements/define/access/record.md).

## Bearer access

A bearer access method allows generating bearer grants with an associated key that can be used to access SurrealDB as a specific [system user](/docs/learn/security/authentication/users.md#system-users) or [record user](/docs/learn/security/authentication/users.md#record-users). Bearer grants allow other systems and software to authenticate with SurrealDB using a secure and unique credential that can be audited and revoked at any time.

Learn more about [bearer access method in the documentation](/docs/reference/query-language/statements/define/access/bearer.md).

## Duration

The duration clause specifies the duration of the token returned after successful authentication with the access method as well as the duration of the session established both using the access method and the aforementioned token. The difference between these concepts is explained in the [expiration documentation](/docs/learn/security/authentication/users.md#expiration).

```surql
-- Create a RECORD access method for accounts
-- On successful authentication, a token expiring after 15 minutes will be returned
-- This token can be used to establish a session that will expire after 6 hours
-- The token will be automatically used to authenticate the session
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
;
```

## With `AUTHENTICATE` clause

The authenticate clause can be used to change the record identifier returned by the `SIGNIN` and `SIGNUP` clauses or replace the identifier provided in the token when authenticating `WITH JWT`, In the context of [`DEFINE ACCESS ... TYPE RECORD`](/docs/reference/query-language/statements/define/access/record.md), the `AUTHENTICATE` clause is always executed across signin, signup and token authentication.

When used in a [`DEFINE ACCESS ... TYPE JWT`](/docs/reference/query-language/statements/define/access/jwt.md), the `AUTHENTICATE` clause is used to validate the token claims and can be used to log or stop authentication attempts.

In both cases, the clause expects nothing to be returned and will otherwise fail with a generic error. The `THROW` statement can be called to return a custom error to the end user.

### Privileges inside the clause

`SIGNIN`, `SIGNUP` and `AUTHENTICATE` clauses are evaluated with a session scoped to the level the access method is defined on, and with the Editor role. An access method defined `ON DATABASE` evaluates its clauses as a Database Editor, one defined `ON NAMESPACE` as a Namespace Editor, and one defined `ON ROOT` as a Root Editor.

The clause therefore reaches only the namespace or database that owns the access method. A statement inside it that targets another namespace fails, whatever role the user who defined the access method holds.

The Editor role is a system role, so table and field `PERMISSIONS` clauses do not apply to lookups made inside these clauses. A `SELECT` against a record table behaves the same whether or not that table restricts record users.

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define an access method only if it does not already exist. If the access method already exists, the `DEFINE ACCESS` statement will return an error.

```surql
-- Create an ACCESS if it does not already exist
DEFINE ACCESS IF NOT EXISTS example ON NAMESPACE ...;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define an access method and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing access method definition. If the access method already exists, the `DEFINE ACCESS` statement will overwrite the existing access method definition with the new one.

```surql
-- Create an ACCESS and overwrite if it already exists
DEFINE ACCESS OVERWRITE example ON NAMESPACE ...;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/access/bearer

# BEARER

A bearer access method allows accessing SurrealDB using a bearer key.

A bearer access method allows generating bearer grants with an associated key that can be used to access SurrealDB as a specific [system user](/docs/learn/security/authentication/users.md#system-users) or [record user](/docs/learn/security/authentication/users.md#record-users). Bearer grants allow other systems and software to authenticate with SurrealDB using a secure and unique credential that can be [audited](/docs/reference/query-language/statements/access.md#show) and [revoked](/docs/reference/query-language/statements/access.md#revoke) at any time.

Allowing access to SurrealDB using a bearer access method requires creating grants associated with that access method. This can be done using the [`GRANT`](/docs/reference/query-language/statements/access.md#grant) clause of the [`ACCESS`](/docs/reference/query-language/statements/access.md) statement.

After creating a grant for a subject (i.e. a [system user](/docs/learn/security/authentication/users.md#system-users) or a [record user](/docs/learn/security/authentication/users.md#record-users)) with a bearer access method, a bearer key will be returned. This bearer key can be used to sign in as the subject of the grant without using its password or any other credentials. As with other credentials in SurrealDB, signing in with a bearer key will return a JWT, which can be used to perform authenticated operations or establish a persistent [authenticated session](/docs/learn/security/authentication/users.md). This makes bearer keys most suitable for automations and other service-to-service authentication use cases that require interacting with SurrealDB in an authenticated context by providing stronger security guarantees than passwords and removing the complexity of having to work with JWT directly.

## Requirements

- You must be authenticated as a [root, namespace or database user](/docs/reference/query-language/statements/define/user.md) before you can define a bearer access method.

## Statement syntax

```syntax title="SurrealQL Syntax"
DEFINE ACCESS [ OVERWRITE | IF NOT EXISTS ] @name
  ON [ NAMESPACE | DATABASE ]
  TYPE BEARER FOR [ USER | RECORD ]
  [ AUTHENTICATE @expression ]
  [ DURATION
    [ FOR GRANT @duration ]
    [ FOR TOKEN @duration ]
    [ FOR SESSION @duration ]
  ]
```

## `FOR USER`

Defining a bearer access method `FOR USER` will ensure that grants can only be created with a [system user](/docs/learn/security/authentication/users.md#system-users) as its subject. This application is useful for integrations that require administering SurrealDB at the `ROOT`, `NAMESPACE` or `DATABASE` level with the roles with which the user has been defined with [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md).

### Example

```surql
DEFINE ACCESS api ON DATABASE TYPE USER DURATION FOR GRANT 30d, FOR TOKEN 15m, FOR SESSION 12h;
```

In this example, grants created with this bearer access method will be valid for 30 days. After signing in with any of those grants, SurrealDB will return a token that will be valid for 15 minutes. This token can be used to establish an authenticated SurrealDB session valid for 12 hours. Grants created with this access method will only allow a system user as its subject.

```surql
-- Define system user that access will be granted to
DEFINE USER automation ON DATABASE PASSWORD 'secret' ROLES VIEWER;
-- Define bearer access method to generate API keys for system users
DEFINE ACCESS api ON DATABASE TYPE BEARER FOR USER DURATION FOR GRANT 10d;
-- Generate bearer grant to be used by a specific automation
ACCESS api GRANT FOR USER automation;
```

```surql title="Output"
{
	ac: 'api',
	creation: d'2025-10-07T04:52:36.157Z',
	expiration: d'2025-10-17T04:52:36.157Z',
	grant: {
		id: 'W9gi9FVexSLP',
		key: 'surreal-bearer-W9gi9FVexSLP-WFmLPW6GyFyj1gJMdEY22YzA'
	},
	id: 'W9gi9FVexSLP',
	revocation: NONE,
	subject: {
		user: 'automation'
	},
	type: 'bearer'
}
```

The key value returned in the grant object is the bearer key, which can be used to sign in as the `automation` user without using its password.

Here are some examples on how to do that using the [JavaScript SDK](/docs/reference/javascript.md) or a raw [HTTP request](/docs/reference/rest-api/http-protocol.md).

#### 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: 'api',

	// Provide the bearer key in the "key" variable
	variables: {
    		key: 'surreal-bearer-BNb2pS0GmaJz-5eTfQ5uEu8jbRb3oblqVMAt8',
	}
});
```

#### HTTP request

```bash
curl -X POST \
	-H "Accept: application/json" \
	-d '{"NS":"main", "DB":"main", "AC":"api", "key":"surreal-bearer-BNb2pS0GmaJz-5eTfQ5uEu8jbRb3oblqVMAt8"}' \
	http://localhost:8000/signin
```

## `FOR RECORD`

Defining a bearer access method `FOR RECORD` will ensure that grants can only be created with a [record user](/docs/learn/security/authentication/users.md#record-users) as its subject. This application is useful for integrations that require accessing only some data in a specific SurrealDB database and in accordance with existing `PERMISSIONS` clauses. Bearer access can only be defined `FOR RECORD` if a database is selected and using `ON DATABASE`.

```surql
-- Create record representing a user
CREATE user:1 CONTENT { name: "tobie" };
-- Define bearer access method to generate API keys for record users
DEFINE ACCESS api ON DATABASE TYPE BEARER FOR RECORD DURATION FOR GRANT 10d;
-- Generate bearer grant to be used by a specific automation belonging to 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'2025-10-07T04:54:26.258986Z',
	expiration: d'2025-10-17T04:54:26.258987Z',
	grant: {
		id: 'jeGA4jfNmnoD',
		key: 'surreal-bearer-jeGA4jfNmnoD-X2xYQ1IILzB47DttrNpMBquN'
	},
	id: 'jeGA4jfNmnoD',
	revocation: NONE,
	subject: {
		record: user:1
	},
	type: 'bearer'
};
```

The key value returned in the grant object is the bearer key, which can be used to sign in as the `user:1` record in SurrealDB.

Here are some examples on how to do that 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: 'api',

	// Provide the bearer key in the "key" variable
	variables: {
    		key: 'surreal-bearer-NJ2I2d7OXxN9-Oa5LqF36IzfURpo6Bhxy9WMF',
	}
});
```

### HTTP request

```bash
curl -X POST \
	-H "Accept: application/json" \
	-d '{"NS":"main", "DB":"main", "AC":"api", "key":"surreal-bearer-NJ2I2d7OXxN9-Oa5LqF36IzfURpo6Bhxy9WMF"}' \
	http://localhost:8000/signin
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define an access method of type BEARER only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining an access method in SurrealDB if you want to ensure that the access method is only created if it does not already exist. If the access method already exists, the `DEFINE ACCESS` statement will return an error.

It's particularly useful when you want to safely attempt to define an access method without manually checking its existence first.

```surql
-- Create a BEARER access method for the example database if it does not already exist
DEFINE ACCESS IF NOT EXISTS example ON DATABASE TYPE BEARER;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define an access method of type BEARER and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing access method definition. If the access method already exists, the `DEFINE ACCESS` statement will overwrite the existing access method definition with the new one.

```surql
-- Create a BEARER access method for the example database and overwrite if it already exists
DEFINE ACCESS OVERWRITE example ON DATABASE TYPE BEARER;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/access/jwt

# JWT

A JWT access method allows accessing SurrealDB with a token signed by a trusted issuer.

A JWT access method allows accessing SurrealDB with a token signed by a trusted issuer. The contents of the token will be trusted by SurrealDB as long as it has been signed with a trusted credential.

SurrealDB can work with third-party authentication providers such as OpenID Connect providers, OAuth providers and other trusted parties providing JWT (JSON Web Tokens, also referred to in this page as “tokens”). Let's say that your provider issues your client (e.g. a user or a service) a JWT once it has authenticated. By using the `DEFINE ACCESS ... TYPE JWT` statement, you can set the public key or shared secret that will be used to verify the authenticity of the token.

This verification is performed automatically by SurrealDB when provided with a JWT through any of its interfaces (i.e. the [HTTP REST API](/docs/reference/rest-api/http-protocol.md) through the “Authorization” header or [any of the SDKs](/docs/languages.md) through the “Authenticate” methods) before trusting the claims contained in the token and allowing SurrealQL queries to access the values of those claims.

Bear in mind that table and field permissions only apply to [record users](/docs/learn/security/authentication/users.md#record-users), which must use tokens that are verified by a `RECORD` access method. Access provided by namespace and database tokens defined in a `JWT` access method is equivalent to access from [system users](/docs/learn/security/authentication/users.md#system-users), which is above fine-grained permissions. When application users will be the ones directly authenticating with JWT, defining a `RECORD` access method `WITH JWT` is most likely the right choice.

## Requirements

- You must be authenticated as a [system user](/docs/learn/security/authentication/users.md#system-users) at the same level or higher than the level to which you want to provide JWT access.
- [You must select a namespace or database](/docs/reference/query-language/statements/use.md) before you can define a JWT access method.

## Statement syntax

```syntax title="SurrealQL Syntax"
DEFINE ACCESS [ OVERWRITE | IF NOT EXISTS ] @name
  ON [ ROOT | NAMESPACE | DATABASE ]
  TYPE JWT [ ALGORITHM @algorithm KEY @key | URL @url ]
  [ AUDIENCE @audience, .. ]
  [ AUTHENTICATE @expression ]
  [ DURATION FOR SESSION @duration ]
```

## Audience validation

_(since v3.3.0)_

The `AUDIENCE` clause lists the values accepted for a token's `aud` claim. A token verified against the access method must carry an `aud` claim that intersects the list, and is rejected otherwise.

```surql
DEFINE ACCESS token_name ON DATABASE TYPE JWT
  ALGORITHM HS512 KEY "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsqu"
  AUDIENCE "surrealdb-api";
```

Several values can be listed, which suits a token issued for more than one service, or a migration between audience names.

```surql
DEFINE ACCESS token_name ON DATABASE TYPE JWT
  ALGORITHM HS512 KEY "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsqu"
  AUDIENCE "surrealdb-api", "surrealdb-internal";
```

The clause works the same way with a remote JWKS object, where it sits before the `WITH ISSUER` clause.

```surql
DEFINE ACCESS token_name ON DATABASE TYPE JWT
  URL "https://example.com/.well-known/jwks.json"
  AUDIENCE "surrealdb-api";
```

Where the clause is omitted the `aud` claim is not checked, which is the behaviour of an access method defined without it.

> [!IMPORTANT]
> An issuer that serves more than one application typically mints tokens with a distinct `aud` value per application. Without `AUDIENCE`, a token minted for a different application by the same issuer verifies successfully here, because the signature is valid. Setting the clause confines an access method to the tokens actually intended for it.

Audience values are configuration rather than secrets, so unlike a key they are shown in full by [`INFO`](/docs/reference/query-language/statements/info.md).

```surql title="Output"
DEFINE ACCESS token_name ON DATABASE TYPE JWT ALGORITHM HS512 KEY '[REDACTED]' AUDIENCE 'surrealdb-api' WITH ISSUER KEY '[REDACTED]' DURATION FOR TOKEN 1h, FOR SESSION NONE
```

## Verification types

When defining a token, its type describes the cryptographic algorithm or specification that will be used to verify the token. This can be an HMAC algorithm, a public-key cryptography algorithm or a remote JWKS object containing all the required information to verify the token. When not specified, the type is defined as the `HS256` HMAC cryptographic algorithm.

### Hash-based message authentication code (HMAC)

With HMAC algorithms (`HS256`,`HS384`,`HS512`) the value of the defined token will be the secret used both to sign (by the issuer of the token) and verify (by SurrealDB) the token. Anyone with access to this secret will be able to issue tokens with arbitrary claims which will be trusted by SurrealDB.

The following example shows the definition of a token using an HMAC algorithm.

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Set the name of the token
DEFINE ACCESS token_name
  -- Use this token provider for database authentication
  ON DATABASE
  -- Specify the type of access being defined
  TYPE JWT
  -- Specify the cryptographic signature algorithm used to verify the token
  ALGORITHM HS512
  -- Specify the symmetric key used to sign and verify the authenticity of the token
  KEY "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsU2H2kGiOpU4GbAF1bCiXmM1b3JGgleBzz7rsrz6VvYEM4q3CLkcO8CMBIlhwhzWmy8"
;
```

### Public-key cryptography

With public-key cryptography algorithms (`EDDSA`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`, `RS256`, `RS384`, `RS512`) the value of the defined token will be the public key used to verify the signature of the token. This value is not secret and should be provided by the issuer of the tokens. Tokens will be signed using the private key, known only to the issuer. The public key value should be provided to SurrealDB including its header and footer. Any whitespace will be trimmed.

The following example shows the definition of a token using a public-key cryptography algorithm.

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Set the name of the token
DEFINE ACCESS token_name
  -- Use this token provider for database authentication
  ON DATABASE
  -- Specify the type of access being defined
  TYPE JWT
  -- Specify the cryptographic signature algorithm used to verify the token
  ALGORITHM RS256
  -- Specify the public key used to verify the authenticity of the token
  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-----"
;
```

### JSON web key set (JWKS)

With JWKS, a set of JWK (JSON Web Key) objects will be dynamically fetched from a remote location and used to verify tokens following the [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517) specification. When defining a JWKS token verification method, its value should contain a valid URL that is reachable by SurrealDB and allowed by the configured network [capabilities](/docs/learn/security/authorization/capabilities.md). This URL should point to a valid JWKS object (as described in [Section 5 of RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517#section-5)) in the form of a JSON document. This is the recommended method to integrate with authentication providers that support JWKS. Providers like [Google](https://developers.google.com/identity/openid-connect/openid-connect#discovery), [AWS Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html#amazon-cognito-user-pools-using-tokens-manually-inspect), [Azure Active Directory](https://azure.github.io/azure-workload-identity/docs/installation/self-managed-clusters/oidc-issuer/jwks.html), [Auth0](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets), [Keycloak](https://documentation.cloud-iam.com/how-to-guides/configure-remote-jkws.html) or [OneLogin](https://developers.onelogin.com/authentication/tools/jwt) provide JWKS endpoints to verify tokens issued by their services.

> [!NOTE: Before you start]
> JWKS downloads need the [network capability](/docs/learn/security/authorization/capabilities.md#network). For a public issuer, allow the JWKS hostname (for example `--allow-net example.com`). If that hostname resolves to a private or special-use address (common for in-cluster issuers), also allow the matching IP or CIDR. See [Network capabilities](/docs/learn/security/authorization/capabilities.md#network) for more details.

The following example shows the definition of a token using a JWKS.

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Set the name of the token
DEFINE ACCESS token_name
  -- Use this token provider for database authentication
  ON DATABASE
  -- Specify the type of access being defined
  TYPE JWT
  -- Specify the URL where the JWKS object can be found
  URL "https://example.com/.well-known/jwks.json"
;
```

Validating tokens generated by third-party authentication providers using JWKS ensures that keys can be revoked directly from the third-party service and will no longer be accepted by SurrealDB after the local cache for those keys expires. Likewise, it ensures that token verification will not break if keys are rotated, as any new keys will be automatically fetched from the authentication provider if a JWT is received containing a new key identifier in its `kid` header.

To avoid performing requests to the remote URL for each token that is verified, SurrealDB caches every JWKS object that it pulls for a period of 12 hours. The cache can be purged earlier (e.g. in the event a key is compromised) by restarting the SurrealDB server. If a JWT is received containing a reference to a new key identifier in its `kid` header, the JWKS object will be fetched again and updated in the cache if the key identifier is found in the remote JWKS object; this operation will only be performed once every 5 minutes to prevent malicious actors from abusing this process to perform denial of service.

## Using tokens

The `DEFINE ACCESS ... TYPE JWT` statement lets you specify the amount of permission granting authority you want to give to a token issuer. You are able to specify if the provider can grant namespace or database access to token holders. For this to work, the JWT issued to be used with SurrealDB must contain claims to specify which namespace or database the token bearer is authorised to act on.

The following claims should be added to the JWT payload by the issuer of the token:

- `exp`: The token expiration Unix time. The token will not be valid after.
- `ac`: The name of the access method used to verify the token.
- `ns`: The namespace that the token is issued for.
- `db`: The database that the token is issued for.

The names of these claims can be in all lowercase (i.e. `ac`) or all uppercase (i.e. `AC`), and can be optionally prefaced with the `https://surrealdb.com` namespace (e.g. `https://surrealdb.com/ac`) in order to separate claims directed to SurrealDB from claims directed to other services. When using a namespace, the claim name can also be used without abbreviation, such as in `https://surrealdb.com/access`, `https://surrealdb.com/database`...

The following optional claim is also processed by SurrealDB:

- `nbf`: The token acceptance Unix time. The token will not be valid before.

The expected claims depend on the level at which the token was defined:

- For tokens defined `ON ROOT`: `exp`, `ac`.
- For tokens defined `ON NAMESPACE`: `exp`, `ac`, `ns`.
- For tokens defined `ON DATABASE`: `exp`, `ac`, `ns`, `db`.

> [!NOTE]
> An `id` claim is **not** required for `TYPE JWT`. That claim identifies a [record user](/docs/learn/security/authentication/users.md#record-users) and belongs to [`DEFINE ACCESS ... TYPE RECORD ... WITH JWT`](/docs/reference/query-language/statements/define/access/record.md#with-json-web-token). A `TYPE JWT` token without `id` authenticates as a [system user](/docs/learn/security/authentication/users.md#system-users) session.

For tokens defined for [system users](/docs/learn/security/authentication/users.md#system-users), the optional `rl` claim containing an array of capitalized [system user roles](/docs/reference/query-language/statements/define/user.md#roles) (e.g. `["Viewer", "Editor", "Owner"]`) can be provided. Doing so will apply the access policy for those roles to any action made using the token. By default, sessions established with tokens without the `rl` claim will only have the `Viewer` role.

When calling any of the SurrealDB interfaces using a JWT, SurrealQL queries will gain access to the claims in the token through the `$token` variable. For example, if the token contains custom claims such as “name” or “email”, the values of those claims will be accessible through `$token.name` and `$token.email`.

The signature of the token is verified with method defined when creating the token. If the signature of the token is invalid, calls to SurrealDB interfaces using that token will fail.

### Root

Root tokens can be used to select, create, update, and delete on all tables in all databases of all namespaces, as well as to define and remove namespaces and databases from the SurrealDB instance.

```surql
-- Set the name of the token
DEFINE ACCESS token_name
  -- Use this token provider for root authentication
  ON ROOT
  -- Specify the type of access being defined
  TYPE JWT
  -- Specify the cryptographic signature algorithm used to verify the token
  ALGORITHM HS512
  -- Specify the symmetric key used to sign and verify the authenticity of the token
  KEY "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsU2H2kGiOpU4GbAF1bCiXmM1b3JGgleBzz7rsrz6VvYEM4q3CLkcO8CMBIlhwhzWmy8"
;
```
The root token payload should at least include the following claims when used to authenticate with SurrealDB.

```json title="JWT Payload"
{
  "exp": 2147483647,
  "ac": "token_name",
}
```

### Namespace

Namespace tokens can be used to select, create, update, and delete on all tables in all databases, as well as to define and remove databases and tables from the namespace.

```surql
-- Specify the namespace for the token
USE NS abcum;

-- Set the name of the token
DEFINE ACCESS token_name
  -- Use this token provider for namespace authentication
  ON NAMESPACE
  -- Specify the type of access being defined
  TYPE JWT
  -- Specify the cryptographic signature algorithm used to verify the token
  ALGORITHM HS512
  -- Specify the symmetric key used to sign and verify the authenticity of the token
  KEY "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsU2H2kGiOpU4GbAF1bCiXmM1b3JGgleBzz7rsrz6VvYEM4q3CLkcO8CMBIlhwhzWmy8"
;
```
The namespace token payload should at least include the following claims when used to authenticate with SurrealDB.

```json title="JWT Payload"
{
  "exp": 2147483647,
  "ac": "token_name",
  "ns": "abcum"
}
```

### Database

Database tokens can be used to select, create, update, and delete on all tables in a specific database, as well as to define and remove tables from the database.

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Set the name of the token
DEFINE ACCESS token_name
  -- Use this token provider for database authentication
  ON DATABASE
  -- Specify the type of access being defined
  TYPE JWT
  -- Specify the cryptographic signature algorithm used to verify the token
  ALGORITHM HS512
  -- Specify the symmetric key used to sign and verify the authenticity of the token
  KEY "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsU2H2kGiOpU4GbAF1bCiXmM1b3JGgleBzz7rsrz6VvYEM4q3CLkcO8CMBIlhwhzWmy8"
;
```

The database token payload should at least include the following claims when used to authenticate with SurrealDB.

```json title="JWT Payload"
{
  "exp": 2147483647,
  "ac": "token_name",
  "ns": "abcum",
  "db": "app_vitalsense"
}
```

## With `AUTHENTICATE` clause

The `AUTHENTICATE` clause allows you to define a custom expression that will be executed when the token is verified. This expression will be executed in the context of the token, allowing you to perform additional checks on the token claims before the token is accepted. If the expression returns any value or throws any error, the token will be rejected.

### Example: JWT user authentication with issuer and audience check

This example sets up additional token verification logic for a system user on a database using JSON Web Tokens (JWT) to authenticate. In this example, the HS512 algorithm is used to sign the token. The `AUTHENTICATE` block contains conditions to verify the token's validity: it checks that the issuer (`iss`) of the token is "surrealdb-test" and throws an error if it is not. Similarly, it checks that the audience of the token (defined in the `aud` claim, which can be provided either as an array of strings or a single string) includes "surrealdb-test" and throws an error if it does not. If both checks pass, the token is considered valid. The session duration is set to 2 hours.

```surql
DEFINE ACCESS user ON DATABASE TYPE JWT
ALGORITHM HS512 KEY "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsU2H2kGiOpU4GbAF1bCiXmM1b3JGgleBzz7rsrz6VvYEM4q3CLkcO8CMBIlhwhzWmy8"
AUTHENTICATE {
  IF $token.iss != "surrealdb-test" { THROW "Invalid token issuer" };
  IF type::is_array($token.aud) {
    IF "surrealdb-test" NOT IN $token.aud { THROW "Invalid token audience" }
  } ELSE {
    IF $token.aud IS NOT "surrealdb-test" { THROW "Invalid token audience" }
  };
}
DURATION FOR SESSION 2h;
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define an access method of type JWT only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining an access method in SurrealDB if you want to ensure that the access method is only created if it does not already exist. If the access method already exists, the `DEFINE ACCESS` statement will return an error.

It's particularly useful when you want to safely attempt to define an access method without manually checking its existence first.

```surql
-- Create a JWT access method for the example database if it does not already exist
DEFINE ACCESS IF NOT EXISTS example ON DATABASE TYPE JWT ALGORITHM HS512 KEY
"sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsU2H2kGiOpU4GbAF1bCiXmM1b3JGgleBzz7rsrz6VvYEM4q3CLkcO8CMBIlhwhzWmy8";
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define an access method of type JWT and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing access method definition. If the access method already exists, the `DEFINE ACCESS` statement will overwrite the existing access method definition with the new one.

```surql
-- Create a JWT access method for the example database and overwrite it if it already exists
DEFINE ACCESS OVERWRITE example ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret';
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/access/record

# RECORD

A record access method allows accessing SurrealDB as a record user.

A record access method allows accessing SurrealDB as a [record user](/docs/learn/security/authentication/users.md#record-users).

Record users allow SurrealDB to operate as a web database by offering mechanisms to define custom signin and signup logic as well as custom table and field permissions.

## Requirements

- You must be authenticated as a [root, namespace or database user](/docs/reference/query-language/statements/define/user.md) before you can define a record access method.
- [You must select your namespace and database](/docs/reference/query-language/statements/use.md) before you can define a record access method.

## Statement syntax

```syntax title="SurrealQL Syntax"
DEFINE ACCESS [ OVERWRITE | IF NOT EXISTS ] @name
  ON DATABASE TYPE RECORD
    [ SIGNUP @expression ]
    [ SIGNIN @expression ]
    [ WITH JWT
      [ ALGORITHM @algorithm KEY @key | URL @url ]
      [ AUDIENCE @audience, .. ]
      [ WITH ISSUER KEY @key ]
    ]
    [ WITH REFRESH ]
  [ AUTHENTICATE @expression ]
  [ DURATION
    [ FOR TOKEN @duration ]
    [ FOR SESSION @duration ]
  ]
```

## Example usage

Below shows how you can define record access using the `DEFINE ACCESS ... TYPE RECORD` statement.

```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 15m, FOR SESSION 12h
;
```

### With JSON web token

Successful authentication with a record access method results in SurrealDB generating a JSON Web Token (JWT or, in the context of SurrealDB, just "token") that can be used until its expiration to authenticate as the record user without the need of providing any additional credentials. These tokens can also be issued by third parties and trusted by SurrealDB in order to allow for the authentication process to take place outside of SurrealDB, while the resulting access claims can be provided to SurrealDB inside of a token that it can trust. This feature is provided by the `WITH JWT` clause, which behaves similarly to [the JWT access method](/docs/reference/query-language/statements/define/access/jwt.md), including the [`AUDIENCE`](/docs/reference/query-language/statements/define/access/jwt.md#audience-validation) clause for restricting which `aud` claim values are accepted.

Since the origin of the claims in the JWT is verified, those claims can be used within SurrealQL in order to provide table and field authorization through an external authenticator using OpenID Connect, OAuth or simply acting as a trusted issuer of a JWT. This can be done by leveraging table permissions to allow or disallow access depending on the values of the claims in the verified token. For example, these claims can be compared with the records in a table to only return those matching certain criteria.

Bear in mind that table and field permissions only apply to [record users](/docs/learn/security/authentication/users.md#record-users), which must use tokens that are verified by a `RECORD` access method. Access provided by namespace and database tokens defined in a `JWT` access method is equivalent to access from [system users](/docs/learn/security/authentication/users.md#system-users), which is above fine-grained permissions. When application users will be the ones directly authenticating with JWT, defining a `RECORD` access method `WITH JWT` is most likely the right choice.

Reference [the JWT access method](/docs/reference/query-language/statements/define/access/jwt.md) documentation for additional information about how JWT tokens can be used in SurrealDB, including verification through [JWKS](/docs/reference/query-language/statements/define/access/jwt.md#json-web-key-set-jwks).

The following example shows how record access with a token can be used to grant authorization either by verifying that the `id` claim in the token (which is used to populate the [`$auth`](/docs/reference/query-language/language-primitives/parameters.md#auth) reserved parameter) matches the record that is being queried from the `user` table or if the `privileged` claim is set to `true` in the token:

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

DEFINE ACCESS token_name 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-----";

DEFINE TABLE user SCHEMAFULL
  -- Authorized users can select, update, delete and create user records
  PERMISSIONS FOR select, update, delete, create
  -- The access method must be "users"
  WHERE $access = "users"
  -- The record of the user being queried must match the one identified in the token
  -- Only matching records will be changed or returned
  AND id = $auth.id
  -- Allow privileged tokens to query any user
  OR $token.privileged = true
;
```

You may also use permissions clauses to perform additional verification on other JWT claims that may be required or recommended by the provider of the token, such as verifying that the `iss` claim matches a specific principal using `$token.iss`. However, this kind of logic may be better suited for the [`AUTHENTICATE`](#with-authenticate-clause) clause, which is only executed when the token is validated before an authenticated session is established instead of in every query and for each record.

The token payload should at least include the following claims when used to authenticate as a record user in SurrealDB.

```json title="JWT Payload"
{
  "exp": 2147483647,
  "ns": "abcum",
  "db": "app_vitalsense",
  "ac": "users",
  "id": "user:1"
}
```

> [!IMPORTANT]
> For `TYPE RECORD ... WITH JWT`, the `id` claim must identify the record user, unless an [`AUTHENTICATE`](#with-authenticate-clause) clause resolves that record from other claims (for example an email address). This differs from [`TYPE JWT`](/docs/reference/query-language/statements/define/access/jwt.md#using-tokens), where `id` is not required and the session is a system user.

When the `id` claim is present in the token, the fields of the record matching the identifier specified will be accessible through the `$auth` reserved parameter. For example, if the value of the `id` claim is `user:73q1bl039y6k8z80v55d`, and user records have fields such as “name” or “email”, then `$auth.name` and `$auth.email` can be used to access those values for the `user:73q1bl039y6k8z80v55d` record specifically, without them being present in the JWT.

#### With issuer

When explicitly defining a way to verify tokens for record access, it is also possible to customise how these tokens are issued by SurrealDB. This allows specifying the algorithm and the signing key, which otherwise default to the HS512 algorithm with a randomly generated 128-character alphanumeric key. Configuring a record access method to sign tokens with specific signing credentials allows third party services to trust tokens issued by SurrealDB by trusting those signing credentials. In this way, an external service may rely on the signup and signin logic that has been implemented for record users in SurrealDB for its own authentication.

Currently, the algorithm for the issuer and the verifier are required to match. For this reason, the issuer algorithm can be omitted if it has already been defined in the `WITH JWT` clause. Likewise, an issuer does not need to be explicitly defined in the case where a key to verify JWT using a symmetric algorithm has already been defined in the `WITH JWT` clause, as the same key will also be used to sign the tokens.

The following is an example of defining a record access method that can issue tokens with an asymmetric key pair:

```surql
DEFINE ACCESS token_name ON DATABASE TYPE RECORD WITH JWT
ALGORITHM RS256
  KEY "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
mwIDAQAB
-----END PUBLIC KEY-----"
  WITH ISSUER KEY "-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj
MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu
NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ
qgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulg
p2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlR
ZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwi
VuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskV
laAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8
sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83H
mQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwY
dgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cw
ta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQ
DM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2T
N0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t
0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPv
t8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDU
AhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk
48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISL
DY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnK
xt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEA
mNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh
2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfz
et6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhr
VBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicD
TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc
dn/RsYEONbwQSjIfMPkvxF+8HQ==
-----END PRIVATE KEY-----"
;
```

The issuer is implicitly defined when using a symmetric algorithm in `WITH JWT`:

```surql
DEFINE ACCESS token_name ON DATABASE TYPE RECORD WITH JWT
-- Symmetric algorithm with a symmetric key
-- The same key is used to sign and verify
ALGORITHM HS512 KEY "secret";
-- The following clause is implicit:
-- WITH ISSUER ALGORITHM HS512 KEY "secret"
```

### With refresh token

> [!CAUTION]
> Currently, the `WITH REFRESH` clause is an experimental feature intended to be used for validating its suitability and security. As such, it may be subject to breaking changes and may present unidentified security issues. Do not rely on this feature in production applications.

> [!NOTE]
> Due to changes required in the RPC API and the SDKs, refresh tokens are currently only available when signing up and in through the [HTTP REST API](/docs/reference/rest-api/http-protocol.md).

Defining a record access method `WITH REFRESH` will result in an additional [bearer key](/docs/reference/query-language/statements/define/access/bearer.md) for the record user being returned after successful authentication with the access method. This bearer key is intended to be used as a "refresh token", which is a concept commonly found in standards such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749#section-1.5).

Unlike authentication tokens (i.e. JWT), refresh tokens (i.e. bearer keys) feature randomly generated opaque strings that contain no authentication information by themselves, but rather a pointer to an access grant that is stored in the datastore. Also unlike authentication tokens, bearer keys such as refresh tokens can be [audited](/docs/reference/query-language/statements/access/#show) and [revoked](/docs/reference/query-language/statements/access/#revoke) using the [`ACCESS`](/docs/reference/query-language/statements/access.md) statement. Refresh tokens are automatically revoked and replaced by a new refresh token whenever used to obtain an authentication token, reducing the time window for exploiting a compromised refresh token. These additional security guarantees allow refresh tokens to be longer-lived than authentication tokens, which in turn encourages making the original authentication tokens as short-lived as technically possible.

By default, refresh tokens will expire after 30 days. However, their duration can be configured using the `DURATION FOR GRANT` clause, which will accept any duration. If set to `NONE`, refresh tokens will never expire. It is strongly recommended to set some expiration for refresh tokens to minimize the potential impact of credential stealing attacks.

Because refresh tokens can be used to indefinitely keep a user authenticated with SurrealDB as long as they are exchanged for a new fresh token before they expire, [special care](/docs/learn/security/best-practices/security-best-practices.md#token-storage) should be taken when storing and applications using them should be suitably protected from attacks.

Like other bearer keys, all refresh tokens are stored in the datastore even after they are expired or revoked. This means that using refresh tokens will have a space cost in addition to the performance cost of retrieving and verifying them against the datastore. Refresh tokens are intended to be used only to obtain a new authentication token after the existing one expires and applications should only use them when necessary, such as after receiving a [token expiration error](/docs/learn/security/best-practices/troubleshooting.md#token-expired-error). For certain high volume applications, you may want to regularly [purge](/docs/reference/query-language/statements/access/#purge) expired refresh tokens to minimize the space used by inactive refresh tokens.

For more information on how to manage existing refresh tokens, see the [`ACCESS`](/docs/reference/query-language/statements/access.md) statement.

#### Example: Signing in with a refresh token

Define a record access method `WITH REFRESH`:

```surql
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) )
	WITH REFRESH
	DURATION FOR GRANT 15d, FOR TOKEN 1m, FOR SESSION 12h
;
```

Sign up with a new user or sign in with an existing user via the [HTTP REST API](/docs/reference/rest-api/http-protocol.md):

```bash
curl -X POST \
	-H "Accept: application/json" \
	-d '{"NS":"main", "DB":"main", "AC":"user", "name":"John Doe", "email":"john.doe@example.com", "pass":"VerySecurePassword!"}' \
	http://localhost:8000/signup
```

```json title="Output"
{
	"code":200,
	"details":"Authentication succeeded",
	"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MzQ1MTkyODIsIm5iZiI6MTczNDUxOTI4MiwiZXhwIjoxNzM0NTE5MzQyLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiJiYzQ3MzhkOS0zMTM3LTQ1ZjMtOGUzMy1jMmJmODI0MzZlZTciLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJBQyI6InVzZXIiLCJJRCI6InVzZXI6dHZ2NWVreXNscjBsb21sNHp4aTkifQ.liEvoYuxk9EgzqBE5MzyG2IaJTJxazz-aD9vqWPGc5AGL2u0H0gggjX3jpcaBAIyU356wxNaxFrvCoTqaA4Vrg",
	"refresh":"surreal-refresh-UgYUNmB3FR8t-zdTZlFNuvdoWOtKe0Aqb1laH"
}
```

Sign in with the refresh token to obtain a new token and refresh token:

```bash
curl -X POST \
	-H "Accept: application/json" \
	-d '{"NS":"main", "DB":"main", "AC":"user", "refresh":"surreal-refresh-UgYUNmB3FR8t-zdTZlFNuvdoWOtKe0Aqb1laH"}' \
	http://localhost:8000/signin
```

```json title="Output"
{
	"code":200,
	"details":"Authentication succeeded",
	"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MzQ1MTkzNzcsIm5iZiI6MTczNDUxOTM3NywiZXhwIjoxNzM0NTE5NDM3LCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiJjN2Q1YjgzYi0yMjJjLTQ2ODYtYjgzYi01ZWVlNDQ5Njk5YmUiLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJBQyI6InVzZXIiLCJJRCI6InVzZXI6dHZ2NWVreXNscjBsb21sNHp4aTkifQ.usM8aMtqAftJcwhUMdmqskr-k58ARF-KbmCYEQuDoGb5PlhVJDwYEwCb0oV8B85MJPvbKlC6HuFKW2wq6-AY9g",
	"refresh":"surreal-refresh-MPKzHBtznxMa-pFJj2Doj2IRHApIzGmeOAcYo"
}
```

### With `AUTHENTICATE` clause

In the context of `DEFINE ACCESS ... TYPE RECORD`, the authenticate clause can be used to change the record identifier returned by the `SIGNIN` and `SIGNUP` clauses or replace the identifier provided in the token when authenticating `WITH JWT`.

This clause can also be used to log or stop authentication attempts from record users, as it is always executed across signin, signup and token authentication.

Unlike the [`PERMISSIONS`](/docs/reference/query-language/statements/define/table.md#defining-permissions) clause, the `AUTHENTICATE` clause is executed only at the time of authentication, resulting in increased performance for queries that only need to be validated at that point.

On the other hand, permissions queries are executed in every query and for each record, ensuring that any authorization conditions are verified at the time of the query. The `AUTHENTICATE` clause is a good fit for validating specific conditions that are not expected to change during the lifetime of the session such as the presence of any required token claims.

#### Example: External authentication providers

Replacing the record identifier that will be used to establish the session is especially useful in scenarios where the token used to authenticate the session does not contain one. This is common when using an external authentication provider, which may only have knowledge of generic user identifiers such as an email address or UUID.

In the below example, we check if the session may already be tied to an existing user by using the `$auth` reserved parameter, which contains the record identifier of the authenticated user. If we can select the `id` field from `$auth`, it means that the token already contained the `id` for a record that exists in the database. If that is not the case, we can check if the token contains a different claim that we can rely on to uniquely identifies users. In this case we check for an email address. If the `email` claim is present in the token, we try to retrieve the user from the `user` table by their email address. If none of the queries return a record, the `AUTHENTICATE` clause will fail with a generic error. You can also choose to `THROW` a custom error as shown in the next example.

```surql
DEFINE ACCESS user ON DATABASE TYPE RECORD
    WITH JWT ALGORITHM HS512 KEY 'secret'
    AUTHENTICATE {
        IF $auth.id {
            RETURN $auth.id;
        } ELSE IF $token.email {
            RETURN SELECT * FROM user WHERE email = $token.email;
        };
    }
;
```

#### Example: Failing authentication

Because the `AUTHENTICATE` clause is always executed across signin, signup and token authentication, it is in a unique position to centralise logic after user credentials are deemed valid, but before the user is completely authenticated.

Below, we show an example of validating if a user is enabled. If this is not the case, we can `THROW` an error stating why the user cannot authenticate, stopping the authentication attempt with a custom message. You can also choose to not return anything, which results in a generic authentication error. If the user is enabled, we can `RETURN` the record identifier, which confirms that authentication is successful and specifies that the record user which will be authenticated in the session is the same that was already authenticated.

```surql
DEFINE ACCESS user ON DATABASE TYPE RECORD
    SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass), enabled = true )
    SIGNIN ( SELECT * FROM user WHERE email = $email
      AND crypto::argon2::compare(pass, $pass) )
    AUTHENTICATE {
        IF !$auth.enabled {
            THROW "This user is not enabled";
        };

        RETURN $auth;
    }
;
```

#### Example: Auditing and revoking tokens

In addition to what is shown in the previous example, the `AUTHENTICATE` clause can also be used to create records and access the claims found in the token itself using the `$token` reserved parameter. These features can be combined to log authentication attempts and stop authentication from completing if some conditions are met.

Below, we show a proof of concept example that leverages the fact that tokens issued by SurrealDB have the standard `jti` claim, which contains a randomly generated unique identifier for the token. This value can be used to uniquely identify each token that is issued by SurrealDB for the purposes of auditing and revocation.

In this example, we create a new record in the `token` table for each token that SurrealDB issues after a successful `SIGNIN` and `SIGNUP`. This record is identified by the value in the `jti`(JWT ID) claim. Every time that a token is used to authenticate, we check if the record in the `token` table with identifier matching the `jti`(JWT ID) claim has been revoked and, if so, we fail authentication with a custom message. Otherwise, we log the time that the token was used to successfully authenticate in the `audit` table and continue authentication without changes.

```surql
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) )
    AUTHENTICATE {
        IF type::record("token", $token.jti).revoked = true {
            THROW "This token has been revoked";
        };
        INSERT INTO token { id: $token.jti, exp: $token.exp, revoked: false };
        CREATE audit CONTENT { token: $token.jti, time: time::now() };
        RETURN $auth;
    }
    DURATION FOR TOKEN 30d, FOR SESSION 1h
;
```

## Token and session duration

_(since v3.2.0)_

Record-access authentication tokens are issued to end users and may be passed to third parties, so they **must** expire. As a result, `DURATION FOR TOKEN NONE` is rejected on `DEFINE ACCESS` and `ALTER ACCESS` for `TYPE RECORD` access methods, including when `NONE` would be the result of clearing the duration with `ALTER ACCESS … DURATION FOR TOKEN NONE`.

For record access, be sure to use a finite `DURATION FOR TOKEN` (for example `15m` or `1h`) and keep session duration separate via `FOR SESSION`.

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define an access method of type RECORD only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining an access method in SurrealDB if you want to ensure that the access method is only created if it does not already exist. If the access method already exists, the `DEFINE ACCESS` statement will return an error.

It's particularly useful when you want to safely attempt to define an access method without manually checking its existence first.

```surql
-- Create a RECORD access method for the example database if it does not already exist
DEFINE ACCESS IF NOT EXISTS example ON DATABASE TYPE RECORD;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define an access method of type RECORD and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing access method definition. If the access method already exists, the `DEFINE ACCESS` statement will overwrite the existing access method definition with the new one.

```surql
-- Create a RECORD access method for the example database and overwrite if it already exists
DEFINE ACCESS OVERWRITE example ON DATABASE TYPE RECORD;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/analyzer

# DEFINE ANALYZER

In the context of a database, an analyzer plays a crucial role in text processing and searching. It is defined by its name, a set of tokenizers, and a collection of filters.

> [!NOTE]
> Before SurrealDB version 3.0.0, the `FULLTEXT ANALYZER` clause used the syntax `SEARCH ANALYZER`.

In the context of a database, an analyzer plays a crucial role in text processing and searching. It is defined by its name, a set of tokenizers, and a collection of filters.

The output of an analyzer can be experimented with by using the [`search::analyze()`](/docs/reference/query-language/functions/database-functions/search.md#searchanalyze) function.

## Requirements
- You must be authenticated as a root, namespace, or database user before you can use the `DEFINE ANALYZER` statement.
- [You must select your namespace and database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE ANALYZER` statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE ANALYZER [ OVERWRITE | IF NOT EXISTS ] @name [ FUNCTION 
  @function ] [ TOKENIZERS @tokenizers ] [ FILTERS @filters ] [ 
  COMMENT @string ]
```

## The `FUNCTION` clause

The `FUNCTION` clause runs a preprocessing step on the initial input before tokenizers and filters run. The reference must be a **function path** (not a call - omit parentheses), and the function must take and return a `string`.

You can use either:

* a [`fn::`](/docs/reference/query-language/statements/define/function.md) user-defined function defined with `DEFINE FUNCTION`, or
* a [`mod::`](/docs/reference/query-language/statements/define/module.md) function from a Surrealism [extension module](/docs/learn/extensions/plugins/overview.md) defined with `DEFINE MODULE` (requires the [`surrealism` experimental capability](/docs/reference/cli/surrealdb-cli/commands/start.md#experimental-capabilities)).

```surql
DEFINE FUNCTION fn::backwardsify($input: string) -> string {
    $input.split('').fold('', |$a, $b| $b + $a);
};

DEFINE ANALYZER backwards FUNCTION fn::backwardsify TOKENIZERS blank;

search::analyze("backwards", "I like SurrealDB");
```

A Surrealism module function works the same way once the module is registered:

```surql
DEFINE ANALYZER custom FUNCTION mod::demo::alter_string TOKENIZERS class;
```

```surql title="Output"
[
	'BDlaerruS',
	'ekil',
	'I'
]
```

## Tokenizers

Tokenizers are responsible for breaking down a given text into individual tokens based on a set of instructions. There are a couple of tokenizers that can be used while defining an analyzer as seen below:

### `blank`

The blank tokenizer breaks down a text into tokens by creating a new token each time it encounters a space, tab, or newline character. It's a straightforward way to split text into words or chunks based on whitespace.

```surql
DEFINE ANALYZER example_blank TOKENIZERS blank;
search::analyze("example_blank", "hello world");
```

```surql title="Output"
[
	'hello',
	'world'
]
```

### `camel`

The camel tokenizer is used for identifying and creating tokens when the next character in the text is uppercase. This is particularly useful for processing camelCase or PascalCase text, common in programming, to split them into meaningful words.

```surql
DEFINE ANALYZER example_camel TOKENIZERS camel;
search::analyze("example_camel", "helloWorld");
```

```surql title="Output"
[
	'hello',
	'World'
]
```

### `class`

The class tokenizer segments text into tokens by detecting changes (digit, letter, punctuation, blank) in the Unicode class of characters. It creates a new token when the character class changes, distinguishing between digits, letters, punctuation, and blanks. This allows for flexible tokenization based on character types.

```surql
DEFINE ANALYZER example_class TOKENIZERS class;
search::analyze("example_class", "123abc!XYZ");
```

```surql title="Output"
[
	'123',
	'abc',
	'!',
	'XYZ'
]
```

### `punct`

The punct tokenizer generates tokens by breaking the text whenever a punctuation character is encountered. It's suitable for tokenizing sentences or breaking text into smaller units based on punctuation marks.

```surql
DEFINE ANALYZER example_punct TOKENIZERS punct;
search::analyze("example_punct", "Hello, World!");
```

```surql title="Output"
[
	'Hello',
	',',
	'World',
	'!'
]
```

## Filters

Filters take on the task of transforming these tokens for further processing and analysis.

### `ascii`

The ascii filter is responsible for processing tokens by replacing or removing diacritical marks (accents and special characters) from the text. It helps standardize text by converting accented characters to their basic ASCII equivalents, making it more suitable for various text analysis tasks.

```surql
DEFINE ANALYZER example_ascii TOKENIZERS class FILTERS ascii;
search::analyze("example_ascii", "résumé café");
```

```surql title="Output"
[
	'resume',
	'cafe'
]
```

### `lowercase`

The lowercase filter converts tokens to lowercase, ensuring that text is consistently in lowercase format. This is often used to make text case-insensitive for search and analysis purposes.

```surql
DEFINE ANALYZER example_lowercase TOKENIZERS class FILTERS lowercase;
search::analyze("example_lowercase", "Hello World");
```

```surql title="Output"
[
	'hello',
	'world'
]
```

### `uppercase`

The uppercase filter converts tokens to uppercase, ensuring text consistency in uppercase format. It can be useful when case-insensitivity is required for specific analysis or search operations.

For example, if you had the text **"Hello World"**, the uppercase filter would create two tokens, **["HELLO", "WORLD"]**. Below is an example of how to use the uppercase filter:

```surql
DEFINE ANALYZER example_uppercase TOKENIZERS class FILTERS uppercase;
search::analyze("example_uppercase", "Hello World");
```

```surql title="Output"
[
	'HELLO',
	'WORLD'
]
```

### `edgengram(min,max)`

The edgengram filter is used to create tokens that represent prefixes of terms. It generates a sequence of tokens that gradually build up a term, which can be useful for autocomplete or searching based on partial words. It accepts two parameters `min` and `max` which define the minimum and maximum amount of characters in the prefix.

For example, if you had the text **"apple banana"**, the edgengram filter would create six tokens, **["a", "ap", "app", "b", "ba", "ban"]**. Below is an example of how to use the edgengram filter:

```surql
DEFINE ANALYZER example_edgengram TOKENIZERS class FILTERS
  edgengram(1,3);
search::analyze("example_edgengram", "apple banana");
```

```text
[
	'a',
	'ap',
	'app',
	'b',
	'ba',
	'ban'
]
```

### `mapper(path)` {#mapperpath}

The mapping filter is designed to enable lemmatization within SurrealDB.

Lemmatization is the process of reducing words to their base or dictionary form. The mapper mechanism allows users to specify a custom dictionary file that maps terms to their base forms. This dictionary file is then used by SurrealDB’s analyzer to standardize terms as they are indexed, improving search consistency.

This is particularly useful for handling irregular verbs and other terms that the default "snowball" filter cannot handle. Lemmatization files are easy to put together and to find online, making it possible to customise full-text search for smaller languages.

#### Filesystem allowlist

A `DEFINE ANALYZER` statement with `mapper('<path>')` opens the dictionary file on the **host filesystem** when the analyzer is defined. Access is gated by [`SURREAL_FILE_ALLOWLIST`](/docs/reference/cli/surrealdb-cli/environment-variables.md#file-config), without which no paths are permitted. Set one or more directories before using `mapper()`:

**Bash**

```bash
# Colon-separated directories
SURREAL_FILE_ALLOWLIST="/var/surreal/dicts:/opt/wordlists" surreal start --user root --pass secret
```

**PowerShell**

```powershell
# Semicolon-separated directories
$env:SURREAL_FILE_ALLOWLIST = "C:\dicts;D:\wordlists"
surreal start --user root --pass secret
```

The path in `mapper()` must resolve to a file **under** an allowed directory. Paths outside the allowlist are rejected at `DEFINE ANALYZER` time.

> [!NOTE]
> This allowlist is for analyzer dictionary files only. The experimental [files](/docs/learn/schema-management/files/buckets.md) feature uses [`SURREAL_BUCKET_FOLDER_ALLOWLIST`](/docs/reference/cli/surrealdb-cli/environment-variables.md#file-config) instead.

How does the mapper work?

Configuration: In the SQL statement below, the mapper parameter is specified within the analyzer definition.
This parameter points to the file that contains the term mappings for lemmatization.

```surql
DEFINE ANALYZER lemme_english TOKENIZERS blank,class FILTERS
  lowercase,mapper( '../tests/data/lemmatization-en.txt' );

RETURN [
    search::analyze("lemme_english", "He drove and swam"),
];
```

```surql title="Output"
[
	[
		'he',
		'drive',
		'and',
		'swim'
	]
]
```

Dictionary File Structure: The file specified in the mapper parameter must follow this format:

- Each line contains a pair of terms separated by a tab.
- The first term represents the canonical (base form) of the word.
- The second term is the form to be mapped to this base form.

Example file format:

```text
drive	driven
drive	drives
drive	driving
drive	drove
swim	swam
swim	swimming
swim	swims
swim	swum
```

Usage: When this analyzer is applied to a text, any word that matches the mapped term in the dictionary file will be replaced by its base form before indexing. This helps ensure consistency in search results by consolidating different forms of a word to a single, standardized entry.

By using this custom dictionary-based mapper, you can control how irregular forms and other variations of terms are indexed,
making search behaviour more predictable and comprehensive.

The following example shows how lemmatization can be used to generate a list of words and their respective frequencies. Other notable functionalities in the example are the [`string::is_alpha()`](/docs/reference/query-language/functions/database-functions/string.md#stringis_alpha) function inside [`array::filter()`](/docs/reference/query-language/functions/database-functions/array.md#arrayfilter) to remove all non-alphabetic strings, the [`type::record()`](/docs/reference/query-language/functions/database-functions/type.md#typerecord) function to construct a record ID from two strings, and an [`UPSERT`](/docs/reference/query-language/statements/upsert.md) statement to create a record if one does not exist, or update it otherwise.

```surql
DEFINE ANALYZER lemme_english TOKENIZERS blank,class FILTERS
  lowercase,mapper( '../tests/data/lemmatization-en.txt' );

LET $text = "The Wheel of Time turns,
  and Ages come and pass,
  leaving memories that become legend. Legend fades to myth,
  and even myth is long forgotten when the Age that gave it birth comes again. In one Age,
  called the Third Age by some,
  an Age yet to come,
  an Age long past,
  a wind rose in the Mountains of Mist. The wind was not the beginning. There are neither beginnings nor endings to the turning of the Wheel of Time. But it was a beginning.";

LET $words = search::analyze("lemme_english", $text)
    .filter(|$c| $c.is_alpha());
FOR $word IN $words {
    UPSERT type::record("word", $word) SET frequency += 1;
};

SELECT * FROM word WHERE frequency >=3 ORDER BY frequency DESC;
```

```surql title="Output"
[
	{
		frequency: 8,
		id: word:the
	},
	{
		frequency: 6,
		id: word:age
	},
	{
		frequency: 4,
		id: word:a
	},
	{
		frequency: 4,
		id: word:be
	},
	{
		frequency: 4,
		id: word:of
	},
	{
		frequency: 3,
		id: word:and
	},
	{
		frequency: 3,
		id: word:come
	},
	{
		frequency: 3,
		id: word:to
	}
]
```

A mapper can also be used for ad-hoc filtering, as long as the file referenced contains two single words separated by a tab. Take the following file for example:

```title="error_filter.txt"
NOT_FOUND	File_not_found
NOT_FOUND	Datei_nicht_gefunden
NOT_FOUND	Fichier_non_trouvé
TIMEOUT	Timed_out
TIMEOUT	Délai_expiré
TIMEOUT	Zeitüberschreitung
```

An analyzer that uses a single mapper filter can then use this lemmatizer to unify multilingual error messages into a single output.

```surql
DEFINE ANALYZER error_filter FILTERS mapper('error_filter.txt');

LET $messages = 
	["File not found", "Datei nicht gefunden", "Zeitüberschreitung"]
	.map(|$word| $word.replace(' ', '_'))
	.join(' ');
search::analyze("error_filter", $messages);
```

```surql title="Output"
[
	'NOT_FOUND',
	'NOT_FOUND',
	'TIMEOUT'
]
```

Example using the same mapper to search for errors in multiple languages:

```surql
DEFINE ANALYZER error_filter FILTERS mapper('error_filter.txt');
DEFINE INDEX OVERWRITE errors
  ON TABLE error FIELDS message FULLTEXT ANALYZER error_filter;

FOR $message IN ["File not found",
  "Datei nicht gefunden",
  "Zeitüberschreitung"] {
	CREATE error SET message = $message.replace(' ',
	  '_'),
	  at = time::now();
};

SELECT * FROM error WHERE message @@ "NOT_FOUND";
```

```surql title="Output"
[
	{
		at: d'2024-11-13T03:56:12.039252Z',
		id: error:acbc044syhnx54wzs3n9,
		message: 'File_not_found'
	},
	{
		at: d'2024-11-13T03:56:12.043643Z',
		id: error:5ifxic9s750x24ts4zof,
		message: 'Datei_nicht_gefunden'
	}
]
```

### `ngram(min,max)`

The ngram filter is used to create a sequence of 'n' tokens from a given sample of text or speech. These items can be syllables, letters, words or base pairs according to the application. It accepts two parameters `min` and `max` which indicates that you want to create n-grams starting from min to size of max.

```surql
DEFINE ANALYZER example_ngram TOKENIZERS class FILTERS ngram(1,3);
search::analyze("example_ngram", "apple banana");
```

```surql title="Output"
[
	'a',
	'ap',
	'app',
	'p',
	'pp',
	'ppl',
	'p',
	'pl',
	'ple',
	'l',
	'le',
	'e',
	'b',
	'ba',
	'ban',
	'a',
	'an',
	'ana',
	'n',
	'na',
	'nan',
	'a',
	'an',
	'ana',
	'n',
	'na',
	'a'
]
```

### `snowball(language)`

The snowball filter applies Snowball stemming to tokens, reducing them to their root form and converts the case to lowercase. The following supported languages can be passed as a parameter in snowball: Arabic, Danish, Dutch, English, French, German, Greek, Hungarian, Italian, Norwegian, Portuguese, Romanian, Russian, Spanish, Swedish, Tamil, Turkish.

```surql
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?")
];
```

```surql title="Output"
[
	[
		'look',
		'at',
		'some',
		'run',
		'cat'
	],
	[
		'soll',
		'wir',
		'was',
		'trink',
		'geh',
		'?'
	]
]
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define an analyzer only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining an analyzer in SurrealDB if you want to ensure that the analyzer is only created if it does not already exist. If the analyzer already exists, the `DEFINE ANALYZER` statement will return an error.

It's particularly useful when you want to safely attempt to define a analyzer without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the analyzer definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a analyzer and overwrite an existing one if it already exists, ensuring that the latest version of the analyzer definition is always in use.

```surql
-- Create an ANALYZER if it does not already exist
DEFINE ANALYZER IF NOT EXISTS example TOKENIZERS blank;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to create an analyzer and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing analyzer definition. If the analyzer already exists, the `DEFINE ANALYZER` statement will overwrite the existing analyzer definition with the new one.

```surql
-- Create an ANALYZER and overwrite if it already exists
DEFINE ANALYZER OVERWRITE example TOKENIZERS blank;
```

## More examples

Examples on application of analyzers to indexes can be found in the documenation on [`DEFINE INDEX`](/docs/reference/query-language/statements/define/indexes.md) statement

This example creates an analyzer that tokenizes text based on the class of characters and then applies the lowercase filter to the tokens.

```surql
-- Creates a simple analyzer removing diacritics marks
DEFINE ANALYZER ascii TOKENIZERS class FILTERS lowercase,ascii;
```

This example creates an analyzer specifically designed for processing English texts.

```surql
-- Creates an analyzer suitable for English text
DEFINE ANALYZER english TOKENIZERS class FILTERS snowball(english);
```

This example creates an analyzer specifically designed for auto-completion tasks.

```surql
-- Creates an analyzer suitable for auto-completion.
DEFINE ANALYZER autocomplete FILTERS lowercase,edgengram(2,10);
```

This example creates an analyzer specifically designed for source code analysis.

```surql
-- Creates an analyzer suitable for source code analysis.
DEFINE ANALYZER code TOKENIZERS class,camel FILTERS lowercase,ascii;
```

## Removing analyzers

[`REMOVE ANALYZER`](/docs/reference/query-language/statements/remove.md) fails while any full-text index still references the analyzer. Remove or redefine those indexes first, then remove the analyzer. `REMOVE ANALYZER IF EXISTS` does not bypass this check when the analyzer is still in use.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/api

# DEFINE API

A DEFINE API statement can be used to set endpoints with custom middleware and permissions.

_(since v3.0.0)_

The `DEFINE API` statements allows a custom endpoint to be created. Each endpoint created by a `DEFINE API` statement is located at the `/api/:namespace/:database/:endpoint_name` path. For example, an endpoint for the path `get_users` for the namespace `my_namespace` and database `my_database` will have the path `/api/my_namespace/my_database/get_users`.

The response is an object with a combination of the following properties:
* `status` - A valid HTTP status code.
* `body` - Any value.
* `headers` - An object of valid header key value pairs. The value of each pair must be a string.
* `context` - An object.

A defined API has access to a preset parameter called [`$request`](/docs/reference/query-language/language-primitives/parameters.md#request) which contains the request sent by the caller of the endpoint. The $request parameter may contain values at the following fields: `body`, `headers`, `params`, `method`, `query`, and `context`.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE API [ OVERWRITE | IF NOT EXISTS ] @endpoint
    [ FOR @HTTP_method, .. ]
    [ MIDDLEWARE @function, .. ]
    [ THEN { @value } ]
    [ PERMISSIONS [ NONE | FULL | @expression ]
```

`DEFINE API` is often used in conjunction with a [capabilities flag](/docs/learn/security/authorization/capabilities.md) or [environment variable](/docs/reference/cli/surrealdb-cli/environment-variables.md) to disable arbitrary queries, thereby forcing record and anonymous users to interact with the database via API endpoints alone.

## Quick example

```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)
                }
            };
        };
```

An API endpoint can be tested using the [`api::invoke` function](/docs/reference/query-language/functions/database-functions/api.md), which takes either the path as a single string or the path along with a request body. It can also be tested via [CURL or other means by directly using the endpoint](/docs/reference/rest-api/http-protocol.md) along with the namespace and database in the headers.

```surql
api::invoke("/test");

api::invoke("/test", {
    body: {
       hi: "please",
        give: "me",
        the: "information"
    }
});
```

```surql title="Output"
-------- Query --------

{
	body: {
		request: NONE,
		response: 'The server works'
	},
    context: {},
	headers: {
		"access-control-allow-origin": '*',
		expires: '2026-01-24T02:43:50.137321Z',
		"last-modified": '2025-02-20T02:43:50.137326Z'
	},
	status: 200
}

-------- Query --------

{
	body: {
		request: {
			give: 'me',
			hi: 'please',
			the: 'information'
		},
		response: 'The server works'
	},
    context: {},
	headers: {
		"access-control-allow-origin": '*',
		expires: '2025-02-24T02:43:50.137455Z',
		"last-modified": '2026-01-20T02:43:50.137457Z'
	},
	status: 200
}
```

Each HTTP method may appear in at most one `FOR` clause on a `DEFINE API` statement. Overlapping methods - including duplicates inside a comma-separated list - are rejected at define time with a clear error instead of silently routing to only the first matching action.

```surql
DEFINE API "/foo"
  FOR get THEN $x
  FOR get THEN $y;
//- Error: duplicate `get` across FOR clauses
```

## API paths

The path of a `DEFINE API` statement can be static, such as `"/test"`, dynamic, or the remainder of a URL.

A dynamic path uses a `:` (colon) followed by a name, which will match on anything passed in at that section of a path.

```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");
```

The first two `api::invoke` calls return the output below, but the third returns nothing as `:anything_goes` only applies to a single path segment.

```surql title="Output"
{ 
    body: NONE, 
    context: {}, 
    headers: {}, 
    status: 404 
}
```

To match on the remainder of a URL, change the `:` (colon) to a `*` (star).

```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 `api::invoke` calls will now show the following output.

```surql title="Output"
{
	body: {
		some: 'data'
	},
	context: {},
	headers: {},
	status: 200
};
```

## Custom middleware

Custom middleware can be used in addition to the functions listed above.

A custom middleware function is defined in the same way as any other [user-defined function](/docs/reference/query-language/statements/define/function.md).

Each such function automatically receives two arguments:

* An object that will contain the user request.
* A function (a closure) that can be called to get the current state of the response to be returned.

The parameter names `$req` for the first and `$next` for the second are commonly used, though the actual parameter names are of no consequence.

The output of such a function must be an `object`. This is used to pass the response on to the next middleware function, or on to the actual response if there is no middleware left to call.

```surql
DEFINE FUNCTION fn::middleware_function($req: object,
  $next: function) -> object {};
```

These functions can take additional arguments on top of the required two.

```surql
DEFINE FUNCTION fn::middleware_function($req: object,
  $next: function,
  $some_string: string) -> object {};
```

### Custom middleware examples

Here is an example of an API endpoint without any middleware.

```surql
DEFINE API "/custom_response"
    FOR get
        THEN {
            {
                status: 200,
                body: {
                    num: 1
                }
            };
        };
```

Calling `api::invoke("/custom_response")` will return the following.

```surql
{
	body: {
		num: 1
	},
	context: {},
	headers: {},
	status: 200
};
```

The next example adds a middleware function that does the following:

* Calls the `$next` closure on the `$req` object. This will return the value after `THEN`: the original return value.
* Returns an object in which the `num` field inside `body` is increased by one.

```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
                }
            };
        };
```

Calling `api::invoke("/custom_response")` will now return a modified output in which `num` is equal to 2.

```surql
{
	body: {
		num: 2
	},
	context: {},
	headers: {},
	status: 200
};
```

Here is an example of the same endpoint with an extra custom middleware function which adds a `called_at` field to the `context` of the response to show when the API endpoint was called.

```surql
DEFINE FUNCTION fn::start_timer($req: object,
  $next: function,
  $called_at: datetime) -> object {
    LET $res = $next($req);
    $res + { context: { called_at: $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");
```

The output will look something like this.

```surql
{
	body: {
		num: 2
	},
	context: {
		api_called_at: d'2026-01-16T01:49:44.115351Z'
	},
	headers: {},
	status: 200
};
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/bucket

# DEFINE BUCKET

A DEFINE BUCKET statement can be used to set endpoints with custom middleware and permissions.

_(since v3.0.0)_

> [!NOTE]
> The `DEFINE BUCKET` statement is currently experimental and subject to change. To use this feature, please ensure you are on the latest supported alpha version of SurrealDB. To enable it, either pass `--allow-experimental files` when [starting the database](/docs/reference/cli/surrealdb-cli/commands/start.md) or set the `SURREAL_CAPS_ALLOW_EXPERIMENTAL` environment variable to `files`.

The `DEFINE BUCKET` statement lets you create a bucket that can hold files.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE BUCKET [ OVERWRITE | IF NOT EXISTS ] @name
  [ BACKEND @string ]
  [ READONLY ]
  [ PERMISSIONS @expression ]
  [ COMMENT @string ]
```

## Example usage

A bucket backend can be set as "memory" for non-persistent in-memory storage, or as "file:/", followed by the path, for storage on disk.

### 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();
```

```surql title="Output"
-------- Query --------

b"4F6E6365207468657265207765726520666F7572206368696C6472656E2077686F7365206E616D657320776572652050657465722C20537573616E2C2045646D756E642C20616E64204C7563792E"

-------- Query --------

'Once there were four children whose names were Peter, Susan, Edmund, and Lucy.'
```

### 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 start --user root --pass secret --allow-experimental files

# Windows (PowerShell)
$env:SURREAL_BUCKET_FOLDER_ALLOWLIST = "/" 
surreal start --user root --pass secret --allow-experimental files
```

### 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.

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/reference/query-language/statements/define/config

# DEFINE CONFIG

This statement allows you to set external configurations on the database, either for API middleware and permissions, or for how the database's tables and functions are exposed via the GraphQL API.

The `DEFINE CONFIG` statement allows you to set external configurations on your database. It can be used to configure API middleware and permissions, or to configure how the database's tables and functions are exposed via the GraphQL API.

## Requirements

- You **must** be authenticated as a **root**, **namespace**, or **database** user before you can use the `DEFINE CONFIG GRAPHQL` statement.
- You **must** select your **namespace** and **database** before you can use the `DEFINE CONFIG GRAPHQL` statement.
- You **must** define at least one table in your database for the GraphQL API to function.
- You **must** have started the SurrealDB instance with GraphQL enabled.

## Statement syntax

**SurrealQL Syntax**

```surql title="SurrealQL Syntax"
DEFINE CONFIG [ OVERWRITE | IF NOT 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
  )
```

## DEFINE CONFIG API

The `DEFINE CONFIG API` statement can be used to set middleware and permissions in order to alter the behaviour of the database for guest and [record users](/docs/reference/query-language/statements/define/access/record.md). This middleware can be used in cases such as rate limiting on the query language and setting how many resources a client can read or alter at a time.

`DEFINE CONFIG API` is often used in conjunction with a [flag](/docs/learn/security/authorization/capabilities.md) or [environment variable](/docs/reference/cli/surrealdb-cli/environment-variables.md) to disable arbitrary queries, thereby forcing record and anonymous users to interact with the database via API endpoints alone.

The following is an example of a `DEFINE CONFIG` statement that includes a timeout and a single header in the responses of all API endpoints.

```surql
DEFINE CONFIG API
    MIDDLEWARE 
        api::timeout(10s),
        api::res::headers({
            'Access-Control-Allow-Origin': '*'
        });
```

To set the actual API endpoints and their middleware, a `DEFINE API` statement is used for each endpoint. For more details and examples, see the pages for [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) and [API functions](/docs/reference/query-language/functions/database-functions/api.md).

The behaviour of API endpoints can be tested using the `api::invoke` method, or through a regular HTTP call to the endpoint that [includes the namespace and database name](/docs/reference/rest-api/http-protocol.md#custom-endpoint-at-apinsdbendpoint).

This next example uses `DEFINE CONFIG` to set a single response header, followed by two endpoints that return different output.

```surql
DEFINE CONFIG API
    MIDDLEWARE 
        api::res::headers({
            'Access-Control-Allow-Origin': '*'
        });

DEFINE API "/test" FOR get THEN {};
DEFINE API "/test2" FOR get THEN {
    {
        body: {
            some: "data"
        }
    }
};

api::invoke("/test");
api::invoke("/test2");
```

The query shows that the endpoints return a combination of the `DEFINE CONFIG` middleware and the response object set in the `DEFINE API` statements.

```surql
-------- Query --------

{
  body: NONE,
  context: {},
	headers: {
		"access-control-allow-origin": '*'
	},
	status: 200
}

-------- Query --------

{
	body: {
		some: 'data'
	},
  context: {},
	headers: {
		"access-control-allow-origin": '*'
	},
	status: 200
}
```

Note that the middleware and permissions inside individual `DEFINE API` statements will override the middleware in a `DEFINE CONFIG API` statement. In the following example, the default `10s` timeout is set to a single microsecond for the `"/test"` endpoint, giving the intended query no time to complete.

```surql
DEFINE CONFIG API
    MIDDLEWARE 
        api::timeout(10s),
        api::res::headers({
            'Access-Control-Allow-Origin': '*'
        });

DEFINE API OVERWRITE "/test"
    FOR get 
        MIDDLEWARE
            api::timeout(1µs)
        THEN {
            RETURN {
                status: 200,
                body: { 
                    data: (SELECT * FROM person),
                    however: "This will probably never return because
                      the timeout is 1 microsecond"
                }
            };
        };

api::invoke("/test");
```

```surql title="Output"
'The query was not executed because it exceeded the timeout: 1µs'
```

## DEFINE CONFIG DEFAULT

The `DEFINE CONFIG DEFAULT` statement sets the namespace and database that a connection selects when it asks for the defaults instead of naming a pair itself. The configuration is stored at the root level, so you must be authenticated as a root user to define it.

```surql
DEFINE CONFIG DEFAULT NAMESPACE my_namespace DATABASE my_database;
```

A client asks for these defaults by sending a `USE` with neither a namespace nor a database - the SDK methods `use_defaults()` in [Rust](/docs/reference/rust/methods/use-defaults.md) and the equivalent in the other SDKs. The defaults only fill in what is missing: a session that already selected a namespace, from a token for example, keeps that selection.

## DEFINE CONFIG GRAPHQL

The configuration set using the `DEFINE CONFIG GRAPHQL` is essential for enabling GraphQL functionality in your database, specifying which tables and functions should be included or excluded from the GraphQL schema.

The GraphQL configuration defined using this statement dictates how clients interact with your database through GraphQL queries and mutations.

### Important notes

- The `DEFINE CONFIG GRAPHQL` statement **must** be executed before any GraphQL queries can be made.
- If you attempt to use the GraphQL API without defining the configuration, you will receive a `NotConfigured` error.
- If no tables are defined in the database, you will receive an error stating "No tables found in database" when attempting to use the GraphQL API.

### Example usage

```surql
-- Define GraphQL configuration
DEFINE CONFIG GRAPHQL AUTO;
```

The above statement will automatically include all tables and functions in the GraphQL schema, and show up in the database as `DEFINE CONFIG GRAPHQL TABLES AUTO FUNCTIONS AUTO`.

### `TABLES` configuration

The `TABLES` clause in the `DEFINE CONFIG GRAPHQL` statement specifies how tables are exposed via GraphQL. There are four options for the `TABLES` configuration:

- `AUTO`: Automatically include all tables in the GraphQL schema.
- `NONE`: Do not include any tables in the GraphQL schema.
- `INCLUDE`: Specify a list of tables to include in the GraphQL schema.
- `EXCLUDE`: Specify a list of tables to exclude from the GraphQL schema.

#### `AUTO`

When you specify `TABLES AUTO`, all tables in the database are automatically included in the GraphQL schema.

```surql
DEFINE CONFIG GRAPHQL TABLES AUTO;
```

#### `NONE`

When you specify `TABLES NONE`, no tables are included in the GraphQL schema.

```surql
DEFINE CONFIG GRAPHQL TABLES NONE;
```

#### `INCLUDE`

You can specify a list of tables to include in the GraphQL schema using the `INCLUDE` clause. The list of tables is specified as a comma-separated list without brackets.

```surql
DEFINE CONFIG GRAPHQL TABLES INCLUDE user, post, comment;
```

> [!NOTE]
> The `EXCLUDE` option for `TABLES` is currently not implemented.

### `FUNCTIONS` configuration

The `FUNCTIONS` clause in the `DEFINE CONFIG GRAPHQL` statement specifies how functions are exposed via GraphQL. There are four options for the `FUNCTIONS` configuration:

- `AUTO`: Automatically include all functions in the GraphQL schema.
- `NONE`: Do not include any functions in the GraphQL schema.
- `INCLUDE`: Specify a list of functions to include in the GraphQL schema.
- `EXCLUDE`: Specify a list of functions to exclude from the GraphQL schema.

#### `AUTO`

When you specify `FUNCTIONS AUTO`, all functions in the database are automatically included in the GraphQL schema.

```surql
DEFINE CONFIG GRAPHQL FUNCTIONS AUTO;
```

#### `NONE`

When you specify `FUNCTIONS NONE`, no functions are included in the GraphQL schema.

```surql
DEFINE CONFIG GRAPHQL FUNCTIONS NONE;
```

#### `INCLUDE`

You can specify a list of functions to include in the GraphQL schema using the `INCLUDE` clause. The list of functions is specified as a comma-separated list enclosed in square brackets `[]`.

```surql
DEFINE CONFIG GRAPHQL FUNCTIONS INCLUDE [getUser,
  listPosts,
  searchComments];
```

#### `EXCLUDE`

You can specify a list of functions to exclude from the GraphQL schema using the `EXCLUDE` clause. The list of functions is specified as a comma-separated list enclosed in square brackets `[]`.

```surql
DEFINE CONFIG GRAPHQL FUNCTIONS EXCLUDE [debugFunction, testFunction];
```

### Using the `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define the GraphQL configuration only if it does not already exist. This is useful when you want to ensure that the configuration is only created if it does not already exist, preventing errors due to duplicate definitions.

```surql
-- Define GraphQL configuration only if it does not already exist
DEFINE CONFIG GRAPHQL IF NOT EXISTS TABLES AUTO FUNCTIONS AUTO;
```

### Using the `OVERWRITE` clause

The `OVERWRITE` clause can be used to redefine the GraphQL configuration, overwriting any existing configuration. This is useful when you want to update or modify the existing GraphQL configuration.

```surql
-- Redefine GraphQL configuration, overwriting existing configuration
DEFINE CONFIG OVERWRITE GRAPHQL TABLES INCLUDE user,
  post FUNCTIONS NONE;
```

### Examples

#### Example 1: Include specific tables and functions

This example defines a GraphQL configuration that includes specific tables and functions.

```surql
DEFINE CONFIG GRAPHQL TABLES INCLUDE user,
  post FUNCTIONS INCLUDE [getUser,
  listPosts];
```

#### Example 2: Automatically include all tables and functions

This example defines a GraphQL configuration that automatically includes all tables and functions.

```surql
DEFINE CONFIG GRAPHQL TABLES AUTO FUNCTIONS AUTO;
```

#### Example 3: Exclude specific functions

This example defines a GraphQL configuration that includes all functions except specific ones.

```surql
DEFINE CONFIG GRAPHQL FUNCTIONS EXCLUDE [debugFunction, testFunction];
```

### Error handling

#### NotConfigured Error

If you attempt to access the GraphQL endpoint without defining the GraphQL configuration, you will receive a `NotConfigured` error.

```surql title="Error output"
{
  "error": "NotConfigured: GraphQL endpoint is not configured. Please
    define the GraphQL configuration using DEFINE CONFIG GRAPHQL."
}
```

Execute the `DEFINE CONFIG GRAPHQL` statement to define the GraphQL configuration.

```surql
-- Define GraphQL configuration
DEFINE CONFIG GRAPHQL TABLES AUTO FUNCTIONS AUTO;
```

#### "No tables found" error

If you have defined the GraphQL configuration but no tables are defined in the database, you will receive an error stating "No tables found in database" when attempting to use the GraphQL API. You can fix this by defining at least one table in your database using the [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) statement.

```surql title="Error output"
{
  "error": "No tables found in database. Please define at least one
    table to use the GraphQL API."
}
```

**Solution**: Define at least one table in your database.

```surql
DEFINE TABLE foo SCHEMAFUL;
DEFINE FIELD val ON foo TYPE int;
CREATE foo:1 SET val = 42;
```

### Authentication errors when accessing data via GraphQL

**Cause**: Insufficient permissions or incorrect authentication credentials.

**Solution**: Ensure you are authenticated as a user with the necessary permissions and that the permissions on tables and fields are correctly configured.

#### Authentication and permissions

- The GraphQL API respects SurrealDB's authentication and permission model.
- You must authenticate using the appropriate credentials to access data via GraphQL.
- Permissions set on tables and fields will affect the data accessible through the GraphQL API.
- If you attempt to access data without sufficient permissions, you will receive an authentication error.

#### Example: Basic authentication

```surql
-- Define a user with access permissions
DEFINE USER my_user ON DATABASE PASSWORD 'my_password';
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 SESSION 60s, FOR TOKEN 1d;

-- Define a table with permissions
DEFINE TABLE foo SCHEMAFUL PERMISSIONS FOR select
  WHERE $auth.email = email;
DEFINE FIELD email ON foo TYPE string;
DEFINE FIELD val ON foo TYPE int;

-- Insert data
CREATE foo:1 SET val = 42, email = "user@example.com";
CREATE foo:2 SET val = 43, email = "other@example.com";
```

When querying the GraphQL API as `user@example.com`, only the records where `email = "user@example.com"` will be accessible due to the permissions set.

### Examples

#### Example 1: Defining GraphQL configuration and fetching data

```surql
-- Define GraphQL configuration to include all tables automatically
DEFINE CONFIG GRAPHQL TABLES AUTO;

-- Define a table and insert data
DEFINE TABLE foo SCHEMAFUL;
DEFINE FIELD val ON foo TYPE int;
CREATE foo:1 SET val = 42;
CREATE foo:2 SET val = 43;
```

Now, you can fetch data via GraphQL:

```graphql
query {
  foo {
    id
    val
  }
}
```

**Response:**

```json
{
  "data": {
    "foo": [
      {
        "id": "foo:1",
        "val": 42
      },
      {
        "id": "foo:2",
        "val": 43
      }
    ]
  }
}
```

#### Example 2: Including Specific Tables

```surql
-- Define GraphQL configuration to include only specific tables
DEFINE CONFIG OVERWRITE GRAPHQL TABLES INCLUDE foo;
```

When querying the GraphQL schema, only the included tables will be available.

#### Example 3: Using limit, start, order, and filter in GraphQL queries

You can use `limit`, `start`, `order`, and `filter` in your GraphQL queries to control the data returned.

##### Limit

```graphql
query {
  foo(limit: 1) {
    id
    val
  }
}
```

##### Start

```graphql
query {
  foo(start: 1) {
    id
    val
  }
}
```

##### Order

```graphql
query {
  foo(order: { desc: val }) {
    id
    val
  }
}
```

##### Filter

```graphql
query {
  foo(filter: { val: { eq: 42 } }) {
    id
    val
  }
}
```

### More information

The `DEFINE CONFIG GRAPHQL` statement is essential for enabling and configuring the GraphQL API in SurrealDB. By specifying which tables and functions are included or excluded, you can fine-tune the GraphQL schema to match your application's needs.

- **Authentication**: Ensure you are authenticated with the appropriate credentials.
- **Permissions**: Set up permissions on tables and fields to control access via GraphQL.
- **Configuration**: The GraphQL configuration must be defined before using the GraphQL API.
- **Error Handling**: Be aware of possible errors when the configuration is missing or incomplete.

> [!IMPORTANT]
> Always ensure you have the necessary permissions and have selected the appropriate namespace and database before using the `DEFINE CONFIG GRAPHQL` statement.

## Summary

- Use `DEFINE CONFIG GRAPHQL` to enable and configure the GraphQL API.
- Define your tables and set up permissions to control data access.
- Use the `OVERWRITE` and `IF NOT EXISTS` clauses as needed to manage your configuration.
- Always authenticate with appropriate credentials when accessing the GraphQL API.
- Utilize GraphQL query features like `limit`, `start`, `order`, and `filter` to control the data returned.

By following these guidelines, you can effectively use the `DEFINE CONFIG GRAPHQL` statement to configure and interact with your SurrealDB database via GraphQL.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/database

# DEFINE DATABASE

The DEFINE DATABASE statement allows you to instantiate a named database, enabling you to specify security and configuration options.

The `DEFINE DATABASE` statement allows you to instantiate a named database, enabling you to specify security and configuration options.

## Requirements

- You must be authenticated as a root owner or editor, or namespace owner or editor before you can use the `DEFINE DATABASE` statement.
- [You must select your namespace](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE DATABASE` statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE DATABASE [ OVERWRITE | IF NOT EXISTS ] @name [ STRICT ] [ COMMENT @string ]
```

## Example usage
Below shows how you can create a database using the DEFINE DATABASE statement.

```surql
-- Specify the namespace for the database
USE NS abcum;

-- Define database
DEFINE DATABASE app_vitalsense;
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define a database only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining a database in SurrealDB if you want to ensure that the database is only created if it does not already exist. If the database already exists, the `DEFINE DATABASE` statement will return an error.

It's particularly useful when you want to safely attempt to define a database without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the database definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a database and overwrite an existing one if it already exists, ensuring that the latest version of the definition is always in use.

```surql
-- Create a database if it does not already exist
DEFINE DATABASE IF NOT EXISTS app_vitalsense;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define a database and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing database definition. If the database already exists, the `DEFINE DATABASE` statement will overwrite the existing definition with the new one.

```surql
-- Create a database and overwrite if it already exists
DEFINE DATABASE OVERWRITE app_vitalsense;
```

## Defining a `STRICT` database

_(since v3.0.0)_

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/create.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/reference/query-language/statements/define/event

# DEFINE EVENT

The DEFINE EVENT statement can be used to create events which can be triggered after any change or modification to the data in a record.

Events allow you to define custom logic that is executed when a record is created, updated, or deleted. These events are triggered automatically within the current transaction after data modifications in the record, giving you access to the state of the record [before and after](/docs/reference/query-language/language-primitives/parameters.md#before-after) the change.

> [!NOTE]
> Events are a side effect of other operations and thus are not triggered when data is [imported](/docs/reference/cli/surrealdb-cli/commands/import.md).

## Key concepts

- **Events**: Triggered after changes (create, update, delete) to records in a table.
* **$event**: A preset parameter containing the type of event as a string, will always be one of "CREATE", "UPDATE", or "DELETE".
- **$before / $after**: Refer to the record state before and after the modification. Learn more about the `$before` and `$after` parameters in the [parameters documentation](/docs/reference/query-language/language-primitives/parameters.md#before-after).
- **$value**: The record in question. For a `CREATE` or `UPDATE` event, this will be the record after the changes were made. For a `DELETE` statement, this will be the record before it was deleted.
- **WHEN condition**: Determines when the event should be triggered.

## Requirements

- You must be authenticated as a root owner or editor, namespace owner or editor, or database owner or editor before you can use the `DEFINE EVENT` statement.
- [You must select your namespace and database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE EVENT` statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE EVENT [ IF NOT EXISTS | OVERWRITE ] @name ON [ TABLE ] @table
  [ ASYNC [ RETRY @retry ] [ MAXDEPTH @max_depth ] ]
  [ WHEN @condition ]
  [ THEN @action ]
  [ COMMENT @string ]
```

### Clauses:

- **OVERWRITE**: Replaces the existing event if it already exists.
- **IF NOT EXISTS**: Only creates the event if it doesn't already exist.
- **WHEN**: Conditional logic that controls whether the event is triggered. Will show up in the event definition as `WHEN true` if not specified.
- **THEN**: Specifies the action(s) to execute when the event is triggered.
- **COMMENT**: Optional comment for describing the event.
- **ASYNC**: Whether to run the event outside of the transaction that triggers it. Available since SurrealDB 3.0.0.

## 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'2024-11-25T02:59:41.003Z',
		id: log:e3thw1l0q7xiapznar1f,
		new_email: 'old_email@test.com',
		old_email: '',
		user: user:test
	},
	{
		action: 'email updated',
		at: d'2024-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'2024-11-25T02:59:41.003Z',
		id: log:mlkag8h1xotglpz9wt2i,
		new_email: '',
		old_email: 'new_email@test.com',
		user: user:test
	}
]
```

### More complex logic:

-  **Purchase Event with Multiple Actions**: 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();
    };
```

In this example:

- We perform multiple actions when a purchase is created: establishing relationships using the [RELATE](/docs/reference/query-language/statements/relate.md) statement and creating a log entry.

## 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'
	}
]
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define an event only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining an event in SurrealDB if you want to ensure that the event is only created if it does not already exist. If the event already exists, the `DEFINE EVENT` statement will return an error.

It's particularly useful when you want to safely attempt to define a event without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the event definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a event and overwrite an existing one if it already exists, ensuring that the latest version of the event definition is always in use

```surql
-- Create a EVENT if it does not already exist
DEFINE EVENT IF NOT EXISTS example ON example THEN {};
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define an event and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing event definition. If the event already exists, the `DEFINE EVENT` statement will overwrite the existing event definition with the new one.

```surql
-- Create an EVENT and overwrite if it already exists
DEFINE EVENT OVERWRITE example ON example THEN {};
```

## Events and permissions

Queries inside the event always execute without any permission checks, even when triggered by changes made by the currently authenticated user. This can be very useful to perform additional checks and changes that involve tables/records that are inaccessible for the 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
;
```

By having the following event defined, SurrealDB will perform the additional checks and changes:

```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

_(since v3.0.0)_

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

_(since v3.0.0)_

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 dependant 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/reference/query-language/statements/define/field

# DEFINE FIELD

The DEFINE FIELD statement allows you to instantiate a named field on a table, enabling you to set the field's schema and configuration.

The `DEFINE FIELD` statement allows you to instantiate a named field on a table, enabling you to set the field's data type, set a default value, apply assertions to protect data consistency, and set permissions specifying what operations can be performed on the field.

## Requirements

- You must be authenticated as a root owner or editor, namespace owner or editor, or database owner or editor before you can use the `DEFINE FIELD` statement.
- [You must select your namespace and database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE FIELD` statement.

## Statement syntax

**Regular Field Syntax**

### Regular fields

```syntax title="SurrealQL Syntax"
DEFINE FIELD [ OVERWRITE | IF NOT EXISTS ] @name ON [ TABLE ] @table
	[ TYPE @type [ FLEXIBLE ] ]
	[ REFERENCE 
		[ ON DELETE REJECT | 
			ON DELETE CASCADE | 
			ON DELETE IGNORE |
			ON DELETE UNSET | 
			ON DELETE THEN @expression ]
	]
	[ DEFAULT [ALWAYS] @expression ]
  [ READONLY ]
	[ VALUE @expression ]
	[ ASSERT @expression ]
	[ PERMISSIONS [ NONE | FULL
		| FOR select @expression
		| FOR create @expression
		| FOR update @expression
	] ]
  [ COMMENT @string ]
```

**Computed Field Syntax**

### Computed fields

_(since v3.0.0)_

> [!NOTE]
> In versions of SurrealDB before 3.0.0, `COMPUTED` fields were implemented using a data type called a `future`. Please see [the page on futures](/docs/reference/query-language/language-primitives/data-types/futures.md) in this case.

A `COMPUTED` field is one that is not stored but computed every time it is accessed. Such fields have a more limited set of clauses that can be used. Furthermore, a `COMPUTED` field cannot be defined on the `id` field of a record, nor any nested fields (i.e. a field `metadata` can be defined as computed, but not `medatata.can_drive`).

```syntax title="SurrealQL Syntax"
DEFINE FIELD [ OVERWRITE | IF NOT EXISTS ] @name ON [ TABLE ] @table
	COMPUTED @expression
	[ TYPE @type ]
	[ PERMISSIONS [ NONE | FULL
		| FOR select @expression
		| FOR create @expression
		| FOR update @expression
	] ]
  [ COMMENT @string ]
```

## 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;
```

Non-unicode fields can be defined and set using backticks where necessary. Be sure that any periods to indicate nested fields are not inside the backticks, as anything enclosed in backticks will be treated as a literal string.

```surql
DEFINE FIELD name.first    ON user TYPE string;
DEFINE FIELD `nómine`.prim ON user TYPE string;
DEFINE FIELD `nómine.prim` ON user TYPE string;

CREATE user:one SET 
	-- Nested field
    name.first = "Billy",
	-- Also nested
    `nómine`.prim = "Billy",
	-- Not nested
    `nómine.prim` = "Billy";
```

As the output shows, the `.` enclosed inside backticks in the last field results in a single non-nested field name that includes the period, while the one immediately preceding it is nested.

```surql
[
	{
		id: user:one,
		name: {
			first: 'Billy'
		},
		"nómine": {
			prim: 'Billy'
		},
		"nómine.prim": 'Billy'
	}
]
```

## Defining data types

The `DEFINE FIELD` statement allows you to set the data type of a field. For a full list of supported data types, see [Data types](/docs/reference/query-language/language-primitives/data-types.md).

When defining nested fields, if both the parent and the nested fields have types defined, those types must agree. Mismatching types are rejected to prevent impossible schema states.

For example, the following will fail:

```surql
DEFINE FIELD OVERWRITE fd ON c TYPE { a: string, b: number };
DEFINE FIELD OVERWRITE fd.* ON c TYPE number;
```

The above will fail with the following error:

```surql
'Cannot set field `fd.*` with type `number` as it mismatched with field `fd` with type `{ a: string, b: number }`'
```

### 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 diffent 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.

```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 data types

On a `SCHEMAFULL` table, every `object` is schemafull by default, meaning that only fields you define with nested `DEFINE FIELD` statements are allowed. The `FLEXIBLE` field clause relaxes that rule for a single field. It must appear immediately after `TYPE`, and it applies to **every `object` reachable in that field's type** - including objects inside `array<object>`, `option<object>`, union arms, and object literals.

`FLEXIBLE` is not part of the type expression. Writing `TYPE object FLEXIBLE` declares a field of type `object`, then sets the field's flexible flag. The same applies to `TYPE array<object> FLEXIBLE`: the stored type is `array<object>`, and each object element accepts arbitrary keys.

The field's type must contain at least one `object`. `FLEXIBLE` cannot be used with types such as `any` or `number` that do not include `object`.

```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;
```

You can still define nested fields such as `metadata.user_id`. Defined subfields keep their types and assertions; `FLEXIBLE` only allows **additional** keys that are not declared in the schema.

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.

In versions of SurrealDB before 3.0, the result of the above statement was a record in which the `metadata` field was only able to populate the `user_id` field.

```surql
{
	id: user:ke8w4u38gbm3ofp2u8fb,
	metadata: {
		user_id: 8876687
	},
  name: "User1"
}
```

As of version 3.0, the statement now returns an error upon finding the first field that was not defined in the schema.

```surql
"Found field 'metadata.age', but no such field exists for table 'user'"
```

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'
}
```

The same field clause works when objects are nested inside other types. For example, `TYPE array<object> FLEXIBLE` makes every object in the array schemaless, while nested `DEFINE FIELD` paths such as `items.*.num` still type-check declared subfields:

```surql
DEFINE TABLE test SCHEMAFULL;
DEFINE FIELD items ON test TYPE array<object> FLEXIBLE;
DEFINE FIELD items.*.num ON test TYPE number;

CREATE test:1 SET items = [{ num: 1 }];
CREATE test:2 SET items = [{ num: 2, extra: 'allowed' }];
-- Fails: 'extra' is allowed but 'num' must be a number, not a string
CREATE test:3 SET items = [{ num: '3', extra: 'allowed' }];
```

### 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` and `ALWAYS` clauses

_(since v2.2.0)_

`DEFAULT ALWAYS` applies a default on `CREATE` and on `UPDATE` when the value is `NONE`. The `ALWAYS` keyword distinguishes this from plain `DEFAULT`, which only runs on `CREATE`.

```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`, and when an existing `product` is updated if the value is 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;

```

```surql title="Query"
DEFINE TABLE post SCHEMAFULL;
DEFINE FIELD tags ON post TYPE array<object> DEFAULT ALWAYS [];
DEFINE FIELD tags.*.color ON post TYPE string DEFAULT ALWAYS 'red';
DEFINE FIELD tags.*.name ON post TYPE string;
--
CREATE post:test;
UPSERT post:test SET tags += { name: 'test' };
UPSERT post:test SET tags += { name: 'test', color: 'blue' };
```

```surql title="Output"
[{ id: post:test, tags: [] }]

[{ id: post:test, tags: [{ color: 'red', name: 'test' }] }]

[{ id: post:test, tags: [{ color: 'red', name: 'test' }, { color: 'blue', name: 'test' }] }]
```

### Using the `VALUE` clause to set a field's value

The `VALUE` clause differs from `DEFAULT` in that a default value is calculated if no other is indicated, otherwise accepting the value given in a query.

```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`](#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);
```

## Comments

A `COMMENT` documents how the field is meant to be used. Comments can be useful when describing how someone (or an agent) would write a query: comparison rules, units, allowed values, or invariants. The comment is stored with the definition and shown by [`INFO`](/docs/reference/query-language/statements/info.md). See also [Comments on definitions](/docs/reference/query-language/statements/define/overview.md#comments-on-definitions).

```surql
DEFINE FIELD time ON meeting TYPE string
	COMMENT "Meeting date as an ISO string, 'YYYY-MM-DD'. It is a string, not a datetime: compare and sort lexicographically (time >= '2026-07-10').";

DEFINE FIELD is_organizer ON attended TYPE bool
	COMMENT "True if this person ran the meeting, false if they only attended. Exactly one attendee per meeting has it true.";

DEFINE FIELD score ON review TYPE float
	COMMENT "Rating from 0.0 to 5.0 inclusive. Always assert score >= 0 AND score <= 5.";
```

## 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;
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define a field only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining a field in SurrealDB if you want to ensure that the field is only created if it does not already exist. If the field already exists, the `DEFINE FIELD` statement will return an error.

It's particularly useful when you want to safely attempt to define a field without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the field definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a field and overwrite an existing one if it already exists, ensuring that the latest version of the definition is always in use

```surql
-- Create a field if it does not already exist
DEFINE FIELD IF NOT EXISTS email ON TABLE user TYPE string;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define a field and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing field definition. If the field already exists, the `DEFINE FIELD` statement will overwrite the existing definition with the new one.

```surql
-- Overwrite the current field definition if it already exists
DEFINE FIELD OVERWRITE example ON TABLE user TYPE string;
```

## Restrictions on computed fields

_(since v3.3.0)_

A [`COMPUTED`](#restrictions-on-computed-fields) body is evaluated on every read of the field, inside the transaction of the statement that reads it. Two rules follow from that.

### A computed body must be read-only

`DEFINE FIELD` refuses a `COMPUTED` body that modifies data. A write inside a body can never succeed inside a plain `SELECT`, so the definition is rejected instead of leaving behind a field that fails on every read.

```surql
-- Refused: the body modifies data
DEFINE FIELD view_count ON article COMPUTED (UPDATE stats:articles SET views += 1);
```

The check covers the whole expression, including subqueries, blocks and closures, and it follows calls to [custom functions](/docs/reference/query-language/statements/define/function.md). A body that calls a function which writes is refused, and the error names the function.

```surql
DEFINE FUNCTION fn::record_view() -> int { CREATE view_log SET at = time::now(); RETURN 1; };

-- Refused: fn::record_view() writes
DEFINE FIELD view_count ON article COMPUTED fn::record_view();
```

The same rule holds when the function changes rather than the field: `DEFINE FUNCTION` and `ALTER FUNCTION` refuse a body that starts to write while a computed field still depends on it. See [Functions that other definitions require to stay read-only](/docs/reference/query-language/statements/define/function.md#functions-that-other-definitions-require-to-stay-read-only).

A write that cannot be resolved when the field is defined - one reached through [`eval::surql()`](/docs/reference/query-language/functions/database-functions/eval.md#evalsurql), a JavaScript function, or a closure that arrives as data - is still accepted at definition time. The write is refused when the field is read.

> [!NOTE]
> These checks are relaxed under `OPTION IMPORT`, so an [export](/docs/reference/cli/surrealdb-cli/commands/export.md) taken before the rules existed still restores.

### A computed body is capped at the definer's permissions

A `COMPUTED` body is evaluated with the reader's own authentication, narrowed so that it can never exceed the level and role of the user who defined the field. As with [`DEFINE FUNCTION`](/docs/reference/query-language/statements/define/function.md), the limit is a ceiling: it only ever removes access.

A reader with broader permissions than the definer therefore gains nothing from the field. In the example below, the body needs a root-level identity, so it is refused under the definer's database-level Editor ceiling however privileged the reader is.

```surql
-- Defined by a database-level Editor
DEFINE FIELD server_info ON article COMPUTED (INFO FOR ROOT);

-- Selected by a root Owner: the body is still capped at database-level Editor
SELECT server_info FROM article;
```

The reverse does not hold, and a computed field is not a way to grant access. A reader with narrower permissions than the definer keeps their own: the body runs as them, and every permission that applies to their own queries applies inside it.

```surql
-- Defined by a root user
DEFINE TABLE audit_log SCHEMALESS PERMISSIONS NONE;
DEFINE TABLE article SCHEMALESS PERMISSIONS FULL;
DEFINE FIELD recent_audits ON article COMPUTED (SELECT * FROM audit_log);

-- Selected by a record user
SELECT id, title, recent_audits FROM article;
```

The record user can read `article`, so the row is returned. They cannot read `audit_log`, so the computed field is empty rather than exposing the table.

```surql title="Output"
[
	{
		id: article:1,
		recent_audits: [],
		title: 'Hello'
	}
]
```

> [!NOTE]
> For record and anonymous readers the narrowing is skipped altogether, because a system user's ceiling cannot narrow them any further. Auth limiting never escalates the reader - see [Capabilities](/docs/learn/security/authorization/capabilities.md).

### A computed field's own select permission is enforced everywhere

`PERMISSIONS FOR select` on a `COMPUTED` field applies wherever the field is read, not only in a `SELECT` projection. That includes the pre-mutation image an `UPDATE` reads, a `WHERE` condition, a write statement's `RETURN` list, and `RETURN DIFF`.

```surql
DEFINE TABLE item PERMISSIONS FOR select, update FULL;
DEFINE FIELD hidden ON item COMPUTED 'secret' PERMISSIONS FOR select NONE;
DEFINE FIELD leak ON item TYPE any PERMISSIONS FOR select, update FULL;

-- As a record user, both read the field as NONE
SELECT * FROM item:1;
UPDATE item:1 SET leak = hidden RETURN leak;
```

> [!WARNING]
> Before SurrealDB 3.3.0, a caller allowed to update a record could read a computed field denied to them and copy it into a field they were allowed to select, as in the `UPDATE` above. If you relied on a computed field's select permission to hide a value, check whether any writable field on the same table was used to copy it out.

### Computed fields and array elements in reduced results

Field-level select permissions narrow a record before it is returned. Before 3.3.0 that pass could remove whole elements of an array field rather than narrowing them, which also made `RETURN DIFF` come back empty for every session that was not the record's owner. Both now return the expected values.

## Field evaluation order

_(since v3.3.0)_

When a record is written, field clauses run in **dependency order**: a field whose clause reads another field is evaluated after that field. `DEFAULT`, `VALUE`, and `COMPUTED` all take part, because each produces a value another field may need. Fields that do not read each other are evaluated in the order they were defined.

```surql
DEFINE FIELD z_src ON v TYPE int VALUE 10;
DEFINE FIELD a_dep ON v TYPE int VALUE z_src + 1;

CREATE v:1;
-- a_dep is 11
```

> [!NOTE]
> Before 3.3.0, fields were evaluated in name order. The example above failed because `a_dep` ran before `z_src` had a value, which meant that renaming a field could break a working schema. Nested fields are still evaluated after their parent.

`ASSERT` is different, because it produces no value and so imposes no order. Assertions run in a second pass, once every field holds its final value, so a clause that reads a sibling always sees that sibling's stored value. Two fields asserting against each other is ordinary rather than a cycle:

```surql
DEFINE FIELD in ON follows TYPE record<user> ASSERT in != out;
DEFINE FIELD out ON follows TYPE record<user> ASSERT out != in;
```

A genuine cycle between value-producing clauses is rejected when the field is defined rather than when a record is written.

> [!NOTE]
> Computed fields are also populated on the records that [events](/docs/reference/query-language/statements/define/event.md), [live queries](/docs/reference/query-language/statements/live-select.md), and changefeeds receive. Before 3.3.0, `$before` and `$after` reported every computed field as `NONE`.

## Setting permissions on fields

By default, the permissions on a field will be set to `FULL` unless otherwise specified. The table is the main access gate, while field permissions only narrow further when you need to (for example, when hiding a password). With `FULL`, a field follows the [table](/docs/reference/query-language/statements/define/table.md#defining-permissions)'s rules without adding its own. Tables default the other way: omitting table `PERMISSIONS` in a `DEFINE` statement 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
-- 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";
```

## Array with allowed values

By using an Access Control List as an example we can show how we can restrict what values can be stored in an array. In this example we are using an array to store the permissions for a user on a resource. The permissions are restricted to a specific set of values.

```surql
-- An ACL can be applied to any kind of resource (record)
DEFINE FIELD resource ON TABLE acl TYPE record;
-- We associate the acl with a user using record<user>
DEFINE FIELD user ON TABLE acl TYPE record<user>;

-- The permissions for the user+resource will be stored in an array.
DEFINE FIELD permissions ON TABLE acl TYPE array
  -- The array must not be empty because at least one permission is required to make a valid ACL
  -- The items in the array must also be restricted to specific permissions
  ASSERT
      array::len($value) > 0
      AND $value ALLINSIDE ["create", "read", "write", "delete"];

-- SEE IT IN ACTION
-- 1: Add users
CREATE user:tobie SET firstName = 'Tobie', lastName = 'Hitchcock',
  email = 'Tobie.Hitchcock@surrealdb.com';
CREATE user:abc SET firstName = 'A', lastName = 'B',
  email = 'c@d.com';
CREATE user:efg SET firstName = 'E', lastName = 'F',
  email = 'g@h.com';

-- 2: Create a resource
CREATE document:SurrealDB_whitepaper SET
  name = "some messaging queue";

-- 3: Associate with ACL
CREATE acl SET user = user:tobie, resource = document:SurrealDB_whitepaper, permissions = ["create", "write", "read"];
CREATE acl SET user = user:abc, resource = document:SurrealDB_whitepaper, permissions = ["read", "delete"];

-- Test Asserts using failure examples
-- A: Create ACL without permissions field
CREATE acl:invalid SET
  user = user:efg,
  permissions = [], # FAIL - permissions must not be empty
  resource = document:SurrealDB_whitepaper;
-- B: Create acl with invalid permisson
CREATE acl:also_invalid SET
  user = user:efg,
  permissions = ["all"], # FAIL - This value is not allowed in the array
  resource = document:SurrealDB_whitepaper;
```

## Using regex to validate a string

You can use the `ASSERT` clause to apply a regular expression to a field to ensure that it matches a specific pattern. In the example below, the `ASSERT` clause is used to ensure that the `countrycode` field is always a valid ISO-3166 country code.

```surql
-- Specify a field on the user table
DEFINE FIELD countrycode ON user TYPE string
	-- Ensure country code is ISO-3166
	ASSERT $value = /[A-Z]{3}/
	-- Set a default value if empty
	VALUE $value OR $before OR 'GBR'
;
```

## Interacting with other fields of the same record

While a `DEFINE TABLE` statement represents a template for any subsequent records to be created, a `DEFINE FIELD` statement pertains to concrete field data of a record. As such, a `DEFINE FIELD` statement gives access to the record's other fields through their names, as well as the current field through the [`$value`](/docs/reference/query-language/language-primitives/parameters.md#value) parameter.

```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 name      
  ON TABLE person             VALUE first_name + ' ' + last_name;

-- Creates a `person` with the name "bob bobson"
CREATE person SET first_name = "BOB", last_name = "BOBSON";
```

The `$this` parameter gives access to the entire record on which a field is defined.

```surql
DEFINE FIELD extra_self ON TABLE person VALUE $this;
CREATE person:one SET name = "Little person", age = 6;
```

```surql title="Output"
[
	{
		age: 6,
		extra_self: {
			age: 6,
			id: person:one,
			name: 'Little person'
		},
		id: person:one,
		name: 'Little person'
	}
]
```

## Order of operations when setting a field's value

As `DEFINE FIELD` statements are computed in alphabetical order, be sure to keep this in mind when using fields that rely on the values of others.

The following example is identical to the above except that `full_name` has been chosen for the previous field `name`. The `full_name` field will be calculated after `first_name`, but before `last_name`.

```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;

-- Creates a `person` with `full_name` of "bob BOBSON", not "bob bobson"
CREATE person SET first_name = "Bob", last_name = "BOBSON";
```

A good rule of thumb is to organise your `DEFINE FIELD` statements in alphabetical order so that the field definitions show up in the same order as that in which they are computed.

## 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 }"
```

One more example of a literal containing settings for a [full text search](/docs/learn/data-models/full-text-search/overview.md) filter:

```surql
DEFINE FIELD filter ON TABLE search_settings TYPE
      "None"
    | { type: "Ascii" }
    | { type: "EdgeNgram", from: int, to: int }
    | { type: "Lowercase" }
    | { type: "Ngram", from: int, to: int }
    | { type: "Snowball", language: string }
    | { type: "Uppercase" };
```

## Defining a `TYPE` for the `id` field

The `DEFINE FIELD` statement can be defined for the `id` field to specify the acceptable type of ID.

```surql
DEFINE FIELD id ON TABLE something TYPE string;
DEFINE FIELD id ON TABLE something TYPE int;
DEFINE FIELD id ON TABLE something TYPE uuid;
```

Complex IDs can be specified as well.

```surql
-- using multiple data types for a Complex Record ID
DEFINE FIELD id
  ON TABLE log TYPE [record, "info" | "warn" | "error", datetime];

-- Incorrect ID format, generates an error
CREATE log:bad SET level = "info", time = time::now(), message = "Database started";

-- Acceptable ID format
CREATE log:[user:one, "info", time::now()] SET message = "Database started";
```

### `ASSERT` and `DEFAULT` on `id`

_(since v3.2.0)_

`ASSERT` on the `id` field is evaluated like any other field assertion. Inside the assertion, `$value` is the whole record id; use `id.id()` (or `record::id($value)`) to inspect the key portion. Assertions run on create for generated, default-supplied, and explicitly supplied ids, and are skipped on update and under `OPTION IMPORT`.

`DEFAULT` supplies the record id when none is given in `CREATE` or `INSERT`, evaluated in the session context and coerced to the declared type. An explicit id in the statement always wins. `DEFAULT ALWAYS` is not allowed on `id`.

```surql
DEFINE FIELD id ON user TYPE string DEFAULT rand::ulid() ASSERT id.id().is_ulid();
CREATE user SET name = 'Ada';
```

`VALUE`, `REFERENCE`, `COMPUTED`, `READONLY`, `FLEXIBLE`, and non-key `TYPE` clauses are forbidden on `id`, a restriction which apple to [`ALTER FIELD`](/docs/reference/query-language/statements/alter/field.md) statements as well.

```surql title="Output"
-------- Query --------

"Couldn't coerce value for field `id` of `log:bad`: Expected `[record, 'info' | 'warn' | 'error', datetime]` but found `'bad'`"

-------- Query --------

[
	{
		id: log:[
			user:one,
			'info',
			d'2025-03-25T03:36:16.323Z'
		],
		message: 'Database started'
	}
]
```

## Defining a reference

_(since v2.2.0)_

A field that is a record link (type `record`, `option<record>`, `array<record<person>>`, and so on) can be defined as a `REFERENCE`. If this clause is used, any linked to record will be able to define a computed field of its own using the `<~` syntax, which will be aware of the incoming links.

For more information, see [the page in the datamodel section on references](/docs/reference/query-language/language-primitives/record-references.md).

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/function

# DEFINE FUNCTION

The DEFINE FUNCTION statement allows you to define custom functions that can be reused throughout a database.

The `DEFINE FUNCTION` statement allows you to define custom functions that can be reused throughout a database. When using the `DEFINE FUNCTION` statement, you can define a function that takes one or more arguments and returns a value. You can then call this function in other SurrealQL statements.

Functions can be used to encapsulate logic that you want to reuse in multiple queries. They can also be used to simplify complex queries by breaking them down into smaller, more manageable pieces. They are particularly useful when you have a complex query that you need to run multiple times with different arguments.

## Requirements

- You must be authenticated as a root owner or editor, namespace owner or editor, or database owner or editor before you can use the `DEFINE FUNCTION` statement.
- [You must select your namespace and database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE FUNCTION` statement.

## Statement syntax

```syntax title="SurrealQL Syntax"
DEFINE FUNCTION [ OVERWRITE | IF NOT EXISTS ] fn::@name
  ( [ @argument: @type ... ] ) [ -> @type ] {
	[ @query ... ]
	[ RETURN @returned ]
} [ COMMENT @string ] [ PERMISSIONS [ NONE | FULL | WHERE @condition]]
```

## Example usage
Below shows how you can define a custom function using the `DEFINE FUNCTION` statement, and how to call it.

```surql
-- It is necessary to prefix the name of your function with "fn::"
-- This indicates that it's a custom function
DEFINE FUNCTION fn::greet($name: string) {
	"Hello, " + $name + "!"
};

-- Returns: "Hello, Tobie!"
RETURN fn::greet("Tobie");
```
To showcase a slightly more complex custom function, this will check if a relation between two nodes exists:

```surql
-- Define a function that checks if a relation exists between two nodes
DEFINE FUNCTION fn::relation_exists(
	$in: record,
	$tb: string,
	$out: record
) {
	-- 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;
};
```

## Optional arguments
If one or more ending arguments have the `option<T>` type, they can be omitted when you run the invoke the function.

```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);
//- { required_present: true, optional_present: true }

RETURN fn::last_option(1);
//- { required_present: true, optional_present: false };
```

## Adding a return value

Optionally, the return value of a function can be specified.

For a function that is infallible, a return value is mostly for the sake of readability.

```surql
DEFINE FUNCTION fn::greet($name: string) -> string {
	"Hello, " + $name + "!"
};
```

For a function that is not infallible, specifying a return value can be used to customise error output.

```surql
-- Arguments must be of type 'number'
DEFINE FUNCTION fn::combine($one: number, $two: number) -> number {
  $one + $two
};

-- Accepts any value but expects the return type 'number'
DEFINE FUNCTION fn::combine_any($one: any, $two: any) -> number {
  $one + $two
};

fn::combine("one", "two");
fn::combine_any("one", "two");
```

While both of these return an error, the output of the second function happens only at the point that it attempts to return the combined arguments to the function.

```surql title="Output"
-------- Query 1 --------
"Expected `number` but found `'one'`"

-------- Query 2 --------
"Couldn't coerce return value from function `fn::combine_any`: Expected `number` but found `'onetwo'`"
```

The return value of a function can even be a [literal type](/docs/reference/query-language/language-primitives/data-types/literals.md). The following function returns such a type by either returning an object of a certain structure, or a string. In this case this output is used in case an application prefers to return an error as a simple string instead of [throwing](/docs/reference/query-language/statements/throw.md) an error or returning a NONE value.

```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 + "!" }        
    }
};

CREATE user:1 SET name = "Billy", age = 15;

fn::age_and_name(1);
fn::age_and_name(2);
```

```surql title="Output"
-------- Query 1 --------

{ age: 15, name: 'Billy' }

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

"Couldn't find user number 2!"
```

## Transactional behaviour

A function body runs as a single transaction, without `BEGIN` or `COMMIT` appearing in the definition. It commits when the body finishes, and rolls back if anything inside it fails.

An error rolls the whole body back, whether it is a [`THROW`](/docs/reference/query-language/statements/throw.md) or a statement that fails on its own:

```surql
DEFINE FUNCTION fn::place_order($item: record<product>, $quantity: int) -> record<order> {
    LET $order = CREATE ONLY order SET item = $item, quantity = $quantity;
    IF $item.stock < $quantity {
        THROW "Insufficient stock for " + <string>$item;
    };
    UPDATE $item SET stock -= $quantity;
    RETURN $order.id;
};

fn::place_order(product:keyboard, 500);

-- No order exists: the THROW rolled the whole body back, so no order
-- is left behind reserving stock that was never decremented
SELECT VALUE id FROM order;
```

An early `RETURN` is not a failure, so the body commits and the work already done is kept:

```surql
DEFINE FUNCTION fn::ship_order($order: record<order>) -> record<shipment> {
    LET $shipment = CREATE ONLY shipment SET order = $order, carrier = 'DHL';
    RETURN $shipment.id;                          -- the caller only wants the id
    UPDATE $order SET status = 'shipped';         -- never runs
};
```

Calling a function from inside a manual transaction makes its statements part of that transaction rather than a nested one, so a failure inside the function aborts the caller's transaction too. See [Transactions](/docs/learn/querying/concepts-and-guides/transactions.md#implicit-transactions).

## Recursive functions

A function is able to call itself, making it a recursive function. One example of a recursive function is the one below which creates a relation between each and every record passed in.

Consider a situation in which seven person records exist. First, `person:1` will need to be related to the rest of the `person` records, after which there are no more relations to create for it. Following this, the relations for `person:2` and all the other records except for `person:1` will need to be created, and so on.

This can be done in a recursive function by creating all the relations between the first record and the remaining records, after which the function calls itself by passing in all the records except the first. This continues until the function receives less than two records, in which case it ceases calling itself by doing nothing, thereby ending the recursion.

```surql
DEFINE FUNCTION fn::relate_all($records: array<record>) {
  IF $records.len() < 2 {
      -- Don't do anything, ending the 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;
```

The last query [can be viewed graphically](/blog/whats-new-in-surrealist-3-2#graph-visualisation) inside SurrealDB Studio, leading to an output showing a seven-pointed star.

<img src="~/assets/img/image/light/recursive_star.png" darkSrc="~/assets/img/image/dark/recursive_star.png" alt="An image of a seven-pointed star created visually by relating seven records to each other and displayed inside SurrealDB Studio's graph view." />

## Permissions

You can set the permissions for a custom function using the `PERMISSIONS` clause. The `PERMISSIONS` clause is mostly used to restrict who can access a function and what data they can access. It can be set to `NONE`, `FULL`, or `WHERE @condition`.

- `FULL`: When Full permissions are granted [record](/docs/learn/security/authentication/users.md#record-users) users have access to the function. This is the default permission when not specified.
- `NONE`: When this permission is granted, [record](/docs/learn/security/authentication/users.md#record-users) users have no access to the defined function.
- `WHERE @condition`: Permissions are granted to the function based on the specified condition.

> [!NOTE]
> The examples below use the [`Surreal Deal Store`](/docs/explore/tutorials/demos/surreal-deal-store.md) dataset.

### Using the `FULL` permission

The `FULL` permission grants all users access to the function. The following example defines a function that fetches all products from the `product` table and grants the function full permissions to access the data to all users.

```surql
-- Define a function to fetch all products. All users can access this function
DEFINE FUNCTION fn::fetchAllProducts() {
	RETURN (SELECT * FROM product LIMIT 10);
} PERMISSIONS FULL;
-- Returns: The first 10 products in the product table
RETURN fn::fetchAllProducts();
```

### Using the `NONE` permission

The `NONE` permission denies all [record](/docs/learn/security/authentication/users.md#record-users) users access to the function. The following example defines a function that fetches all products from the `product` table

```surql
-- Define a function that fetches all expiration years from the payment_details table and denies access to all none-admin users
DEFINE FUNCTION fn::fetchAllPaymentDetails() -> array {
	SELECT stored_cards.expiry_year FROM payment_details LIMIT 5
} PERMISSIONS NONE;

RETURN fn::fetchAllPaymentDetails();
```

### Using the `WHERE` clause

The `WHERE` clause allows you to specify a condition that determines the permissions granted to the function. The condition must evaluate to a boolean value. If the condition evaluates to `true`, the function is granted permissions. If the condition evaluates to `false`, the function is not granted permissions.

```surql
-- Define a function that fetches all products with the condition that only admin users can access it
DEFINE FUNCTION fn::fetchAllProducts() -> array {
	 SELECT * FROM product LIMIT 10
} PERMISSIONS WHERE $auth.admin = true;
```

## Functions that other definitions require to stay read-only

_(since v3.3.0)_

Two places in the schema evaluate an expression that must not modify data: a [`COMPUTED` field](/docs/reference/query-language/statements/define/field.md#restrictions-on-computed-fields) body, and any `PERMISSIONS` clause. Both are checked when they are defined, and the check follows calls into custom functions.

A `PERMISSIONS` clause is therefore refused if it calls a function that writes.

```surql
DEFINE FUNCTION fn::log_access() -> bool { CREATE access_log SET at = time::now(); RETURN true; };

-- Refused: the guard calls a function that writes
DEFINE TABLE product PERMISSIONS FOR select WHERE fn::log_access();
```

The rule also applies when the function changes rather than the caller. `DEFINE FUNCTION` and [`ALTER FUNCTION`](/docs/reference/query-language/statements/alter/function.md) refuse a body that starts to write while a computed field or a permission guard still depends on that function staying read-only. The error names the definitions that depend on it, so redefining a function cannot silently break the fields and guards that call it.

```surql
DEFINE FUNCTION fn::price_with_tax($price: number) -> number { RETURN $price * 1.2; };
DEFINE FIELD gross_price ON product COMPUTED fn::price_with_tax(price);

-- Refused: `gross_price` on `product` requires fn::price_with_tax() to stay read-only
DEFINE FUNCTION OVERWRITE fn::price_with_tax($price: number) -> number {
	CREATE price_change SET price = $price, at = time::now();
	RETURN $price * 1.2;
};
```

The check resolves calls against the stored function bodies, so it also follows a call several functions deep. A write reached through something that cannot be resolved at definition time - [`eval::surql()`](/docs/reference/query-language/functions/database-functions/eval.md#evalsurql), a JavaScript function, or a closure that arrives as data - is still accepted, and the write is refused when the field or guard is evaluated.

> [!NOTE]
> These checks are relaxed under `OPTION IMPORT`, so an [export](/docs/reference/cli/surrealdb-cli/commands/export.md) taken before the rules existed still restores.

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define a function only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining a function in SurrealDB if you want to ensure that the function is only created if it does not already exist. If the function already exists, the `DEFINE FUNCTION` statement will return an error.

It's particularly useful when you want to safely attempt to define a function without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the function definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a function and overwrite an existing one if it already exists, ensuring that the latest version of the function definition is always in use

```surql
-- Create a FUNCTION if it does not already exist
DEFINE FUNCTION IF NOT EXISTS fn::example() {};
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define a function and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing user definition. If the user already exists, the `DEFINE FUNCTION` statement will overwrite the existing definition with the new one.

```surql
-- Create a FUNCTION and overwrite if it already exists
DEFINE FUNCTION OVERWRITE fn::example() {};
```

## Functions as custom middleware

_(since v3.0.0)_

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.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/indexes

# DEFINE INDEX

SurrealDB uses indexes to help optimise query performance. An index can consist of one or more fields in a table and can enforce a uniqueness constraint - including HNSW and DISKANN vector indexes.

> [!NOTE]
> Before SurrealDB version 3.0.0, the `FULLTEXT ANALYZER` clause used the syntax `SEARCH ANALYZER`.

Just like in other databases, SurrealDB uses indexes to help optimise query performance. An index can consist of one or more fields in a table and can enforce a uniqueness constraint. If you don't intend for your index to have a uniqueness constraint, then the fields you select for your index should have a high degree of cardinality, meaning that there is a high amount of diversity between the data in the indexed table records.

## Requirements

- You must be authenticated as a root owner or editor, namespace owner or editor, or database owner or editor before you can use the `DEFINE INDEX` statement.
- [You must select your namespace and database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE INDEX` statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="Basic syntax"
DEFINE INDEX [ OVERWRITE | IF NOT EXISTS ] @name
    ON [ TABLE ] @table 
    [ FIELDS | COLUMNS ] @fields
    [ @special_clause ]
    [ COMMENT @string ]
    [ CONCURRENTLY ]
    [ DEFER ]
```

The `@special_clause` part of the statement is an optional part in which an index can be declared for special usage such as guaranteeing unique values, full-text search, and so on. The available clauses are:

```syntax title="Special index clauses"
UNIQUE
| COUNT [ WHERE @condition ]
| FULLTEXT ANALYZER @analyzer [ BM25 [(@k1, @b)] ] [ HIGHLIGHTS ]
| HNSW DIMENSION @dimension [ TYPE @type ] [ DIST @distance ] [ EFC @efc ] [ M @m ]
| DISKANN DIMENSION @dimension [ TYPE @type ] [ DIST @distance ] [ DEGREE @degree ] [ L_BUILD @l_build ] [ ALPHA @alpha ] [ HASHED_VECTOR ]
```

## Index types

SurrealDB offers a range of indexing capabilities designed to optimise data retrieval and search efficiency.

### Standard (non-unique) index

An index without any special clauses allows for the indexing of attributes that may have non-unique values, facilitating efficient data retrieval. Non-unique indexes help index frequently appearing data in queries that do not require uniqueness, such as categorization tags or status indicators.

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 COLUMNS age;
```

### Unique index

Ensures 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 COLUMNS email UNIQUE;
```

The created index can be tested 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` column, a duplicate entry for that column or field will throw an error.

```surql
-- Create a user record and set an email ID.
CREATE user:1 SET email = 'test@surrealdb.com';
```

```surql title="Output"
[
    {
        "email": "test@surrealdb.com",
        "id": "user:1"
    }
]
```

Creating another record with the same email ID will throw an error.

```surql
-- 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`
```

To set the same email for `user:2`, the original record must be deleted

```surql
DELETE user:1;
CREATE user:2 SET email = 'test@surrealdb.com'
```
```text
[
    {
        "email": "test@surrealdb.com",
        "id": "user:2"
    }
]
```

### Composite index

A composite index spans multiple fields and columns of a table. Composite indexes are mainly used to create a unique index 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;
```

#### Array-element composite indexes _(since v3.1.0)_

From SurrealDB 3.1.0, a composite index can lead with an **array-element** column (`tags.*`) so containment predicates combine with ordering on a trailing column without a full table scan. This supports patterns such as `WHERE tags CONTAINS 'x' ORDER BY age DESC LIMIT N` and the multi-value forms `CONTAINSANY` / `ANYINSIDE` on array fields.

```surql
DEFINE INDEX tag_age ON article FIELDS tags.*, age;

-- Bounded index walk (check with EXPLAIN FULL)
SELECT * FROM article
WHERE tags CONTAINS 'release-notes'
ORDER BY age DESC
LIMIT 10;
```

`CONTAINSALL` / `ALLINSIDE` may still apply a residual filter above the union when intersection semantics require it. See [operators](/docs/reference/query-language/language-primitives/operators.md) and [`EXPLAIN`](/docs/reference/query-language/statements/explain.md) to confirm the planner choice.

### Count index

_(since v3.0.0)_

A count index maintains a running count of records on a table. It is used with `count()` and `GROUP ALL` in a query. SurrealDB stores incremental count deltas and compacts them in the background. From 3.2.5, a bare `count()` projection [implies `GROUP ALL`](/docs/reference/query-language/statements/select.md#bare-count-implies-group-all); the examples below keep the explicit form.

Count indexes come in two forms:

- **`COUNT`** - counts every record in the table. Used with `SELECT count() FROM <table> GROUP ALL` (no `WHERE`).
- **`COUNT WHERE <condition>`** - counts only records that match the condition. Used with `SELECT count() FROM <table> WHERE <condition> GROUP ALL` when the query `WHERE` clause **exactly matches** the index condition.

As a count index is declared on a table as a whole, it cannot use the `FIELDS` / `COLUMNS` clause.

#### Full-table counts

For `SELECT count() FROM <table> GROUP ALL`, SurrealDB already uses a `CountScan` fast path that counts record keys in storage without deserialising every row. An unconditional `COUNT` index is optional here; it can reduce work further once count deltas have been compacted, but a freshly built index on a large existing table may hold millions of delta entries until compaction runs.

```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 counts (`COUNT WHERE`)

Use `COUNT WHERE` when you repeatedly need `SELECT count() … WHERE … GROUP ALL` and do not want to scan and deserialise every record. The planner uses `IndexCountScan` when the query `WHERE` matches the index condition exactly (same expression as written in `DEFINE INDEX`).

```surql
DEFINE TABLE item SCHEMALESS;
DEFINE INDEX item_active_count ON item COUNT WHERE status = "active";

CREATE item:a SET status = "active";
CREATE item:b SET status = "active";
CREATE item:c SET status = "inactive";

SELECT count() FROM item WHERE status = "active" GROUP ALL;

EXPLAIN ANALYZE SELECT count() FROM item WHERE status = "active" GROUP ALL;
```

Requirements for the filtered fast path:

- `GROUP ALL` must be present (written explicitly, or [implied](/docs/reference/query-language/statements/select.md#bare-count-implies-group-all) by a bare `count()` projection from 3.2.5).
- The query `WHERE` must match the index condition exactly (not a broader or different predicate).
- The index build must be complete (`INFO FOR INDEX …` shows `ready`).
- Field-level `SELECT` permissions on fields in the condition must not block index-only counting.

If the `WHERE` clause is not an exact match, SurrealDB falls back to a full table scan, filter, and count.

#### When to use which

For many applications, a **standard B-tree index** (`FIELDS` / `COLUMNS`) is the default for filtered counts. Recent planner improvements let a covering B-tree use the same `IndexCountScan` fast path as a `COUNT WHERE` index, so the two often perform similarly at query time.

That does **not** make count indexes obsolete, however, as they solve a narrower problem and have a smaller storage footprint.

* If you would add `DEFINE INDEX … FIELDS status` anyway, a separate `COUNT WHERE status = "active"` index is usually redundant. In this case, a standard B-tree is preferred.

* When to use `COUNT WHERE` instead: materialised cardinality for a fixed condition you do not want to back with a B-tree; compound predicates without a covering index; or a dedicated counter separate from lookup indexes.

The following chart sums up a few use cases and when to prefer one vs. the other.

| Goal | Prefer |
| --- | --- |
| `count() … GROUP ALL` with no `WHERE` | Nothing extra required; `CountScan` counts record keys. Unconditional `COUNT` is optional. |
| `count() … WHERE field = value GROUP ALL` on a field you already filter on | `FIELDS field` B-tree (also supports `SELECT`, `ORDER BY`, and other predicates) |
| Many per-value counts on the same field | One B-tree on that field |
| One fixed combined predicate (`IN`, `AND`, etc.) counted repeatedly | `COUNT WHERE` with the **exact** same expression in the query |
| Fast counts for a predicate but **no** B-tree on those fields | `COUNT WHERE` |
| Compound `WHERE` with no covering composite B-tree | `COUNT WHERE` with the exact compound condition |

#### Storage footprint

Count indexes and B-tree indexes store different shapes of data, so size is not the same even when query performance is similar.

A **B-tree index** stores one entry per indexed record: encoded field value(s) plus the record id in the key. Storage grows **with the number of rows** in the table (O(n) for that index).

A **`COUNT WHERE` index** does not store per-row field keys. It appends small **delta entries** (`IndexCountKey`) when rows enter or leave the predicate, then **compacts** them into a single aggregate entry in the background. In steady state, footprint is usually **much smaller** than a B-tree on the same field, often a handful of keys rather than one per row.

So `COUNT WHERE` can be attractive when you need fast filtered counts **without** paying the per-row storage of a B-tree if it will not be used for anything else.

To compare indexed and non-indexed performance, the `WITH NOINDEX` clause can be used.

```surql
-- Slow path: scan every record
SELECT count() FROM item WITH NOINDEX WHERE status = "active" GROUP ALL;

-- Fast path: count index deltas (or B-tree keys if a covering index exists)
SELECT count() FROM item WHERE status = "active" GROUP ALL;
```

#### Multiple conditions

As each `COUNT WHERE` index tracks one fixed predicate, it does not accelerate several different counts unless each query repeats the **exact** condition stored in the index.

```surql
DEFINE INDEX openish_count ON person COUNT
    WHERE status IN ["active", "non-active", "delayed"];

-- Fast only when the WHERE matches the index exactly
SELECT count() FROM person
    WHERE status IN ["active", "non-active", "delayed"]
    GROUP ALL;
```

In the above example, it does not also speed up `WHERE status = "active" GROUP ALL` or other subsets.

For separate per-status totals, one `FIELDS status` B-tree is usually simpler than one `COUNT WHERE` index per value. See [When to use which](/docs/reference/query-language/statements/define/indexes.md#when-to-use-which).

Use [`EXPLAIN ANALYZE`](/docs/reference/query-language/statements/explain.md) to confirm `IndexCountScan` is selected. If the query `WHERE` differs even slightly from the index condition, SurrealDB falls back to a full scan.

#### Other `COUNT` syntax notes

```surql
-- Other clauses like `COMMENT` are fine
DEFINE INDEX idx ON users COUNT
    COMMENT "Users are expected to grow substantially so index the count"
    CONCURRENTLY;

-- Conditional count indexes
DEFINE INDEX active_users ON users COUNT WHERE status = "active" CONCURRENTLY;

-- But not `FIELD`
DEFINE INDEX idx2 ON person FIELD name;
```

```surql title="Output"
'There was a problem with the database: Parse error: Unexpected token `FIELD`, expected Eof
 //- [1:29]
  |
1 | DEFINE INDEX idx2 ON person FIELD name;
  |                             ^^^^^ 
'
```

### 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 the an analyzer with
DEFINE ANALYZER example_ascii TOKENIZERS class FILTERS ascii;
-- Since 3.0.0: only FULLTEXT used to benefit from concurrent full-text search
DEFINE INDEX userNameIndex ON TABLE user COLUMNS name FULLTEXT ANALYZER example_ascii BM25 HIGHLIGHTS;
```

- `SEARCH` or `FULLTEXT`: By using the `SEARCH` keyword, you enable full-text search on the specified column.
- `ANALYZER ascii`: Uses a custom [analyzer](/docs/reference/query-language/statements/define/analyzer.md) called `example_ascii` which uses the class tokenizier and `ascii` filter to analysing the text input.
- `BM25`: Ranking algorithm used for relevance scoring. BM25 weighs a term by how rare it is, and clamps that weight to zero for any term appearing in half or more of the indexed documents, which makes [`search::score`](/docs/reference/query-language/functions/database-functions/search.md#searchscore) return `0` for it. See [why a score can be 0](/docs/learn/data-models/full-text-search/scoring-and-ranking.md#why-a-score-can-be-0).
- `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.

#### `FULLTEXT` vs. `SEARCH`

Since version 3.0.0, using `FULLTEXT ANALYZER` is the syntax used for a text analyzer. The `FULLTEXT` clause allows for more performant [concurrent full-text search](https://github.com/surrealdb/surrealdb/pull/5571), as well as the ability to [use the `OR` operator](https://github.com/surrealdb/surrealdb/pull/6179).

## Vector search indexes

Vector search indexes in SurrealDB support efficient [k-nearest neighbors](https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm) (kNN) and [Approximate Nearest Neighbor](https://en.wikipedia.org/wiki/Nearest_neighbor_search) (ANN) operations, which are pivotal in performing similarity searches within complex, high-dimensional datasets and data types. Refer to the [Vector Search Cheat Sheet](/docs/learn/data-models/vector-search/vector-indexes.md#vector-search-cheat-sheet) for the parameters allowed.

### Types

When defining a vector index with [HNSW](#hnsw-hierarchical-navigable-small-world), you can define the types the vector will be stored in. The `TYPE` clause is optional and can be used to specify the data type of the vector. SurrealDB supports the following types:
`F64` | `F32` | `I64` | `I32` | `I16`

- `F64`: Represents 64-bit floating-point numbers (double precision floating-point numbers).
- `F32`: Represents 32-bit floating-point numbers (single precision floating-point numbers).
- `I64`: Represents 64-bit signed integers.
- `I32`: Represents 32-bit signed integers.
- `I16`: Represents 16-bit signed integers.

> [!NOTE]
> In SurrealDB the default type for vectors is `F32`.

_(since v3.1.0)_

[DISKANN](#diskann-disk-based-approximate-nearest-neighbours) indexes accept a **narrower** set of element types: `F32`, `F16`, `I8`, and `U8` only, and only a subset of distance metrics - see the [DISKANN](#diskann-disk-based-approximate-nearest-neighbours) section below.

For example, to define a vector index with 64-bit signed integers, you can use the following query:

```surql
DEFINE INDEX idx_hnsw_embedding
  ON Document FIELDS items.embedding HNSW DIMENSION 4 TYPE I64;
```

### HNSW (Hierarchical Navigable Small World)

This method uses a graph-based approach to efficiently navigate and search in high-dimensional spaces.
While it is an approximate technique, it offers a high-performance balance between speed and accuracy, making it ideal for very large datasets.

> [!NOTE]
> Keep in mind the in-memory nature of HNSW when considering system resource allocation.

```surql
CREATE pts:3 SET point = [8,9,10,11];
DEFINE INDEX mt_pts ON pts FIELDS point HNSW DIMENSION 4 DIST EUCLIDEAN EFC 150 M 12;
// See output for info on EFC,M,MO AND LM
INFO FOR TABLE pts;
SELECT id FROM pts WHERE point <|10,40|> [2,3,4,5];
```

In the example above, you may notice the `EFC` and `M` parameters. These are optional to your query but are parameters of the [HNSW algorithm](https://arxiv.org/abs/1603.09320) and can be used to tune the index for better performance.

- M (Max Connections per Element):
Defines the maximum number of bidirectional links (neighbors) per node in each layer of the graph, except for the lowest layer. This parameter controls the connectivity and overall structure of the network. Higher values of MM generally improve search accuracy but increase memory usage and construction time.

- EFC (EF construction):
Stands for "exploration factor during construction." This parameter determines the size of the dynamic list for the nearest neighbor candidates during the graph construction phase. A larger efConstruction value leads to a more thorough construction, improving the quality and accuracy of the search but increasing construction time. The default value is 150.

- M0 (Max Connections in the Lowest Layer):
Similar to M, but specifically for the bottom layer (the base layer) of the graph. This layer contains the actual data points. M0 is often set to twice the value of M to enhance search performance and connectivity at the base layer, at the cost of increased memory usage.

- LM (Multiplier for Level Generation):
Used to determine the maximum level ll for a new element during its insertion into the hierarchical structure. It is used in the formula l←⌊−ln⁡(unif(0..1))⋅mL⌋, where unif(0..1) is a uniform random variable between 0 and 1. This parameter influences the distribution of elements across different levels, impacting the overall balance and efficiency of the search structure.

> [!NOTE]
> You can only provide TYPE, M, and EFC. SurrealDB automatically computes M0 and LM with the most appropriate value. If not specified, M AND EFC are set to 12 and 150, respectively.  Refer to the [Vector Search Cheat Sheet](/docs/learn/data-models/vector-search/vector-indexes.md#vector-search-cheat-sheet) for the parameters allowed.

#### Memory cache usage for HNSW indexes

_(since v3.0.0)_

HNSW vector search caches element vectors in a bounded memory cache, reducing memory spikes and improving stability under load. The cache is a single budget **shared across every HNSW index** in the process, not a per-index allowance. It defaults to 256 MiB and can be modified via the `SURREAL_HNSW_CACHE_SIZE` [environment variable](/docs/reference/cli/surrealdb-cli/environment-variables.md). The value is an integer number of bytes - for example, `268435456` for the 256 MiB default.

This budget covers the cached **vectors** only. As noted above, the HNSW graph itself is held in memory: it is loaded on first use and is not bounded by this setting, so it must be sized for separately. If the full graph will not fit in RAM, DISKANN below is the index designed for that case.

### DISKANN (disk-based approximate nearest neighbours)

_(since v3.1.0)_

[DiskANN](https://arxiv.org/abs/1907.01668)-style indexes provide **on-disk** approximate nearest-neighbour graphs. They are aimed at **very large** embedding sets where keeping the full graph in RAM (as with HNSW) is impractical: vectors and graph structure are persisted in the key-value store and paged through a bounded cache, trading some latency for a much larger working set.

> [!NOTE]
> DISKANN indexes are **not supported on WASM** targets. Use HNSW or brute-force KNN for browser / embedded WASM builds.

#### Syntax and defaults

The clause mirrors HNSW: `DISKANN DIMENSION …` followed by optional `TYPE`, `DIST`, tuning knobs, and `HASHED_VECTOR`.

```surql
DEFINE INDEX diskann_pts
  ON pts FIELDS point DISKANN DIMENSION 4 DIST EUCLIDEAN TYPE F32;
-- Same index with defaults written out explicitly:
-- DEFINE INDEX diskann_pts ON pts FIELDS point DISKANN DIMENSION 4 DIST EUCLIDEAN TYPE F32 DEGREE 64 L_BUILD 100 ALPHA 1.2;
```

| Clause | Default | Notes |
| --- | --- | --- |
| `DIST` | `EUCLIDEAN` | Only `EUCLIDEAN`, `COSINE`, `INNER_PRODUCT`, and `COSINE_NORMALIZED` are valid for DISKANN. |
| `TYPE` | `F32` | Must be one of `F32`, `F16`, `I8`, `U8`. `COSINE_NORMALIZED` further requires `F32` or `F16`. |
| `DEGREE` | `64` | Target maximum graph degree (`> 0`). |
| `L_BUILD` | `100` | Construction search-list size (`> 0`). |
| `ALPHA` | `1.2` | DiskANN pruning parameter. |
| `HASHED_VECTOR` | off | Same semantics as for HNSW - hash-stabilised vector - document keys. |

Queries use the **same** KNN operator shape as HNSW: `<|K, EF|>` (approximate) or `<|K, DIST|>` when the distance matches the index, letting the optimiser pick the DISKANN graph. The second number in the approximate form bounds the dynamic candidate list during search (analogous to HNSW `EF`).

#### Memory model and cache size

While HNSW's graph is held resident in memory, a DISKANN index is **key-value-backed**. The graph, its adjacency lists, and the full-precision element vectors are persisted in the key-value store (for example, on disk under RocksDB) and remain the source of truth. Only a bounded in-memory cache holds the hot working set, while cold nodes and vectors are streamed from the key-value store on demand. A DISKANN index's resident memory is therefore bounded by the cache size rather than by the size of the dataset, which is what makes it suitable for larger-than-memory workloads.

Vectors are stored and searched at **full precision**, applying no product quantisation or other compression. The only lever on per-vector size is the chosen `TYPE` (`F32`, `F16`, `I8`, or `U8`).

The cache is a single budget **shared across every DISKANN index** in the process. It defaults to 256 MiB and can be capped with the `SURREAL_DISKANN_CACHE_SIZE` [environment variable](/docs/reference/cli/surrealdb-cli/environment-variables.md#cache-config), the DISKANN counterpart of `SURREAL_HNSW_CACHE_SIZE`. The value is an integer number of bytes - for example, `268435456` for the 256 MiB default.

#### Choosing HNSW or DISKANN

| Concern | Prefer HNSW | Prefer DISKANN |
| --- | --- | --- |
| Working set size | Fits comfortably in memory with headroom for the HNSW cache | Much larger than RAM; cold data should stay on disk |
| Latency profile | Lowest latency when the graph is hot in cache | Extra I/O; better for throughput to cost on huge corpora |
| Vector element types | Full set (`F64` … `I16` in docs above) | Compact types (`F32`, `F16`, `I8`, `U8`) |
| Deployment target | Includes WASM | Server / native binaries only |

### Brute Force method

The Brute Force method is suitable for tasks with smaller datasets or when the highest accuracy is required.
Brute Force currently supports [Euclidean](/docs/reference/query-language/functions/database-functions/vector.md#vectordistanceeuclidean), [Cosine](/docs/reference/query-language/functions/database-functions/vector.md#vectorsimilaritycosine), [Manhattan](/docs/reference/query-language/functions/database-functions/vector.md#vectordistancemanhattan) and [Minkowski](/docs/reference/query-language/functions/database-functions/vector.md#vectordistanceminkowski) distance functions.

In the example below, the query searches for points closest to the vector `[2,3,4,5]` and uses [vector functions](/docs/reference/query-language/functions/database-functions/vector.md) to calculate the distance between two points, indicated by `<|2|>`.

```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];
LET $pt = [2,3,4,5];
SELECT id, vector::distance::euclidean(point, $pt) AS dist FROM pts WHERE point <|2,EUCLIDEAN|> $pt;
SELECT id FROM pts WHERE point <|2|> $pt EXPLAIN;
```

## Verifying Index Utilization in Queries

The [`EXPLAIN` clause](/docs/reference/query-language/statements/select.md#the-explain-clause) from SurrealQL helps you understand the execution plan of the query and provides transparency around index utilization.

```surql
SELECT * FROM user WHERE email='test@surrealdb.com' EXPLAIN FULL;
```

It also reveals details about which `operation` was used by the query planner and how many records matched the search criteria.

```surql
[
    {
        "detail": {
            "plan": {
                "index": "userEmailIndex",
                "operator": "=",
                "value": "test@surrealdb.com"
            },
            "table": "user"
        },
        "operation": "Iterate Index"
    },
    {
        "detail": {
            "count": 1
        },
        "operation": "Fetch"
    }
]
```

## 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 overtime to ensure that the index is up-to-date with the latest data in the table.

```surql
REBUILD INDEX userEmailIndex ON user;
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define an index only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining a index in SurrealDB if you want to ensure that the index is only created if it does not already exist. If the index already exists, the `DEFINE INDEX` statement will return an error.

It's particularly useful when you want to safely attempt to define a index without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the index definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a index and overwrite an existing one if it already exists, ensuring that the latest version of the index definition is always in use

```surql
-- Create a INDEX if it does not already exist
DEFINE INDEX IF NOT EXISTS example ON example FIELDS example;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define an index and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing index definition. If the index already exists, the `DEFINE INDEX` statement will overwrite the existing definition with the new one.

```surql
-- Create an INDEX and overwrite if it already exists
DEFINE INDEX OVERWRITE example ON example FIELDS example;
```

## 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 fully built (or the build fails). The `CONCURRENTLY` clause can be used when you need the statement to return immediately while indexing continues in the background. This allows other operations to keep running while the index builds, during which you can monitor progress 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;
```

When building an index concurrently, SurrealDB starts the index creation as a background process. You can monitor the status of this process using the `INFO FOR INDEX` statement. The output includes a building block that provides several key details:

```surql
-- Check the indexing status
INFO FOR INDEX test ON user;
```

```surql title="Possible output"
-- Query

{
    building:  {
        initial: 8143,
        pending: 19,
        status: 'indexing',
        updated: 80
    }
}
```
The indexing process consists of two stages: **initial** and **update**.

1. **Initial Stage:**

   During this stage, SurrealDB indexes all existing records. The number of indexed records is represented by the `initial` property. While this stage is in progress, any new inserts, updates, or deletions are tracked as `pending`.

2. **Update Stage:**

   Once the initial stage is completed, SurrealDB begins indexing the pending records accumulated during the initial phase. At this point:

   - The `initial` count remains stable.
   - The `pending` count should gradually decrease as these records are processed; however, it may temporarily increase if new modifications occur during indexing.
   - The `updated` property indicates the number of pending records that have been indexed during this stage.

When both stages are complete, the index status changes to **ready**, meaning that the index is now automatically updated within the same transaction that inserts, updates, or deletes records.

```surql
-- Query

{
	building: {
		status: 'ready'
	}
}
```

## Using the `ANY`/`ALL` operators for string indexes

_(since v2.4.0)_

An index defined on a string value can be used via the operators `CONTAINSANY`, `ALLINSIDE`, or `ANYINSIDE`. The operator `CONTAINS`, however, will not use a defined index as `CONTAINS` is used for substring matches between strings themselves as opposed to an index lookup.

```surql
DEFINE FIELD name ON account TYPE string;
DEFINE INDEX name_index ON account FIELDS name;

CREATE account:billy SET name = "Billy McConnell";

-- Both return the user Billy McConnell
SELECT * FROM account WHERE name CONTAINS "Billy McConnell";
SELECT * FROM account WHERE name CONTAINSANY ["Billy McConnell"];

-- However, CONTAINS does not use the index
SELECT * FROM account WHERE name CONTAINS "Billy McConnell" EXPLAIN FULL;
-- CONTAINSANY + putting the value inside an array will use the index
SELECT * FROM account
  WHERE name CONTAINSANY ["Billy McConnell"] EXPLAIN FULL;
```

## The `DEFER` clause

_(since v2.5.0)_

> [!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 SEARCH ANALYZER simple BM25(1.2,0.75) 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, it's essential to consider the fields most frequently queried or used to optimise performance.

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/insert.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. Therefore, it's essential to balance the need for read performance with write performance.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/module

# DEFINE MODULE

A DEFINE MODULE statement can be used to define a module through which Surrealism extension functions can be called.

_(since v3.0.0)_

A `DEFINE MODULE` statement is used to define a module via which [Surrealism](/docs/learn/extensions/plugins/overview.md) extensions functions can be called.

> [!NOTE]
> The [`surrealism` experimental](/docs/reference/cli/surrealdb-cli/commands/module.md) feature must be enabled before you can use a `DEFINE MODULE` statement.

## Statement syntax

**Before SurrealDB 3.3**

```syntax title="SurrealQL Syntax"
DEFINE MODULE [ OVERWRITE | IF NOT EXISTS ] @mod::@sub AS @file_name
	[ COMMENT @string ]
```

**SurrealDB 3.3 and later**

```syntax title="SurrealQL Syntax"
DEFINE MODULE [ OVERWRITE | IF NOT EXISTS ] @mod::@sub FROM @file_name UNSIGNED
	[ COMMENT @string ]

DEFINE MODULE [ OVERWRITE | IF NOT EXISTS ] silo::@org::@package::<@version> UNSIGNED
	[ COMMENT @string ]
```

> [!WARNING]
> `DEFINE MODULE` changed in SurrealDB 3.3.0. `FROM` replaces `AS` before the module executable, and the new `UNSIGNED` keyword is required. `AS` is rejected rather than kept as an alternative spelling, so a statement written for an earlier version does not parse on 3.3. Nothing stored changes, so existing modules keep working and an export taken on an earlier version still restores.

## Example

A module includes a module and a sub, followed by a pointer to the `.surli` file containing the Rust code compiled to WASM through the Surrealism CLI.

**Before SurrealDB 3.3**

```surql
DEFINE MODULE mod::test AS f"test:/demo.surli";
```

**SurrealDB 3.3 and later**

```surql
DEFINE MODULE mod::test FROM f"test:/demo.surli" UNSIGNED;
```

On 3.3 the earlier spelling is a parse error:

```surql
DEFINE MODULE mod::test AS f"test:/demo.surli" UNSIGNED;
```

```text
Unexpected token `AS`, expected FROM
```

The keyword changed because everywhere else in SurrealQL `AS` introduces an alias, with the new name on the right. `DEFINE MODULE` read the other way round: the name on the left, the module's source on the right.

Once the module is defined, functions can be accessed through this path.

Assuming these two functions in the Rust code before compilation to WASM via the Surrealism CLI:

```rust
#[surrealism]
fn returns_true() -> bool { true };

#[surrealism]
fn check_num_size(num: i32) -> Result<i32, &'static str> {
    if num >= 500 {
        Err("Number is too big!")
    } else {
        Ok(num)
    }
}
```

They will then be accessible using the following paths.

```surql
mod::test::returns_true();
//- true

mod::test::check_num_size(100);
//- 100
```

## The UNSIGNED keyword

_(since v3.3.0)_

`UNSIGNED` is required on every `DEFINE MODULE` statement, and its absence is a parse error. It sits at the end of the statement and can appear in any order alongside `COMMENT` and `PERMISSIONS`, because it reads as an option of the definition rather than a property of the executable.

```surql
DEFINE MODULE mod::color FROM f"modules:/color.surli" UNSIGNED COMMENT "Colour helpers";
```

The keyword marks a module as carrying no signature. Only packages published to Silo are signed - Silo signs a package when it is uploaded - so a module loaded from anywhere else can never carry one and must say so explicitly.

The requirement is in place before signature verification ships, so a definition written today stays valid once the keyword starts gating a real check. A module defined before 3.3.0 is treated as unsigned and keeps loading unchanged, and both [`INFO FOR DB`](/docs/reference/query-language/statements/info.md) and [`surreal export`](/docs/reference/cli/surrealdb-cli/commands/export.md) render the keyword back, so a dump re-imports.

## Silo packages

_(since v3.3.0)_

A module can also be defined from a package published to Silo. This form is keyed by its package coordinates, so it takes no `mod::` alias and no `FROM`:

```surql
DEFINE MODULE silo::surrealdb::color::<1.0.0> UNSIGNED;
```

The package is fetched over HTTPS from the configured Silo endpoint and cached, so you no longer need to build each module locally and upload the `.surli` file to a bucket before `DEFINE MODULE` can reach it.

Two databases that define the same package hold it separately, exactly as two databases using the same bucket object already do. A module keeps state between invocations, so this keeps that state inside the namespace and database that created it. `REMOVE MODULE` and `DEFINE MODULE ... OVERWRITE` therefore only evict the calling database's copy.

> [!NOTE]
> Before 3.3.0, `DEFINE MODULE silo::org::pkg::<1.0.0>` failed to parse with ``Unexpected character `.` starting float, only integers are allowed here``, so no Silo module could be declared at all.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/namespace

# DEFINE NAMESPACE

The DEFINE NAMESPACE statement can be used to setup namespaces, which can contain multiple databases.

SurrealDB has a multi-tenancy model which allows you to scope databases to a namespace. There is no limit to the number of databases that can be in a namespace, nor is there a limit to the number of namespaces allowed. Only users with root access are authorised to create namespaces.

Let's say that you're using SurrealDB to create a multi-tenant SaaS application. You can guarantee that the data of each tenant will be kept separate from other tenants if you put each tenant's databases into separate namespaces. In other words, this will ensure that information will remain siloed so user will only have access the information in the namespace they are a member of.

## Requirements

- You must be authenticated as a root owner or editor before you can use the `DEFINE NAMESPACE` statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE NAMESPACE [ OVERWRITE | IF NOT EXISTS ] @name [ COMMENT @string ]
```

## Example usage
Below shows how you can create a namespace using the `DEFINE NAMESPACE` statement.

```surql
-- Namespace for Abcum Ltd.
DEFINE NAMESPACE abcum;
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define a namespace only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining a namespace in SurrealDB if you want to ensure that the namespace is only created if it does not already exist. If the namespace already exists, the `DEFINE NAMESPACE` statement will return an error.

It's particularly useful when you want to safely attempt to define a namespace without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the namespace definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a namespace and overwrite an existing one if it already exists, ensuring that the latest version of the namespace definition is always in use

```surql
-- Create a NAMESPACE if it does not already exist
DEFINE NAMESPACE IF NOT EXISTS example;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define a namespace and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing namespace definition. If the namespace already exists, the `DEFINE NAMESPACE` statement will overwrite the existing namespace definition with the new one.

```surql
-- Create an NAMESPACE and overwrite if it already exists
DEFINE NAMESPACE OVERWRITE example;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/overview

# DEFINE

DEFINE declares SurrealDB schema: namespaces, tables, fields, indexes, functions, events and access in SurrealQL.

The DEFINE statement can be used to specify instructions to the schema such as authentication access and behaviour, global parameters, table configurations, table events, analyzers, and indexes.

> [!NOTE]
> Before SurrealDB version 3.0.0, the `FULLTEXT ANALYZER` clause used the syntax `SEARCH ANALYZER`.

```syntax title="SurrealQL Syntax"
DEFINE [
	NAMESPACE [ OVERWRITE | IF NOT EXISTS ] @name
	| DATABASE [ OVERWRITE | IF NOT EXISTS ] @name
	| USER [ OVERWRITE | IF NOT EXISTS ] @name ON [ ROOT | NAMESPACE | DATABASE ] [ PASSWORD @pass | PASSHASH @hash ] ROLES @roles
	| TABLE [ OVERWRITE | IF NOT EXISTS ] @name
		[ DROP ]
		[ SCHEMAFULL | SCHEMALESS ]
		[ AS SELECT @projections
			FROM @tables
			[ WHERE @condition ]
			[ GROUP [ BY ] @groups ]
		]
		[ PERMISSIONS [ NONE | FULL
			| FOR select @expression
			| FOR create @expression
			| FOR update @expression
			| FOR delete @expression
		] ]
	| EVENT [ OVERWRITE | IF NOT EXISTS ] @name ON [ TABLE ] @table WHEN @expression THEN @expression
	| FIELD [ OVERWRITE | IF NOT EXISTS ] @name ON [ TABLE ] @table
		[ TYPE @type ]
		[ VALUE @expression ]
		[ ASSERT @expression ]
		[ PERMISSIONS [ NONE | FULL
			| FOR select @expression
			| FOR create @expression
			| FOR update @expression
			| FOR delete @expression
		] ]
	| PARAM [ OVERWRITE | IF NOT EXISTS ] $name VALUE @value
	| FUNCTION [ OVERWRITE | IF NOT EXISTS ] fn::@name ( [ ( @argument:@type ... ) ] ) { [@query] [RETURNS @returned] }
	| ANALYZER [ OVERWRITE | IF NOT EXISTS ] @name
		[ TOKENIZERS @tokenizers ]
		[ FILTERS @filters ]
	| INDEX [ OVERWRITE | IF NOT EXISTS ] @name ON [ TABLE ] @table [ FIELDS | COLUMNS ] @fields
		[ UNIQUE | FULLTEXT ANALYZER @analyzer [ BM25 [(@k1, @b)] ] [ HIGHLIGHTS ] ]
	| SEQUENCE [ OVERWRITE | IF NOT EXISTS ] @name
		[ BATCH @batch ]
		[ START @start ]
	| ACCESS [ OVERWRITE | IF NOT EXISTS ] @name ON [ NAMESPACE | DATABASE ]
		TYPE [
			JWT [ ALGORITHM @algorithm KEY @key | URL @url ]
			| RECORD
				[ SIGNUP @expression ]
				[ SIGNIN @expression ]
				[ WITH JWT [ ALGORITHM @algorithm KEY @key | URL @url ] [ WITH ISSUER KEY @key ] ]
		]
		[ DURATION [ FOR TOKEN @duration ] [ FOR SESSION @duration ] ]
    [ COMMENT @string ]
]
```

The [INFO](/docs/reference/query-language/statements/info.md) statement can be used to see which definition statements currently exist in a database connection. All `DEFINE` statements can be followed up with a `COMMENT`.

## Comments on definitions

A `COMMENT` is stored with the definition and returned by [`INFO`](/docs/reference/query-language/statements/info.md) (and by schema tools such as MCP `info` / `list`). Short, concrete comments help people and agents alike: they explain what a table or field is for, how values should be compared, and which graph edges or record-ID conventions matter.

Useful comments tend to include:

- What the entity represents, and what it deliberately does not store
- How to read the record ID or field (for example `record::id(id)` when the ID is the canonical name)
- How to compare or sort unusual types (ISO date strings vs datetimes)
- Cardinality or invariants ("exactly one organiser per meeting")
- The main graph paths to related tables

```surql
DEFINE TABLE person TYPE NORMAL SCHEMAFULL
	COMMENT "A person, deduplicated across notes. Has no fields: the record id IS the canonical full name, e.g. person:`Alice Chen`. Read the name with record::id(id). Meetings: person->attended->meeting.";

DEFINE TABLE meeting TYPE NORMAL SCHEMAFULL
	COMMENT "A calendar meeting. Link attendees with RELATE person->attended->meeting.";

DEFINE TABLE attended TYPE RELATION IN person OUT meeting SCHEMAFULL
	COMMENT "Attendance edge. Exactly one attendee per meeting has is_organizer = true.";

DEFINE FIELD is_organizer ON attended TYPE bool
	COMMENT "True if this person ran the meeting, false if they only attended.";
```

An example of defining a field on a table, followed by an `INFO` command for the same table:

```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: {}
}
```

An example of defining a user and a table for a database, followed by an `INFO` command for the current database:

```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"
    }
}
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/param

# DEFINE PARAM

The DEFINE PARAM statement allows you to define global (database-wide) parameters that are available to every client.

The `DEFINE PARAM` statement allows you to define global (database-wide) parameters that are available to every client.

## Requirements

- You must be authenticated as a root owner or editor, namespace owner or editor, or database owner or editor before you can use the `DEFINE PARAM` statement.
- [You must select your namespace and database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE PARAM` statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE PARAM [ OVERWRITE | IF NOT EXISTS ] $name 
    VALUE @value
    [ COMMENT @string ]
    [ PERMISSIONS [ NONE | FULL | WHERE @condition ] ]
```

## Example usage
Below shows how you can create a parameter using the `DEFINE PARAM` statement.

```surql
DEFINE PARAM $endpointBase VALUE "https://dummyjson.com";
```

Then, simply use the global parameter like you would with any variable.

```surql
http::get($endpointBase + "/products");
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define a param only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining a param in SurrealDB if you want to ensure that the param is only created if it does not already exist. If the param already exists, the `DEFINE PARAM` statement will return an error.

It's particularly useful when you want to safely attempt to define a param without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the param definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a param and overwrite an existing one if it already exists, ensuring that the latest version of the param definition is always in use

```surql
-- Create a PARAM if it does not already exist
DEFINE PARAM IF NOT EXISTS $example VALUE 123;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define a param and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing param definition. If the param already exists, the `DEFINE PARAM` statement will overwrite the existing param definition with the new one.

```surql
-- Create an PARAM and overwrite if it already exists
DEFINE PARAM OVERWRITE $example VALUE 123;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/scope

# DEFINE SCOPE

Setting scope access allows SurrealDB to operate as a web database. With scopes you can set authentication and access rules which enable fine-grained access to tables and fields.

> [!WARNING]
> This statement was deprecated in favour of `DEFINE ACCESS ... TYPE RECORD` in SurrealDB versions 2.x, and has been removed as of SurrealDB 3.0. Learn more in the [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md).

Setting scope access allows SurrealDB to operate as a web database. With scopes you can set authentication and access rules which enable fine-grained access to tables and fields.

## Requirements

- You must be authenticated as a root or namespace user before you can use the `DEFINE SCOPE` statement.
- [You must select your namespace and database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE SCOPE` statement.

## Statement syntax

```syntax title="SurrealQL Syntax"
DEFINE SCOPE [ OVERWRITE | IF NOT EXISTS ] @name SESSION @duration SIGNUP @expression SIGNIN @expression [ COMMENT @string ]
```

## Example usage
Below shows how you can create a scope using the `DEFINE SCOPE` statement.

```surql
-- Enable scope authentication directly in SurrealDB
DEFINE SCOPE account 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) )
;
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define a scope only if it does not already exist. If the scope already exists, the `DEFINE SCOPE` statement will return an error.

```surql
-- Create a SCOPE if it does not already exist
DEFINE SCOPE IF NOT EXISTS example;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/sequence

# DEFINE SEQUENCE

A DEFINE SEQUENCE statement defines a distributed generator of monotonically increasing numeric sequences.

_(since v3.0.0)_

A sequence is used to generate reliable, monotonically increasing numeric sequences in both single-node and clustered SurrealDB deployments. It uses a batch-allocation strategy to minimise coordination while guaranteeing global uniqueness.

The key features of a sequence are as follows:

* Batch allocation: Nodes request ranges of sequence values at once, reducing network chatter and coordination overhead.
* Node ownership tagging: Every batch is tagged with the requesting node's UUID to prevent overlap between nodes.
* Durable Persistence: Sequence metadata is stored in the underlying key-value store to survive restarts and network partitions.
* Concurrent, thread-safe access: A DashMap caches active sequences, allowing lock-free reads on the hot path.
* Exponential back-off with full jitter: When a batch-allocation attempt fails, the node retries with an exponential delay that includes full jitter to avoid thundering-herd effects across the cluster.
* Automatic cleanup: Listens for namespace and database-removal events and purges the corresponding sequence state.

The sequence implementation avoids contention by having each node reserve a range of sequence values, allowing it to serve multiple requests locally without requiring distributed coordination for every request. When a node exhausts its allocated range, it acquires a new batch from the distributed store.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE SEQUENCE [ OVERWRITE | IF NOT EXISTS ] @name [ BATCH @batch ] [ START @start ] [ TIMEOUT @duration ]
```

## Examples

A sequence can be created with nothing more than a name.

```surql
DEFINE SEQUENCE mySeq;
```

The `BATCH`, `START`, and `TIMEOUT` clauses can be included to configure the sequence.

```surql
DEFINE SEQUENCE mySeq2 BATCH 1000 START 100 TIMEOUT 5s;
sequence::nextval('mySeq2');
//- 100

DEFINE SEQUENCE mySeq3 BATCH 1000 START 100 TIMEOUT 0ns;
-- Possible output, since the timeout may or may not be exceeded
sequence::nextval('mySeq3');
//- 'The query was not executed because it exceeded the timeout'
```

Sequences are never rolled back, even in a failed transaction. This differs from an approach like a single record with a manually incrementing value.

```surql
DEFINE SEQUENCE seq;
CREATE my:counter SET val = 0;
sequence::nextval("seq");        -- 0
my:counter.val;                  -- 0

BEGIN TRANSACTION;
sequence::nextval("seq");        -- 0
UPDATE my:counter SET val += 1;  -- my:counter.val = 1
CANCEL TRANSACTION;
-- my:counter.val now rolled back to 0

sequence::nextval("seq");        -- 2
UPDATE my:counter SET val += 1;  -- 1
```

## See also

* [Sequence functions](/docs/reference/query-language/functions/database-functions/sequence.md)

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/table

# DEFINE TABLE

The DEFINE TABLE statement allows you to declare your table by name, enabling you to apply strict controls to a table's schema and access permissions.

The `DEFINE TABLE` statement allows you to declare your table by name, enabling you to apply strict controls to a table's schema by making it `SCHEMAFULL`, create a foreign table view, and set permissions specifying what operations can be performed on the table.

> [!NOTE]
> The fields of a table are not defined using `DEFINE TABLE`, but via individual [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statements.

## Requirements

- You must be authenticated as a root owner or editor, namespace owner or editor, or database owner or editor before you can use the `DEFINE TABLE` statement.
- [You must select your namespace and database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE TABLE` statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE TABLE [ OVERWRITE | IF NOT EXISTS ] @name
	[ DROP ]
	[ SCHEMAFULL | SCHEMALESS ]
	[ TYPE [ ANY | NORMAL | RELATION [ IN | FROM ] @table [ OUT | TO ] @table [ ENFORCED ]]]
	[ AS SELECT @projections
		FROM @tables
		[ WHERE @condition ]
		[ GROUP [ BY @groups | ALL ] ]
	]
	[ CHANGEFEED @duration [ INCLUDE ORIGINAL ] ]
	[ PERMISSIONS [ NONE | FULL
		| FOR select @expression
		| FOR create @expression
		| FOR update @expression
		| FOR delete @expression
	] ]
    [ COMMENT @string ]
```

## Example usage

Below shows how you can create a table using the `DEFINE TABLE` statement.

```surql
-- Declare the name of a table.
DEFINE TABLE reading;
```

### Comments

Add a `COMMENT` when the table's role is not obvious from the name alone. The text is returned by [`INFO`](/docs/reference/query-language/statements/info.md) and by agent-facing schema tools, so prefer operational detail (record-ID conventions, graph paths, invariants) over a one-line restatement of the table name. See [Comments on definitions](/docs/reference/query-language/statements/define/overview.md#comments-on-definitions) for a fuller pattern.

```surql
DEFINE TABLE person TYPE NORMAL SCHEMAFULL
	COMMENT "Deduplicated person. The record id is the canonical full name (person:`Alice Chen`); use record::id(id) to read it. Edges: person->attended->meeting.";
```

The following example uses the `DROP` portion of the `DEFINE TABLE` statement. Marking a table as `DROP` disallows creating or updating records.

`DROP` tables are useful in combination with events or foreign (view) tables, as you can compute a record and essentially drop the input.

```surql
-- By marking a table as DROP, you disallow any records to be created or updated.
-- Records that currently exist in the table will not automatically be deleted, you can still remove them manually.
DEFINE TABLE reading DROP;
```

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` will show the changes that have taken place during this time.

If an entry holds a change to an existing record, the diff will show the operation needed to modify the record to the state immediately preceding its current state. In other words, the diff included is a reverse diff.

```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";
UPDATE reading SET is_interesting = true;

-- 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;
```

```surql title="Output without INCLUDE ORIGINAL"
[
	{
		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: 116395447100768256
	},
	{
		changes: [
			{
				update: {
					id: reading:bqlejs8fx4phgbo6g5ve,
					story: 'Once upon a time'
				}
			}
		],
		versionstamp: 116395447100833792
	},
	{
		changes: [
			{
				update: {
					id: reading:fa8o65ccxykfxqqz91yo,
					story: 'there was a database'
				}
			}
		],
		versionstamp: 116395447100833793
	},
	{
		changes: [
			{
				update: {
					id: reading:bqlejs8fx4phgbo6g5ve,
					is_interesting: true,
					story: 'Once upon a time'
				}
			},
			{
				update: {
					id: reading:fa8o65ccxykfxqqz91yo,
					is_interesting: true,
					story: 'there was a database'
				}
			}
		],
		versionstamp: 116395447100833794
	}
]
```

```surql title="Output with INCLUDE ORIGINAL"
[
	{
		changes: [
			{
				define_table: {
					changefeed: {
						expiry: 3d,
						original: true
					},
					drop: false,
					id: 0,
					kind: {
						kind: 'ANY'
					},
					name: 'reading',
					permissions: {
						create: false,
						delete: false,
						select: false,
						update: false
					},
					schemafull: false
				}
			}
		],
		versionstamp: 116395448975818752
	},
	{
		changes: [
			{
				update: {
					id: reading:kypj876yubk4fnnja93b,
					story: 'Once upon a time'
				}
			}
		],
		versionstamp: 116395448975818753
	},
	{
		changes: [
			{
				update: {
					id: reading:0i2qwoi053nmrl4k2wm2,
					story: 'there was a database'
				}
			}
		],
		versionstamp: 116395448975818754
	},
	{
		changes: [
			{
				current: {
					id: reading:0i2qwoi053nmrl4k2wm2,
					is_interesting: true,
					story: 'there was a database'
				},
				update: [
					{
						op: 'remove',
						path: '/is_interesting'
					}
				]
			},
			{
				current: {
					id: reading:kypj876yubk4fnnja93b,
					is_interesting: true,
					story: 'Once upon a time'
				},
				update: [
					{
						op: 'remove',
						path: '/is_interesting'
					}
				]
			}
		],
		versionstamp: 116395448975818755
	}
]
```

## 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.

> [!NOTE]
> Schemafull tables are implicitly type [`NORMAL`](#table-with-specialized-type-clause) tables by default.

```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);
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;

-- SEE IT IN ACTION
-- 1: Try to add a user with an undefined field, 'photoURI'.
CREATE user CONTENT {
    firstName: 'Tobie',
    lastName: 'Hitchcock',
    email: 'Tobie.Hitchcock@surrealdb.com',
    photoURI: 'photo/yxCFi22Jw2.webp'
};
-- 2: The statement fails: a schemafull table rejects any field that has
--    not been defined. (Before version 3.0, the undefined field was
--    silently dropped instead.)

-- 3: Create the user with defined fields only.
CREATE user CONTENT {
    firstName: 'Tobie',
    lastName: 'Hitchcock',
    email: 'Tobie.Hitchcock@surrealdb.com'
};

-- 4: Query the data
SELECT * FROM user;
```

## 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);
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;

-- SEE IT IN ACTION - Example 1
-- 1: Add a user with all required fields and an undefined one.
CREATE user:tobie SET firstName = 'Tobie', lastName = 'Hitchcock', email = 'Tobie.Hitchcock@surrealdb.com', photoURI = 'photo/yxCFi22Jw2.webp';
-- 2: Statement will succeed because user is a SCHEMALESS table.

-- SEE IT IN ACTION - Example 2
-- 1: Add a user with an invalid email address and include a new field that was never defined.
CREATE user:jaime SET firstName = 'Jamie', lastName = 'Hitchcock', email = 'Jamie.Hitchcock', photoURI = 'photo/yxCFi22Jw2.webp';
-- 2: Statement will fail because the value for email was not valid.
```

## Interaction between fields

While a `DEFINE TABLE` statement represents a template for any subsequent records to be created, a `DEFINE FIELD` statement pertains to concrete field data of a record. As such, a `DEFINE FIELD` statement gives access to the record's other fields through their names, as well as the current field through the [`$value`](/docs/reference/query-language/language-primitives/parameters.md#value) parameter.

```surql
DEFINE TABLE person SCHEMAFULL;

DEFINE FIELD first_name
  ON TABLE person TYPE string ASSERT string::len($value) < 20;
DEFINE FIELD last_name 
  ON TABLE person TYPE string ASSERT string::len($value) < 20;
DEFINE FIELD name      
  ON TABLE person             VALUE first_name + ' ' + last_name;

-- Creates a `person` with the name "Bob Bobson"
CREATE person SET first_name = "Bob", last_name = "Bobson";
```

## 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.

```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;
```

There are a few important things which make our views far more powerful than a typical relational database view and a few limitations to keep in mind.

Starting with what makes them powerful. Our pre-computed table views are most similar to event-based, incrementally updating, materialised views. Let's explain what that means.

- Event-based, meaning that when you run add or remove data from the underlying table, in our example, the `review` table, it triggers a matching event on the `avg_product_review` table view.
- Materialised view, meaning that the first time we run the table view query, it will run the query like a normal `SELECT` statement, but then materialise the result. Instead of normal views which behave like bookmarked `SELECT` queries, that just look like tables to the user.
- Incrementally updating, meaning that for any subsequent run, it will listen for the event trigger and perform the most efficient operation possible to always keep the result up to date, instead of just running the `SELECT` statement again.

While this functionality can be replicated in many other databases, it is usually only done by expert users as it can be very complicated to set up and maintain. Therefore, the true power of our pre-computed table views is making this advanced functionality accessible to everyone.

As mentioned though, there are a few limitations to keep in mind.

- First, while subsequent runs are very efficient, the initial run of large analytical queries can be slow and use a lot of resources, because its just a normal `SELECT` statement. Therefore indexing and query optimisation are still very important.
- Second, while both graph relations and record links are supported, the table view update event, only gets triggered based on the table we have in our `FROM` clause. In our case, just the `review` table, not the `product` we are also using in the query. Meaning that if you delete a `review` the `avg_product_review`  will reflect that in near real-time. However if you delete a `product`, it will still show up in `avg_product_review`.
- Third, view tables are **read-only**. Since 3.2.0, `CREATE`, `INSERT`, `UPSERT`, `UPDATE`, `DELETE`, and `RELATE` against a view table are rejected - records are computed from the source query only. The one exception is import: rows emitted for a view during export can be re-applied under `OPTION IMPORT`, to allow export → import round-trips to keep working.

Also note that table views are not triggered when importing data.

## 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 stores `PERMISSIONS NONE`. That 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 [`$auth`](/docs/learn/security/authorization/permissions-and-row-level-security.md)).

```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`. This is because 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 [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md#setting-permissions-on-fields) and [Permissions & row-level security](/docs/learn/security/authorization/permissions-and-row-level-security.md).

### Writes inside a permission clause

A permission clause is evaluated with permissions bypassed, so a write inside one runs unchecked. Whether that is allowed depends on which clause it sits in.

**A `FOR select` clause may never modify data.** A read must never trigger a write, so the write is blocked when the clause is evaluated. From 3.3.0 that is the only check: the definition is stored without complaint and the failure arrives the first time a read reaches the write, with `A PERMISSIONS clause cannot contain a statement that modifies data`.

```surql
-- Accepted, but every read of `post` now fails
DEFINE TABLE post PERMISSIONS FOR select WHERE (CREATE audit SET at = time::now()) OR true;
```

**`FOR create`, `FOR update`, and `FOR delete` clauses may modify data**, because they are evaluated during a write that is already taking place. This supports patterns such as audit logging from within the clause.

```surql
DEFINE FUNCTION fn::audit($id: record) -> bool { CREATE audit SET record = $id, at = time::now(); RETURN true; };

DEFINE TABLE post PERMISSIONS
	FOR create, update WHERE fn::audit($value.id) AND user = $auth.id;
```

Nevertheless, [`DEFINE EVENT`](/docs/reference/query-language/statements/define/event.md) is the preferred option for this. An event is evaluated with the caller's own permissions, states the side effect where a reader expects to find it, and does not run on every access check.

The check runs where the write is reached, so it is exact however the write is arrived at - written into the clause directly, reached through a [custom function](/docs/reference/query-language/statements/define/function.md), or through `eval`, a script or a closure passed as data. Call depth and indirection make no difference to it.

> [!NOTE]
> The above behaviour changed in 3.3.0. On 3.2 and earlier a writing clause is refused outright when the table is defined, so a guard that 3.2 rejects is stored on 3.3 and fails the first time it is evaluated.

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define a table only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining a table in SurrealDB if you want to ensure that the table is only created if it does not already exist. If the table already exists, the `DEFINE TABLE` statement will return an error.

It's particularly useful when you want to safely attempt to define a table without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the table definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a table and overwrite an existing one if it already exists, ensuring that the latest version of the table definition is always in use

```surql
-- Create a TABLE if it does not already exist
DEFINE TABLE IF NOT EXISTS reading;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define a table and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing table definition. If the table already exists, the `DEFINE TABLE` statement will overwrite the existing table definition with the new one.

```surql
-- Create an TABLE and overwrite if it already exists
DEFINE TABLE OVERWRITE example;
```

## 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;
```

```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;
```

## 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;
```

```surql title="Output"
[
	{
		distance: 12.4f,
		id: road_to:pacwucj25a056hhs2s5h,
		in: city:one,
		out: city:two,
		slope: 5.4f
	}
]
```

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"
```

The endpoint check is an admission gate on the write path, so it is deferred during an [import](/docs/reference/cli/surrealdb-cli/commands/import.md), alongside the field, event, view and changefeed checks that already defer there. An export writes tables in name order, which can place an enforced relation table before the tables it points at, and the check would otherwise reject every edge as its endpoints did not yet exist.

> [!WARNING]
> Before SurrealDB 3.3.0, restoring an export of a database containing an `ENFORCED` relation table silently dropped its edges whenever the relation table sorted before its endpoint tables - `knows` before `person`, for example. The vertices and their counts restored correctly, so the loss only showed up on a traversal. If you restored such an export on an earlier version, check the edge counts before relying on the result.

## Inserting data from undefined fields on a `SCHEMAFULL` table

_(since v3.0.0)_

Previously, an insert into a `SCHEMAFULL` table would work even if extra data was present. The query below shows this behaviour, in which the data inside `unneeded_data` is simply filtered out.

```surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON user TYPE string;
DEFINE FIELD user_num ON user TYPE int;

CREATE ONLY user CONTENT { 
    name: "Billy", 
    user_num: 100,
    unneeded_data: {
        some: "other",
        needless: "data"
    }
};
```

```surql title="Output"
{
    id: user:r38bg4fnp9nurksd06nl,
    name: 'Billy',
    user_num: 100
}
```

While convenient, this led to the possibility of a typo leading to unexpected results.

```surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON user TYPE string;
DEFINE FIELD user_num ON user TYPE option<int>;

CREATE ONLY user CONTENT { 
    name: "Billy", 
    User_num: 100 // Note: field is capitalized
};
```

The output shows that the `user` lacks a value for `user_num` even though the query above intended to provide this data.

```surql
{
	id: user:jn4762t7oyure86fe3qk,
	name: 'Billy'
}
```

The same query in SurrealDB 3.0 now returns an error.

```surql
"Found field 'User_num', but no such field exists for table 'user'"
```

To avoid an error when working with data that contains unneeded fields, use `.{}` (the destructuring operator) to pass on only the necessary fields.

```surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON user TYPE string;
DEFINE FIELD user_num ON user TYPE int;

CREATE ONLY user CONTENT { 
    name: "Billy", 
    user_num: 100,
    unneeded_data: {
        some: "other",
        needless: "data"
    }
}.{
    -- Only pass on the name and user_num data
    name,
    user_num
};
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/token

# DEFINE TOKEN

SurrealDB can work with third-party authentication providers such as OpenID Connect providers, OAuth providers and other trusted third parties.

> [!WARNING]
> This statement was deprecated in favour of [`DEFINE ACCESS ... TYPE JWT`](/docs/reference/query-language/statements/define/access/jwt.md) and [`DEFINE ACCESS ... TYPE RECORD ... WITH JWT`](/docs/reference/query-language/statements/define/access/record.md) in SurrealDB versions 2.x, and has been removed as of SurrealDB 3.0. Learn more in [define access documentation](/docs/reference/query-language/statements/define/access.md).

SurrealDB can work with third-party authentication providers such as OpenID Connect providers, OAuth providers and other trusted parties providing JWT (JSON Web Tokens, also referred to in this page as “tokens”). Let's say that your provider issues your client (e.g. a user or a service) a JWT once it has authenticated. By using the DEFINE TOKEN statement, you can set the public key or shared secret that will be used to verify the authenticity of the token.

This verification is performed automatically by SurrealDB when provided with a JWT through any of its interfaces (i.e. the [HTTP REST API](/docs/reference/rest-api/http-protocol.md) through the “Authorization” header or [any of the SDKs](/docs/languages.md) through the “Authenticate” methods) before trusting the claims contained in the token and allowing SurrealQL queries to access the values of those claims.

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE TOKEN [ OVERWRITE | IF NOT EXISTS ] @name ON [ NAMESPACE | DATABASE | SCOPE @scope ] TYPE @type VALUE @value [ COMMENT @string ]
```

## Verification types

When defining a token, its type describes the cryptographic algorithm or specification that will be used to verify the token. This can be an HMAC algorithm, a public-key cryptography algorithm or a remote JWKS object containing all the required information to verify the token. When not specified, the type is defined as the `HS256` HMAC cryptographic algorithm.

### Hash-based message authentication code (HMAC)

With HMAC algorithms (`HS256`,`HS384`,`HS512`) the value of the defined token will be the secret used both to sign (by the issuer of the token) and verify (by SurrealDB) the token. Anyone with access to this secret will be able to issue tokens with arbitrary claims which will be trusted by SurrealDB.

The following example shows the definition of a token using an HMAC algorithm.

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Set the name of the token
DEFINE TOKEN token_name
  -- Use this token provider for database authorization
  ON DATABASE
  -- Specify the cryptographic signature algorithm used to verify the token
  TYPE HS512
  -- Specify the secret used to sign and verify the authenticity of the token
  VALUE "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsU2H2kGiOpU4GbAF1bCiXmM1b3JGgleBzz7rsrz6VvYEM4q3CLkcO8CMBIlhwhzWmy8"
;
```

### Public-key cryptography

With public-key cryptography algorithms (`EDDSA`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`, `RS256`, `RS384`, `RS512`) the value of the defined token will be the public key used to verify the signature of the token. This value is not secret and should be provided by the issuer of the tokens. Tokens will be signed using the private key, known only to the issuer. The public key value should be provided to SurrealDB including its header and footer. Any whitespace will be trimmed.

The following example shows the definition of a token using a public-key cryptography algorithm.

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Set the name of the token
DEFINE TOKEN token_name
  -- Use this token provider for database authorization
  ON DATABASE
  -- Specify the cryptographic signature algorithm used to verify the token
  TYPE RS256
  -- Specify the public key used to verify the authenticity of the token
  VALUE "-----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-----"
;
```

### JSON web key set (JWKS)

With JWKS, a set of JWK (JSON Web Key) objects will be dynamically fetched from a remote location and used to verify tokens following the [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517) specification. When defining a JWKS token verification method, its value should contain a valid URL that is reachable by SurrealDB and allowed by the configured network [capabilities](/docs/learn/security/authorization/capabilities.md). This URL should point to a valid JWKS object (as described in [Section 5 of RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517#section-5)) in the form of a JSON document. This is the recommended method to integrate with authentication providers that support JWKS. Providers like [Google](https://developers.google.com/identity/openid-connect/openid-connect#discovery), [AWS Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html#amazon-cognito-user-pools-using-tokens-manually-inspect), [Azure Active Directory](https://azure.github.io/azure-workload-identity/docs/installation/self-managed-clusters/oidc-issuer/jwks.html), [Auth0](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets), [Keycloak](https://documentation.cloud-iam.com/how-to-guides/configure-remote-jkws.html) or [OneLogin](https://developers.onelogin.com/authentication/tools/jwt) provide JWKS endpoints to verify tokens issued by their services.

The following example shows the definition of a token using a JWKS.

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Set the name of the token
DEFINE TOKEN token_name
  -- Use this token provider for database authorization
  ON DATABASE
  -- Specify the JWKS specification used to verify the token
  TYPE JWKS
  -- Specify the URL where the JWKS object can be found
  VALUE "https://example.com/.well-known/jwks.json"
;
```

Validating tokens generated by third-party authentication providers using JWKS ensures that keys can be revoked directly from the third-party service and will no longer be accepted by SurrealDB after the local cache for those keys expires. Likewise, it ensures that token verification will not break if keys are rotated, as any new keys will be automatically fetched from the authentication provider if a JWT is received containing a new key identifier in its `kid` header.

To avoid performing requests to the remote URL for each token that is verified, SurrealDB caches every JWKS object that it pulls for a period of 12 hours. The cache can be purged earlier (e.g. in the event a key is compromised) by restarting the SurrealDB server. If a JWT is received containing a reference to a new key identifier in its `kid` header, the JWKS object will be fetched again and updated in the cache if the key identifier is found in the remote JWKS object; this operation will only be performed once every 5 minutes to prevent malicious actors from abusing this process to perform denial of service.

## Requirements

- To `DEFINE TOKEN ... ON NAMESPACE ...` you must have root or namespace level access.
- To `DEFINE TOKEN ... ON DATABASE ...` you must have root, namespace, or database level access.
- To `DEFINE TOKEN ... ON SCOPE ...` you must have root, namespace, or database level access.
- [You must select your namespace and/or database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE TOKEN` statement for database or namespace tokens.

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define a token only if it does not already exist. If the token already exists, the `DEFINE TOKEN` statement will return an error.

```surql
-- Create a TOKEN if it does not already exist
DEFINE TOKEN IF NOT EXISTS example ON SCOPE example TYPE HS512 VALUE "example";
```

## Using tokens

The `DEFINE TOKEN` statement lets you specify the amount of permission granting authority you want to give to a token issuer. You are able to specify if the provider can grant namespace, database, or scope level access to token holders. For this to work, the JWT issued to be used with SurrealDB must contain claims to specify which namespace, database or scope the token bearer is authorised to act on.

The following claims should be added to the JWT payload by the issuer of the token:

- `exp`: The token expiration Unix time. The token will not be valid after.
- `tk`: The name that you chose when defining the token.
- `ns`: The namespace that the token is issued for.
- `db`: The database that the token is issued for.
- `sc`: The scope that the token is issued for.

The names of these claims can be in all lowercase (i.e. `tk`) or all uppercase (i.e. `TK`), and can be optionally prefaced with the `https://surrealdb.com` namespace (e.g. `https://surrealdb.com/tk`) in order to separate claims directed to SurrealDB from claims directed to other services. When using a namespace, the claim name can also be used without abbreviation, such as in `https://surrealdb.com/token` or `https://surrealdb.com/scope`. Even when present in the token with a namespace prefix, [SurrealDB claims](https://github.com/surrealdb/surrealdb/blob/main/core/src/iam/token.rs) are directly accessible via the `$token` parameter (e.g. `$token.sc`), whereas custom claims will need to include the namespace prefix (e.g. `$token['https://surrealdb.com/pet_name']`) to be accessed in the same way.

The following optional claims are also processed by SurrealDB:

- `id`: The identifier of the resource (e.g. user) associated with the token.
- `nbf`: The token acceptance Unix time. The token will not be valid before.

The expected claims depend on the level at which the token was defined:

- For tokens defined `ON NAMESPACE`: `exp`, `tk`, `ns`.
- For tokens defined `ON DATABASE`: `exp`, `tk`,`ns`,`db`.
- For tokens defined `ON SCOPE`: `exp`, `tk`, `ns`,`db`, `sc`, optionally `id`.

For tokens defined `ON NAMESPACE` and `ON DATABASE`, the optional `rl` claim containing an array of capitalized [system user roles](/docs/reference/query-language/statements/define/user.md#roles) (e.g. `["Viewer", "Editor", "Owner"]`) can be provided. Doing so will apply the access policy for those roles to any action made using the token. By default, tokens without the `rl` claim will only have the `Viewer` role.

When calling any of the SurrealDB interfaces using a JWT, SurrealQL queries will gain access to the claims in the token through the `$token` variable. For example, if the token contains custom claims such as “name” or “email”, the values of those claims will be accessible through `$token.name` and `$token.email`.

Additionally, when the `id` claim is present in the token, the fields of the record matching the identifier specified will be accessible through the `$auth` variable. For example, if the value of the `id` claim is `user:73q1bl039y6k8z80v55d`, and user records have fields such as “name” or “email”, then `$auth.name` and `$auth.email` can be used to access those values for the `user:73q1bl039y6k8z80v55d` record specifically, without them being present in the JWT.

The signature of the token is verified with method defined when creating the token. If the signature of the token is invalid, calls to SurrealDB interfaces using that token will fail.

### Namespace

Namespace tokens can be used to select, create, update, and delete on all tables in all databases, as well as to define and remove databases and tables from the namespace.

```surql
-- Specify the namespace for the token
USE NS abcum;

-- Set the name of the token
DEFINE TOKEN token_name
  -- Use this OAuth provider for namespace authorization
  ON NAMESPACE
  -- Specify the cryptographic signature algorithm used to verify the token
  TYPE HS512
  -- Specify the public key so we can verify the authenticity of the token
  VALUE "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsU2H2kGiOpU4GbAF1bCiXmM1b3JGgleBzz7rsrz6VvYEM4q3CLkcO8CMBIlhwhzWmy8"
;
```
The namespace token payload should at least include the following claims when used to authenticate with SurrealDB.

```json title="JWT Payload"
{
  "exp": 2147483647,
  "tk": "token_name",
  "ns": "abcum"
}
```

### Database

Database tokens can be used to select, create, update, and delete on all tables in a specific database, as well as to define and remove tables from the database.

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Set the name of the token
DEFINE TOKEN token_name
  -- Use this OAuth provider for database authorization
  ON DATABASE
  -- Specify the cryptographic signature algorithm used to verify the token
  TYPE HS512
  -- Specify the public key so we can verify the authenticity of the token
  VALUE "sNSYneezcr8kqphfOC6NwwraUHJCVAt0XjsRSNmssBaBRh3WyMa9TRfq8ST7fsU2H2kGiOpU4GbAF1bCiXmM1b3JGgleBzz7rsrz6VvYEM4q3CLkcO8CMBIlhwhzWmy8"
;
```

The database token payload should at least include the following claims when used to authenticate with SurrealDB.

```json title="JWT Payload"
{
  "exp": 2147483647,
  "tk": "token_name",
  "ns": "abcum",
  "db": "app_vitalsense"
}
```

### Scope

Since the origin of the claims in the JWT is verified, those claims can be used within SurrealQL in the context of a scope in order to provide table and field authorization through an external authenticator using OpenID Connect, OAuth or simply acting as a trusted issuer of a JWT.

This can be done by leveraging table permissions to allow or disallow access depending on the values of the claims in the verified token. For example, these claims can be compared with the records in a table to only return those matching certain criteria.

The scope for which the token was issued will be accessible to SurrealQL through the `$scope` variable, corresponding to the contents of the `sc` claim. External authorization providers may provide additional scopes that will not be accessible in this way, and instead should be accessed as any other claim through the `$token` variable.

Bear in mind that table and field permissions only apply to scope level tokens Access provided by namespace and database tokens is above table-level permissions. When application users will be the ones directly authenticating with JWT, scope tokens are most likely the right choice.

The following example shows how scope tokens can be used to grant authorization by verifying that the “email” claim in the token matches the email used as the index of a user table:

```surql
-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Necessary in order to define a scope token
DEFINE SCOPE users;

DEFINE TOKEN token_name ON SCOPE users TYPE RS256 VALUE "-----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-----";

DEFINE TABLE user SCHEMAFULL
  -- Authorized users can select, update, delete and create user records
  PERMISSIONS FOR select, update, delete, create
  -- The current scope must be "users"
  WHERE $scope = "users"
  -- The email of the user being queried must match the email claim in the token
  -- Only matching records will be changed or returned
  AND email = $token.email
;

DEFINE INDEX email ON user FIELDS email UNIQUE;
DEFINE FIELD email ON user TYPE string ASSERT string::is_email($value);
DEFINE FIELD name ON user TYPE string;
DEFINE FIELD nickname ON user TYPE string;
DEFINE FIELD picture ON user TYPE string;
```

You may also use permissions clauses to perform additional verification on other JWT claims (e.g. verifying that the iss claim matches a specific principal using $token.iss) that may be required or recommended by a the provider of the token.

The scope token payload should at least include the following claims when used to authenticate with SurrealDB.

```json title="JWT Payload"
{
  "exp": 2147483647,
  "tk": "token_name",
  "ns": "abcum",
  "db": "app_vitalsense",
  "sc": "users"
}
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/define/user

# DEFINE USER

A DEFINE USER statement can be used to create system users on SurrealDB.

Use the `DEFINE USER` statement to create system users on SurrealDB.

> [!NOTE]
> While existing logins still function, the DEFINE LOGIN statement has been replaced with DEFINE USER.

## Requirements

- You must be authenticated with a user that has enough permissions. Only the OWNER built-in role grants permissions to create users.
- You must be authenticated with a user that has permissions on the level where you are creating the user:
  - Root users with owner permissions can create Root, Namespace and Database users.
  - Namespace users with owner permissions can create Namespace and Database users
  - Database users with owner permissions can create Database users.
- To select the level where you want to create the user, [you may need to select a namespace and/or database](/docs/reference/query-language/statements/use.md) before you can use the `DEFINE USER` statement for database or namespace tokens.

> [!NOTE]
> You cannot use the DEFINE USER statement to create a record user.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DEFINE USER [ OVERWRITE | IF NOT EXISTS ] @name
	ON [ ROOT | NAMESPACE | DATABASE ]
	[ PASSWORD @pass | PASSHASH @hash ]
	[ ROLES @roles ]
	[ DURATION ( FOR TOKEN @duration [ , ] [ FOR SESSION @duration ] | FOR SESSION @duration [ , ] [ FOR TOKEN @duration ] ) ]
  [ COMMENT @string ]
```

## Example usage

The following example shows how you can create a `ROOT` user using the `DEFINE USER` statement.

```surql
-- Create the user with an owner role and some example durations
DEFINE USER username ON ROOT PASSWORD '123456' ROLES OWNER DURATION
  FOR SESSION 15m,
  FOR TOKEN 5s;
```

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 coworkers high up the org chart that are not particularly tech-savvy.

```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";
```

The following example shows how you can create a `NAMESPACE` user using the `DEFINE USER` statement.

```surql
-- Specify the namespace
USE NS abcum;
-- Create the user with an editor role and some example durations
DEFINE USER username ON NAMESPACE PASSWORD '123456' ROLES EDITOR DURATION
  FOR SESSION 12h,
  FOR TOKEN 1m;
```

The following example shows how you can create a `DATABASE` user using the `DEFINE USER` statement.

```surql
-- Specify the namespace and database for the user
USE NS abcum DB app_vitalsense;
-- Create the user with a viewer role and some example durations
DEFINE USER username ON DATABASE PASSWORD '123456' ROLES VIEWER DURATION
  FOR SESSION 5d,
  FOR TOKEN 2h;
```

## Using `IF NOT EXISTS` clause

The `IF NOT EXISTS` clause can be used to define a user only if it does not already exist. You should use the `IF NOT EXISTS` clause when defining a user in SurrealDB if you want to ensure that the user is only created if it does not already exist. If the user already exists, the `DEFINE USER` statement will return an error.

It's particularly useful when you want to safely attempt to define a user without manually checking its existence first.

On the other hand, you should not use the `IF NOT EXISTS` clause when you want to ensure that the user definition is updated regardless of whether it already exists. In such cases, you might prefer using the `OVERWRITE` clause, which allows you to define a user and overwrite an existing one if it already exists, ensuring that the latest version of the user definition is always in use

```surql
-- Create a USER if it does not already exist
DEFINE USER IF NOT EXISTS example
  ON ROOT PASSWORD "example" ROLES OWNER;
```

## Using `OVERWRITE` clause

The `OVERWRITE` clause can be used to define a user and overwrite an existing one if it already exists. You should use the `OVERWRITE` clause when you want to modify an existing user definition. If the user already exists, the `DEFINE USER` statement will overwrite the existing user definition with the new one.

```surql
-- Create an USER and overwrite if it already exists
DEFINE USER OVERWRITE example ON ROOT PASSWORD "example" ROLES OWNER;
```

## Roles

Currently, only the built-in roles OWNER, EDITOR and VIEWER are available.

<table>
<thead>
  <tr>
    <th>Role</th>
    <th>Description</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>OWNER</td>
    <td>Can view and edit any resource on the user's level or below, including user and token (IAM) resources.<br/>It also grants full permissions for child resources that support the `PERMISSIONS` clause (tables, fields, etc.)</td>
  </tr>
  <tr>
    <td>EDITOR</td>
    <td>Can view and edit any resource on the user's level or below, but not users or token (IAM) resources<br/>It also grants full permissions for child resources that support the `PERMISSIONS` clause (tables, fields, etc.)</td>
  </tr>
  <tr>
    <td>VIEWER</td>
    <td>Grants permissions to view any resource on the user's level or below, but not edit.<br/>It also grants view permissions for child resources that support the `PERMISSIONS` clause (tables, fields, etc.)</td>
  </tr>
</tbody>
</table>

## Duration

The duration clause specifies the duration of the token returned after successful authentication with a password or passhash as well as the duration of the session established both using a password or passhash and the aforementioned token. The difference between these concepts is explained in the [expiration](/docs/learn/security/authentication/users.md#expiration) documentation.

## SCRAM credentials for Postgres clients

_(since v3.3.0)_

When a system user is defined or updated with a plaintext **`PASSWORD`**, SurrealDB automatically derives and stores **SCRAM-SHA-256 verifier material** (PostgreSQL format, PBKDF2-HMAC-SHA-256 with 4096 iterations) alongside the existing Argon2 **`PASSHASH`**. This material is used by the [Postgres wire protocol](/docs/reference/rest-api/postgres-protocol.md) listener for SASL challenge - response authentication. You do not call any crypto functions - derivation is entirely automatic.

| Clause | Argon2 hash | SCRAM verifier |
| --- | --- | --- |
| `PASSWORD '…'` | Derived and stored | Derived and stored |
| `PASSHASH '…'` | Stored as given | Not stored (no plaintext available) |

`ALTER USER … PASSWORD '…'` regenerates both the Argon2 hash and the SCRAM verifier. `ALTER USER … PASSHASH '…'` updates the hash and **clears** any existing SCRAM verifier.

Users created before this release, or defined with **`PASSHASH` only**, have no SCRAM material until you set a plaintext password. Those users can still authenticate on the Postgres port via **cleartext password** fallback (prefer TLS in production).

---

Source: https://surrealdb.com/docs/reference/query-language/statements/delete

# DELETE

The DELETE statement can be used to delete records from the database.

The `DELETE` statement can be used to delete records from the database.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
DELETE [ FROM | ONLY ] @targets
	[ WHERE @condition ]
	[ RETURN NONE | RETURN BEFORE | RETURN AFTER | RETURN DIFF | RETURN @statement_param, ... ]
	[ TIMEOUT @duration ]
	[ EXPLAIN [ FULL ]]
;
```

## Example usage

### Basic usage

The following queries shows basic usage of the DELETE statement, which is used to delete records from a table or a graph edge.

Deleting records can be done in multiple ways.

Specifying only the table name will delete all the records from a table. Note that a `DELETE` statement returns nothing (i.e. an empty array) by default.

```surql
-- Delete all records from a table
DELETE person;
```

```surql title="Output"
[]
```

A `DELETE` statement on a specific ID will delete a single record.

```surql
-- Delete a record with a specific numeric id
DELETE person:100;

-- Delete a record with a specific string id
DELETE person:tobie;
```

The `ONLY` keyword unwraps the result, returning a single object instead of an array holding one object. It governs the shape of the output rather than the number of records the statement deletes.

**After 3.0.2**

A `DELETE` statement returns nothing by default, so `DELETE ONLY` returns `NONE` and deletes the record.

```surql
CREATE ONLY person:tobie;
DELETE ONLY person:tobie;
```

```surql title="Output"
NONE
```

A record that is already gone also returns `NONE`, so the statement is safe to repeat.

```surql
CREATE ONLY person:tobie;
DELETE ONLY person:tobie;
DELETE ONLY person:tobie;
```

```surql title="Output"
NONE
```

**Before 3.0.2**

`DELETE ONLY` had to be followed by a `RETURN BEFORE` clause. Without one the statement returned an error, because the empty default output did not match the single object that `ONLY` expected.

```surql
DELETE ONLY person:tobie;
```

```surql title="Output"
'Expected a single result output when using the ONLY keyword'
```

Add a `RETURN BEFORE` clause to return the deleted record as a single object.

```surql
CREATE ONLY person:tobie;
DELETE ONLY person:tobie RETURN BEFORE;
```

```surql title="Output"
{ id: person:tobie }
```

As `ONLY` checks the output and not the target, it raises an error when a `RETURN` clause yields more than one record.

```surql
CREATE person:one, person:two, person:three;
DELETE ONLY person RETURN BEFORE;
```

> [!WARNING]
> Without a `RETURN` clause there is no output to check, so `ONLY` places no limit on how many records are deleted. `DELETE ONLY person` removes every record in the `person` table and returns `NONE`. Target a record ID, or use the `LIMIT 1` pattern below, when a statement should reach at most one record.

A `DELETE` statement has no `LIMIT` clause, so pass a `SELECT` subquery with `LIMIT 1` as the target to cap it at a single record.

```surql
CREATE person:one, person:two, person:three;
DELETE ONLY (SELECT * FROM ONLY person LIMIT 1) RETURN BEFORE;
SELECT VALUE id FROM person;
```

### Deleting records based on conditions

The delete statement supports conditional matching of records using a `WHERE` clause. If the expression in the `WHERE` clause evaluates to true, then the respective record will be deleted.

```surql
-- Update all records which match the condition
DELETE city WHERE name = 'London';
```

By default, the delete statement does not return any data, returning only an empty array if the statement succeeds completely. Specify a `RETURN` clause to change the value which is returned for each document that is deleted.

```surql
-- Don't return any result (the default)
DELETE user WHERE age < 18 RETURN NONE;

-- Return the changeset diff
DELETE user WHERE interests CONTAINS 'reading' RETURN DIFF;

-- Return the record before changes were applied
DELETE user WHERE interests CONTAINS 'reading' RETURN BEFORE;

-- Return the record after changes were applied
DELETE user WHERE interests CONTAINS 'reading' RETURN AFTER;
```

An important point to know when using a `WHERE` clause is that it performs a check on the [truthiness](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness) of a value, namely whether a value exists and is not a default value like 0, an empty string, empty array, and so on.

As such, the `DELETE` query below that only specifies `WHERE age` essentially evaluates to "WHERE age exists" and will delete every cat in the database with an `age`.

```surql
CREATE cat:one SET age = 4;
CREATE cat:two;
DELETE cat WHERE age;
SELECT * FROM cat;
```

```surql title="Output"
[
	{
		id: cat:two
	}
]
```

This pattern is particularly useful when using SurrealDB's [literal types](/docs/reference/query-language/language-primitives/data-types/literals.md). A literal type containing objects that contain a single top-level field can easily be matched on through the field name.

```surql
DEFINE FIELD error_info ON TABLE information TYPE
      { continue: { message: "Continue" } }
    | { retry_with_id: { error: string  } }
    | { deprecated: { message: string   } };

CREATE information SET error_info = { continue: { message: "Continue" }};
CREATE information SET error_info = { continue: { message: "Continue" }};
CREATE information SET error_info = { deprecated: { message: "We don't use this anymore" }};

DELETE information WHERE error_info.continue;
SELECT * FROM information;
```

```surql title="Output"
[
	{
		error_info: {
			deprecated: {
				message: "We don't use this anymore"
			}
		},
		id: info:o0pmm7zos98iv03xliav
	}
]
```

### Using TIMEOUT duration records based on conditions
When processing a large result set with many interconnected records, it is possible to use the `TIMEOUT` keywords to specify a timeout duration for the statement. If the statement continues beyond this duration, then the transaction will fail, no records will be deleted from the database, and the statement will return an error.

```surql
DELETE person
  WHERE ->knows->person->(knows
  WHERE influencer = false) TIMEOUT 5s;
```

## Deleting graph edges

You can also delete graph edges between two records in the database by using the DELETE statement.

For example the graph edge below:

```surql
RELATE person:tobie->bought->product:iphone;

[
	{
		"id": bought:ctwsll49k37a7rmqz9rr,
		"in": person:tobie,
		"out": product:iphone
	}
]
```

Can be deleted by:

```surql
DELETE person:tobie->bought WHERE out=product:iphone;
```

## Soft deletions

While soft deletions do not exist natively in SurrealDB, they can be simulated by [defining an event](/docs/reference/query-language/statements/define/event.md) that reacts whenever a deletion occurs.

The following example archives the data of a deleted record in another table. This can be combined with  [fewer permissions for the new table](/docs/reference/query-language/statements/define/table.md#defining-permissions) so that it can be accessed only by [system users](/docs/reference/query-language/statements/define/user.md) and not [record users](/docs/reference/query-language/statements/define/access/record.md).

```surql
DEFINE EVENT archive_person ON TABLE person WHEN $event = "DELETE" THEN {
    CREATE deleted_person SET
        data = $before,
        deleted_at = time::now()
};

CREATE |person:1..5|;
DELETE person:1;

-- Only two `person` records left
SELECT * FROM person;
-- But the data of `person:1` is still here
SELECT * FROM deleted_person;
```

```surql title="Output"
-------- Query --------

[
	{
		id: person:2
	},
	{
		id: person:3
	}
]

-------- Query --------

[
	{
		data: {
			id: person:1
		},
		deleted_at: d'2024-09-12T00:46:59.176Z',
		id: deleted_person:p3fpzhpxuu9jvjn8juyf
	}
]
```

## The `EXPLAIN` clause

When `EXPLAIN` is used:

1. The `DELETE` statement returns an explanation, essentially revealing the execution plan to provide transparency and understanding of the query performance.
2. The records are not deleted.

`EXPLAIN` can be followed by `FULL` to see the number of executed rows.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/explain

# EXPLAIN

The EXPLAIN statement is used to display the query planner for a following statement.

_(since v3.0.0)_

> [!NOTE]
> The output for the `EXPLAIN` statement is for informational purposes and subject to change. Be sure not to develop tools around it that rely on a single predictible output.

The `EXPLAIN` statement is used to display the query planner for a statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
EXPLAIN [ ANALYZE ] [ FORMAT TEXT | JSON ] @statement
```

## Example usage

An `EXPLAIN` statement is one that can be appended to another statement that does not modify database resources, i.e. a `SELECT` statement or another statement that returns a value.

The two main decisions to make when using an `EXPLAIN` statement are:

* Use default text format or add `FORMAT JSON` to output the format in JSON?
* Add the `ANALYZE` clause after `EXPLAIN`

The following example shows the four possible types of output when followed by a simple string.

```surql
EXPLAIN "yourself!";
EXPLAIN ANALYZE "yourself!";
EXPLAIN FORMAT JSON "yourself!";
EXPLAIN ANALYZE FORMAT JSON "yourself!";
```

As the output shows, the `ANALYZE` clause adds information on the metrics and total rows.

```surql title="Output"
-------- Query --------

"Expr [ctx: Rt] [expr: 'yourself!']"

-------- Query --------

"Expr [ctx: Rt] [expr: 'yourself!'] {rows: 0, batches: 0, elapsed: 0ns}

Total rows: 1"

-------- Query --------

{
	attributes: {
		expr: "'yourself!'"
	},
	context: 'Rt',
	expressions: [
		{
			role: 'expr',
			sql: "'yourself!'"
		}
	],
	operator: 'Expr'
}

-------- Query --------

{
	attributes: {
		expr: "'yourself!'"
	},
	context: 'Rt',
	expressions: [
		{
			role: 'expr',
			sql: "'yourself!'"
		}
	],
	metrics: {
		elapsed_ns: 0,
		output_batches: 0,
		output_rows: 0
	},
	operator: 'Expr',
	total_rows: 1
}
```

### Context

The `context` field in an `EXPLAIN` statement refers to the minimum context level for an operation: `Rt` (root), `Ns` (namespace), or `Db` (database) level.

### Operator types

The `operator` field in the output of an `EXPLAIN` statement is the most relevant area to take note of. Here is a list of many of the operator types you will see in the statement output.

```text
Aggregate
Compute
CountScan
Explain
ExplainAnalyze
Expr
Fetch
Filter
Foreach
FullTextScan
GraphEdgeScan
IfElse
IndexCountScan
IndexScan
KnnScan
Let
Limit
ProjectValue
Project
SelectProject
ReferenceScan
Return
Scan
Sequence
Sleep
SourceExpr
Split
Union
UnwrapExactlyOne
InfoDatabase
InfoIndex
InfoNamespace
InfoRoot
InfoTable
InfoUser
ExternalSort
Sort
SortByKey
RandomShuffle
SortTopK
SortTopKByKey
```

This allows you to get an insight into exactly what sort of work is being performed by the database when a query is executed.

For example, take the following simple example in which one `person` record has a single friend. The final two queries return the same result, but one is a `SELECT...FROM ONLY` query while the other is a direct destructuring of the link from its record id.

```surql
CREATE person:one, person:two;
RELATE person:one->friend->person:two;

EXPLAIN SELECT ->friend->person AS friends FROM ONLY person:one;
EXPLAIN person:one.{ friends: ->friend->person };
```

Not only is the second query faster, but we can see why as the first query is doing more work with four operations instead of one.

```surql title="Output"
-------- Query 1 --------

'UnwrapExactlyOne [ctx: Db]
    Project [ctx: Db]
          field.lookup: GraphEdgeScan [ctx: Db] [direction: ->, tables: person, output: TargetId]
                    GraphEdgeScan [ctx: Db] [direction: ->, tables: friend, output: TargetId]
                                  CurrentValueSource [ctx: Rt]
                                          RecordIdScan [ctx: Db] [record_id: person:one]'

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

'Expr [ctx: Db] [expr: (person:one).{ friends: ->friend->person }]'
```

Here is an example of output for a query of a complexity more similar to those seen in production applications.

```surql
EXPLAIN ANALYZE SELECT
  id as commentId,
  in.id as id,
  in.creationDate as creationDate
FROM is_comment_of
WHERE out = media_text_test:0
  AND in.creationDate < d'2026-01-09T00:00:00.000Z'
ORDER BY in.creationDate DESC
LIMIT 2;
```

```surql title="Output"
"Project [ctx: Db] {rows: 0, batches: 0, elapsed: 1.71µs}
    Limit [ctx: Db] [limit: 2] {rows: 0, batches: 0, elapsed: 13.92µs}
            SortTopKByKey [ctx: Db] [sort_keys: in.creationDate DESC, limit: 2] {rows: 0, batches: 0, elapsed: 7.50µs}
                        TableScan [ctx: Db] [table: is_comment_of, direction: Forward, predicate: out = media_text_test:0 AND in.creationDate < d'2026-01-09T00:00:00Z'] {rows: 0, batches: 0, elapsed: 361.42µs}
                        
                        Total rows: 0"
```

### Filtered KNN and the `predicate` attribute on `KnnScan`

_(since v3.1.5)_

When a [K-nearest neighbours search](/docs/reference/query-language/language-primitives/operators.md#knn) over an indexed vector field is combined with an additional non-KNN condition, the planner pushes that residual condition *into* the index search. Non-matching candidates are rejected during the graph traversal, before they can occupy one of the `K` result slots, rather than being filtered out after the neighbours have been retrieved.

This behaviour can be seen by appending the `EXPLAIN` clause to the end of a query to show its plan. Here, the `KnnScan` operator surfaces this pushed-down condition as a `predicate` attribute, in the same way that `TableScan` exposes its own `predicate`.

```surql
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 }
];

EXPLAIN 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="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: idx_pt, k: 2, ef: 40, dimension: 4, predicate: flag = true]'
```

The `KnnScan` line shows that the index `idx_pt` is searched for `k: 2` neighbours with an exploration factor of `ef: 40` (the two values of the `<|2,40|>` operator), and that the `flag = true` condition is applied *inside* the search as a `predicate`. The same condition also appears on the `Filter` line above; the difference is that the `predicate` on `KnnScan` is what causes non-matching candidates to be discarded *during* the search rather than only afterwards. Without an extra condition the `predicate` attribute is absent. DISKANN indexes use the same `KnnScan` operator and render an identical line.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/for

# FOR

The FOR statement creates a loop that iterates over the values of an array.

The `FOR` statement can be used to iterate over the values of an array, and to perform certain actions with those values.

> [!NOTE]
> A `FOR` loop currently cannot modify items outside its own scope, such as variables declared before the loop.

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
FOR @item IN @iterable {
@block
};
```

## Example usage
The following query shows example usage of this statement.

```surql
-- Create a person for everyone in the array
FOR $name IN ['Tobie', 'Jaime'] {
	CREATE type::record('person', $name) CONTENT {
		name: $name
	};
};
```

The following query shows the `FOR` statement being used update a property on every user matching certain criteria.

```surql
-- Set can_vote to true for every person over 18 years old.
FOR $person IN (SELECT VALUE id FROM person WHERE age >= 18) {
	UPDATE $person SET can_vote = true;
};
```

## Ranges in FOR loops

A `FOR` loop can also be made out of a [range UUID](/docs/reference/query-language/language-primitives/data-types/ranges.md) of integers.

```surql
FOR $year IN 0..=2024 {
    CREATE historical_events SET
        for_year = $year,
        events = "To be added";
};
```

## Limitations of FOR loops

Parameters declared outside of a `FOR` loop can be used inside the loop.

```surql
LET $table1 = "person";
LET $table2 = "cat";

FOR $key in 0..4 {
    CREATE type::record($table1, $key);
	  CREATE type::record($table2, $key);
};
```

However, they currently cannot be modified inside a loop, making an operation like the following impossible.

```surql
LET $init = [];

FOR $num IN 1..=3 {
	$init += $num;
};
//- Error: 'assignment operators are only allowed in SET and DUPLICATE KEY UPDATE clauses'

RETURN $init;
```

In this case, the [`array::fold`](/docs/reference/query-language/functions/database-functions/array.md#arrayfold) and [`array::reduce`](/docs/reference/query-language/functions/database-functions/array.md#arrayreduce) functions can often be used to accomplish the intended behaviour.

```surql
(<array>1..=3).reduce(|$one, $two| $one + $two);
```

```surql title="Output"
6
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/if-else

# IF ELSE

The IF ELSE statement can be used as a main statement, or within a parent statement, to return a value depending on whether a condition, or a series of conditions match.

The `IF ELSE` statement can be used as a main statement, or within a parent statement, to return a value depending on whether a condition, or a series of conditions match. The statement allows for multiple `ELSE IF` expressions, and a final `ELSE` expression, with no limit to the number of `ELSE IF` conditional expressions.

> [!NOTE]
> As [THROW](/docs/reference/query-language/statements/throw.md), [CONTINUE](/docs/reference/query-language/statements/continue.md), and [BREAK](/docs/reference/query-language/statements/break.md) do not return an expression, they must be inside a separate code block inside an `IF ELSE` statement.

An `IF ELSE` syntax uses `{}` to open up a code block on each condition check which will be run when it evaluates as [truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness).

**Syntax**

```surql title="Modern syntax"
IF @condition { @expression; .. }
   [ ELSE IF @condition { @expression; .. } ] ...
   [ ELSE { @expression; .. } ]
```

> [!NOTE]
> An older `IF @condition THEN @expression ELSE @expression END` form predates the block syntax introduced in 1.0.0 and is still accepted, so that definitions written against earlier versions keep parsing. A definition stored that way is also returned by `INFO FOR DB` in the same form. Use the block syntax in new queries: a `THEN` branch holds a single expression, which leaves no room for a `LET`, a `THROW`, a `BREAK`, a `CONTINUE`, or two statements separated by a semicolon.

## Example usage

### Basic usage

The following queries show example usage of this statement.

The smallest possible `IF` statement simply does something when a condition is true, and nothing otherwise.

```surql
IF 9 = 9 { 'Nine is indeed nine' };
```

As the last line of a scope is its return value, the `RETURN` keyword can also be placed before the entire `IF ELSE` statement. This is particularly convenient in long `IF ELSE` chains to avoid using the `RETURN` keyword at the end of every check for a condition.

```surql
LET $num = 100;

RETURN IF $num < 0 {
    "Negative"
} ELSE IF $num = 0 {
    "Zero"
} ELSE IF $num = 13 {
    "Thirteen"
} ELSE {
    "Positive uninteresting number"
};
```

The `RETURN` keyword can even be omitted, as the output at each point is the output of the entire expression if evaluated as truthy.

```surql
LET $num = 100;

IF $num < 0 {
    "Negative"
} ELSE IF $num = 0 {
    "Zero"
} ELSE IF $num = 13 {
    "Thirteen"
} ELSE {
    "Positive uninteresting number"
};
```

The `THROW` keyword inside `{}` braces can be used to break out of an `IF ELSE` statement early.

```surql
LET $badly_formatted_datetime = "2024-04TT08:08:08Z";

IF !type::is_datetime($badly_formatted_datetime) {
    THROW "Whoops, that isn't a real datetime"
};
```

```surql title="Output"
"An error occurred: Whoops, that isn't a real datetime"
```

`ELSE IF` branches and a final `ELSE` can be added into an `IF ELSE` statement:

```surql
RETURN
    IF $access = "admin" { (SELECT * FROM account) }
    ELSE IF $access = "user"  { (SELECT * FROM $auth.account) }
    ELSE { THROW "Access method hasn't been defined!" };
```

### Advanced usage

The output of an `IF ELSE` statement can be assigned to a parameter:

```surql
LET $num = 9;

LET $odd_even = 
    IF $num % 2 = 0 { "even" } 
    ELSE { "odd" };
```

If-else statements can also be used as subqueries within other statements.

```surql
UPSERT person SET railcard =
    IF age <= 10 { 'junior' }
    ELSE IF age <= 21 { 'student' }
    ELSE IF age >= 65 { 'senior' }
    ELSE { NULL };
```

You can also have nested conditions:

```surql
IF $access = 'admin'
	{
        CREATE admin_user_event SET 
            time = time::now(),
            info = "Admin user activity registered";
		SELECT * FROM admin_data WHERE access_level = 'full';
	}
ELSE IF $access = 'user'
	{
		IF $auth.role = 'premium'
			{
                CREATE premium_user_event SET 
                    time = time::now(),
                    info = "Premium user activity registered";

				IF $auth.subscription_status = 'active'
					{ SELECT * FROM premium_user_data WHERE active = 1 }
				ELSE IF $auth.subscription_status = 'trial'
					{ SELECT * FROM trial_user_data }
				ELSE
					{ SELECT * FROM basic_user_data }
			}
		ELSE IF $auth.role = 'standard'
			{ SELECT * FROM standard_user_data WHERE region = 'US' }
		ELSE IF $auth.role = 'standard' AND $auth.subscription_status = 'active'
			{ SELECT * FROM standard_user_data WHERE region = 'EU' }
		ELSE
			{ SELECT * FROM unauthorized_user_data }
	}
ELSE
	{ SELECT * FROM unknown_access_data };
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/info

# INFO

The INFO command outputs information about the setup of the SurrealDB system.

The `INFO` command outputs information about the setup of the SurrealDB system. There are a number of different `INFO` commands for retrieving the configuration at the different levels of the database.

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
INFO FOR [
	ROOT
	| NS | NAMESPACE
	| DB | DATABASE
	| TABLE @table
	| USER @user [ON @level]
    | INDEX @index ON @table
];
```

The information returned from an `INFO` command is an object containing items that almost always correspond to a matching [DEFINE](/docs/reference/query-language/statements/define/overview.md) statement. For example, the `INFO FOR NS` command returns the information on the access methods, databases and users of a namespace, which are defined with `DEFINE ACCESS`, `DEFINE DATABASE` and `DEFINE USER` statements.

> [!NOTE]
> Before SurrealDB v3.0.0, the output of an `INFO FOR` was only able to be used as a standalone statement and not in a dynamic context, such as inside other queries or as the value of a parameter.

> [!NOTE]
> From SurrealDB 3.2.2, `INFO FOR ROOT`, `INFO FOR NS`, `INFO FOR DB`, and `INFO FOR USER` always show password hashes as `PASSHASH '[REDACTED]'` (including for root). That keeps schema listings from exposing credential material to every role that can run `INFO`. The hash is still stored, while a privileged [export](/docs/reference/cli/surrealdb-cli/commands/export.md) still includes the real `PASSHASH` so backups can restore users. Note that Argon2 hashes remain one-way and salted, so redaction is about who can read the PHC string, not about treating a hash as a plaintext password.

## Example usage

There are a number of different `INFO` commands for retrieving the configuration at the different levels of the database.

## System information

### Root information
The top-level ROOT command returns information regarding:
- The users and namespaces which exists within the SurrealDB system.
- The memory allocated by SurrealDB itself. Note that this may not match what the operating system reports, as it also includes memory consumed by third-party libraries or pre-allocated memory.
- The level of parallelism: This number indicates the number of available hardware threads.

> [!NOTE]
> You must be authenticated as a top-level root user to execute this command.

#### Examples

```surql
INFO FOR ROOT;
```

```surql title="Sample output"
{
	accesses: {},
	namespaces: {
		ns: 'DEFINE NAMESPACE ns'
	},
	nodes: {
		"2d3b720d-f152-4c0d-8a16-26d1474ed3cd": 'NODE 2d3b720d-f152-4c0d-8a16-26d1474ed3cd SEEN 1745463977888 ACTIVE'
	},
	system: {
		available_parallelism: 14,
		cpu_usage: 0.3816290497779846f,
		load_average: [
			1.2734375f,
			1.68310546875f,
			1.9189453125f
		],
		memory_allocated: 13900485,
		memory_usage: 136314880,
		physical_cores: 14,
		threads: 32
	},
	users: {
		root: "DEFINE USER root ON ROOT PASSHASH '[REDACTED]' ROLES OWNER DURATION FOR TOKEN 1h, FOR SESSION NONE"
	}
}
```

### Namespace information

The `NS` or `NAMESPACE` command returns information regarding the users, databases and access methods under the namespace in use.

> [!NOTE]
> You must be authenticated as a top-level root user, or a namespace user to execute this command.

> [!NOTE]
> You must have a NAMESPACE selected before running this command.

#### Examples

```surql
INFO FOR NS;
```

```surql title="Sample output"
{
    accesses: {},
    databases: {
        db: 'DEFINE DATABASE db'
    },
    users: {
        n: "DEFINE USER n ON NAMESPACE ROLES VIEWER DURATION FOR TOKEN 1h, FOR SESSION NONE",
        username: "DEFINE USER username ON NAMESPACE PASSHASH '[REDACTED]' ROLES EDITOR DURATION FOR TOKEN 1m, FOR SESSION 12h"
    }
}
```

### Database information

The `DB` or `DATABASE` command returns information regarding the users, tables, params, models, functions, analyzers and access methods under the database in use.

> [!NOTE]
> You must be authenticated as a top-level root user, a namespace user, or a database user to execute this command.

> [!NOTE]
> You must have a NAMESPACE and a DATABASE selected before running this command.

#### Examples

```surql
INFO FOR DB;
```

```surql title="Sample output"
{
    accesses: {},
    analyzers: {},
    apis: {},
    buckets: {},
    configs: {},
    functions: {},
    models: {},
    params: {},
    tables: {
        person: 'DEFINE TABLE person TYPE ANY SCHEMALESS PERMISSIONS NONE'
    },
    users: {
        db_user: "DEFINE USER db_user ON DATABASE PASSHASH '[REDACTED]' ROLES OWNER DURATION FOR TOKEN 1h, FOR SESSION NONE"
    }
}
```

### Table information

The `TABLE` command returns information regarding the events, fields, tables, and live statement configurations on a specific table.

> [!NOTE]
> You must be authenticated as a top-level root user, a namespace user, or a database user to execute this command.

> [!NOTE]
> You must have a NAMESPACE and a DATABASE selected before running this command.

#### Examples

```surql
INFO FOR TABLE user;
```

```surql title="Sample output"
{
    events: {},
    fields: {
        name: 'DEFINE FIELD name ON user TYPE string PERMISSIONS FULL'
    },
    indexes: {},
    lives: {},
    tables: {}
}
```

### User information

The `USER` command returns information for a user [defined](/docs/reference/query-language/statements/define/user.md) on either the root, namespace, or database level.

> [!NOTE]
> You must be authenticated as a user equal to or greater than the level of the user you are attempting to obtain information for to execute this command.

#### Examples

```surql
INFO FOR USER root ON ROOT;
INFO FOR USER ns_user ON NAMESPACE;
INFO FOR USER db_user ON DATABASE;
```

If a level after `ON` is not specified, the `INFO` command will default to the database level. Thus, the following two commands are equivalent.

```surql
INFO FOR USER db_user ON DATABASE;
INFO FOR USER db_user;
```

```surql title="Sample output"
"DEFINE USER db_user ON DATABASE PASSHASH '[REDACTED]' ROLES OWNER DURATION FOR TOKEN 1h, FOR SESSION NONE"
```

### Index information

`INFO FOR INDEX` returns the status for an index: started, initial indexing, update indexing, built, or error.

This command only applies when the [`CONCURRENTLY`](/docs/reference/query-language/statements/define/indexes.md#using-concurrently-clause) clause is used in a `DEFINE INDEX` command. Without this clause, the following statement will not be executed until the index is fully created, or fails. In this case, the `INFO FOR INDEX` statement will return an empty object: `{}`.

```surql
CREATE |user:50000| SET name = id.id() RETURN NONE;
DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
INFO FOR INDEX unique_name ON TABLE user;
```

However, when the `CONCURRENTLY` clause is used, the index will build in the background while other statements are permitted to run. In this case, the `INFO FOR INDEX` statement will provide the current status on the index. The following code sample shows such an example in which an index is defined on a large number of records. A [`SLEEP`](/docs/reference/query-language/statements/sleep.md) statement is run in between each `INFO FOR INDEX` command to show the progress after each 50 millisecond interval.

```surql
CREATE |user:50000| SET name = id.id() RETURN NONE;
DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE CONCURRENTLY;
INFO FOR INDEX unique_name ON user;
SLEEP 50ms;
INFO FOR INDEX unique_name ON user;
SLEEP 50ms;
INFO FOR INDEX unique_name ON user;
SLEEP 50ms;
INFO FOR INDEX unique_name ON user;
```

```surql title="Possible output"
-------- Query 1 --------
{ 
    building: {
        initial: 0,
        pending: 0,
        status: 'indexing', 
        updated: 0
    }
}

-------- Query 2 --------
{ 
    building: {
        initial: 100,
        pending: 20,
        status: 'indexing', 
        updated: 0
    }
}

-------- Query 3 --------
{ 
    building: {
        initial: 100,
        pending: 4,
        status: 'indexing', 
        updated: 16
    }
}

-------- Query 4 --------
{
    building: {
        status: 'ready'
    }
}
```

### The `STRUCTURE` clause

> [!NOTE]
> This clause was created for internal use and is subject to change without notice.

Adding the `STRUCTURE` clause changes the structure of the statement from an object that contains objects into an object with fields that each contain an array and often extra info.

```surql
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON user TYPE STRING;

INFO FOR TABLE user;
INFO FOR TABLE user STRUCTURE;
```

```surql title="Output"
-------- Query --------

{
	events: {},
	fields: {
		name: 'DEFINE FIELD name ON user TYPE string PERMISSIONS FULL'
	},
	indexes: {},
	lives: {},
	tables: {}
}

-------- Query --------

{
	events: [],
	fields: [
		{
			flex: false,
			kind: 'string',
			name: 'name',
			permissions: {
				create: true,
				delete: true,
				select: true,
				update: true
			},
			readonly: false,
			what: 'user'
		}
	],
	indexes: [],
	lives: [],
	tables: []
}
```

### Using the output of `INFO`

_(since v3.0.0)_

The output of an `INFO` statement, both with and without the `STRUCTURE` clause, can be used in other operations. As the output of the statement is always a single object, the SurrealQL [object functions](/docs/reference/query-language/functions/database-functions/object.md) can also be used on the output for such tasks as schema change tracking.

```surql
LET $cat = CREATE ONLY cat RETURN VALUE id;

LET $first_schema = {
    revision: rand::uuid(),
    schema: INFO FOR DB
};

$first_schema;

CREATE person SET feeds = [$cat];

LET $second_schema = {
    revision: rand::uuid(),
    schema: INFO FOR DB
};

$second_schema;

$first_schema.diff($second_schema);
```

```surql title="Output"
-------- First schema --------

{
	revision: u'019665cc-f730-75f0-8251-894e11fee7d8',
	schema: {
		accesses: {},
		analyzers: {},
		apis: {},
		buckets: {},
		configs: {},
		functions: {},
		models: {},
		params: {},
		tables: {
			cat: 'DEFINE TABLE cat TYPE ANY SCHEMALESS PERMISSIONS NONE'
		},
		users: {}
	}
}

-------- Second schema --------

{
	revision: u'019665cc-f73b-7313-807f-dd22ad1a0685',
	schema: {
		accesses: {},
		analyzers: {},
		apis: {},
		buckets: {},
		configs: {},
		functions: {},
		models: {},
		params: {},
		tables: {
			cat: 'DEFINE TABLE cat TYPE ANY SCHEMALESS PERMISSIONS NONE',
			person: 'DEFINE TABLE person TYPE ANY SCHEMALESS PERMISSIONS NONE'
		},
		users: {}
	}
}

-------- Diff --------

[
	{
		op: 'replace',
		path: '/revision',
		value: u'019665cc-f73b-7313-807f-dd22ad1a0685'
	},
	{
		op: 'add',
		path: '/schema/tables/person',
		value: 'DEFINE TABLE person TYPE ANY SCHEMALESS PERMISSIONS NONE'
	}
]
```

As `INFO` statements return an object and objects can be turned into arrays using SurrealDB's [object functions](/docs/reference/query-language/functions/database-functions/object.md), all of the statements for a schema can be constructed using `INFO` statements alone. This query for example shows each of the statements found in a single database.

```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);
```

### Default namespace and database output

_(since v3.0.0)_

A namespace and database with the name of `name` are generated by default when starting a SurrealDB instance unless the `SURREAL_NO_DEFAULTS` [environment variable](/docs/reference/cli/surrealdb-cli/environment-variables.md) is set to false or the [`--no-defaults`](/docs/reference/cli/surrealdb-cli/commands/start.md) flag is used.

The output of the `INFO` statement in this case includes a comment on how these two resources were defined.

```surql
[INFO FOR ROOT.namespaces, INFO FOR NS.databases];
```

```surql title="Output"
[
	{ main: "DEFINE NAMESPACE main COMMENT 'Default namespace generated by SurrealDB'" }, 
	{ main: "DEFINE DATABASE main COMMENT 'Default database generated by SurrealDB'" }
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/insert

# INSERT

The INSERT statement can be used to insert or update data into the database, using the same statement syntax as the traditional SQL Insert statement.

The `INSERT` statement can be used to insert or update data into the database, using the same statement syntax as the traditional SQL Insert statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
INSERT [ RELATION ] [ IGNORE ] INTO @what
	[ @value
	  | (@fields) VALUES (@values)
		[ ON DUPLICATE KEY UPDATE @field = @value ... ]
	]
	[ RETURN NONE | RETURN BEFORE | RETURN AFTER | RETURN DIFF | RETURN @statement_param, ... | RETURN VALUE @statement_param ]
;
```

## Example usage

The following query shows example usage of this statement.

```surql
INSERT INTO company {
	name: 'SurrealDB',
	founded: "2021-09-10",
	founders: [person:tobie, person:jaime],
	tags: ['big data', 'database']
};
```

Records can also be inserted by using the `VALUES` keyword. This keyword is preceded by the name of the fields in question, and followed by comma-separated values matching the number of fields specified.

```surql
-- Insert a single record
INSERT INTO
	company (name, founded)
	VALUES  ('SurrealDB', '2021-09-10');

-- Insert multiple records
INSERT INTO
	company (name, founded)
	VALUES  ('Acme Inc.', '1967-05-03'), ('Apple Inc.', '1976-04-01');
```

It is possible to update records which already exist or violate a unique index by specifying an `ON DUPLICATE KEY UPDATE` clause. This clause also allows incrementing and decrementing numeric values, and adding or removing values from arrays. To increment a numeric value, or to add an item to an array, use the `+=` operator. To decrement a numeric value, or to remove an value from an array, use the `-=` operator.

```surql
INSERT INTO product (name, url) VALUES ('Salesforce', 'salesforce.com') ON DUPLICATE KEY UPDATE tags += 'crm';
```

Field names inside `ON DUPLICATE KEY UPDATE` refer to the fields of the existing record. To access the fields of the new record that was attempted to be inserted, prefix the field name with [`$input`](/docs/reference/query-language/language-primitives/parameters.md#input):

```surql
INSERT INTO city (id, population, at_year) VALUES ("Calgary", 1665000, 2024)
ON DUPLICATE KEY UPDATE
	population = $input.population,
	at_year = $input.at_year;
```

An example of `ON DUPLICATE KEY UPDATE` when a unique key is encountered shows the same behaviour as that with a duplicate record:

```surql
DEFINE FIELD data_for ON user_data TYPE record<user>;
DEFINE INDEX one_user ON user_data FIELDS data_for UNIQUE;

INSERT INTO user_data {
    data_for: user:one,
    some: "data"
} ON DUPLICATE KEY UPDATE times_updated += 1, last_edited = time::now();

INSERT INTO user_data {
    data_for: user:one,
    some_more: "data"
} ON DUPLICATE KEY UPDATE times_updated += 1, last_edited = time::now();
```

```surql title="Output"
-------- Query --------

[
	{
		for: user:one,
		id: user_data:kp78dubsxmp4f04x0de3,
		some: 'data'
	}
]

-------- Query --------

[
	{
		for: user:one,
		id: user_data:kp78dubsxmp4f04x0de3,
		last_edited: d'2025-07-14T05:15:52.146Z',
		some: 'data',
		times_updated: 1
	}
]
```

Using the insert statement, it is possible to copy records easily between tables. The records being copied will have the same id in the new table, but the record id will signify the new table name.

```surql
INSERT INTO recordings_san_francisco (SELECT * FROM temperature
  WHERE city = 'San Francisco');
```

Furthermore, it is possible to perform a bulk insert in a single query. The `@what` part of the syntax can be either a single object or an array of objects.

```surql
INSERT INTO person [
   { id: "jaime", name: "Jaime", surname: "Morgan Hitchcock" },
   { id: "tobie", name: "Tobie", surname: "Morgan Hitchcock" },
];
```

### Ignoring duplicates

While attempting to insert one or more records via the `INSERT` statement, if the record ID is already present in the table, the query will encounter an error and fail. If the `IGNORE` clause is supplied, records with an already existing or duplicate ID will be silently ignored.

```surql
INSERT IGNORE INTO person [
   { id: "jaime", name: "Jaime", surname: "Morgan Hitchcock" },
   { id: "tobie", name: "Tobie", surname: "Morgan Hitchcock" },

   { id: "jaime", name: "Jaime", surname: "Morgan Hitchcock" }, -- will not throw an error
];
```

### Return values

By default, the `INSERT` statement returns the record once it has been inserted. To change what is returned, we can use the `RETURN` clause, specifying either `NONE`, `BEFORE`, `AFTER`, `DIFF`, or a comma-separated list of specific fields to return.

`RETURN NONE` can be useful to avoid excess output:

```surql
-- Insert a record and return nothing
INSERT INTO company {
	name: 'SurrealDB',
	founded: "2021-09-10",
	founders: [person:tobie, person:jaime],
	tags: ['big data', 'database']
} RETURN NONE;
```

`RETURN DIFF` returns the changeset diff:

```surql
-- Insert a record and return the diff
INSERT INTO company {
	name: 'SurrealDB',
	founded: "2021-09-10",
	founders: [person:tobie, person:jaime],
	tags: ['big data', 'database']
} RETURN DIFF;
```

```surql title="Output"
-------- Query 1 --------

[
	[
		{
			op: 'replace',
			path: '/',
			value: {
				founded: '2021-09-10',
				founders: [
					person:tobie,
					person:jaime
				],
				id: company:hu5o1wqbo29t10engbeo,
				name: 'SurrealDB',
				tags: [
					'big data',
					'database'
				]
			}
		}
	]
]
```

`RETURN BEFORE` inside a `INSERT` statement is essentially a synonym for `RETURN NONE`, while `RETURN AFTER` is the default behaviour for `INSERT`.

```surql
-- Before insert will always return NONE as it is the same as the record being inserted
INSERT INTO company {
	name: 'SurrealDB',
	founded: "2021-09-10",
	founders: [person:tobie, person:jaime],
	tags: ['big data', 'database']
} RETURN BEFORE;
```

```surql
-- Return the record after creation
INSERT INTO company {
	name: 'SurrealDB',
	founded: "2021-09-10",
	founders: [person:tobie, person:jaime],
	tags: ['big data', 'database']
} RETURN AFTER;
```

You can also return specific fields from a created record, the value of a single field using `VALUE`, as well as ad-hoc fields to modify the output as needed.

```surql
INSERT INTO person {
    age: 46,
    username : "john-smith",
    interests : ['skiing', 'music'] }
RETURN
    age,
    interests,
    age + 1 AS age_next_year;

INSERT INTO planet [
	{
		name: 'Venus',
        surface_temp: 462,
        temp_55_km_up: 27
	},
	{
		name: 'Earth',
        surface_temp: 15,
        temp_55_km_up: -55
	}
] RETURN VALUE temp_55_km_up;
```

```surql title="Output"
-------- Query --------

[
	{
		age: 46,
		age_next_year: 47,
		interests: [
			'skiing',
			'music'
		]
	}
]

-------- Query --------

[
	27,
	-55
]
```

## Bulk insert

The `INSERT` statement supports bulk insert, which allows multiple records to be inserted in a single query. The `@what` part of the syntax can be either a single object or an array of objects.

```surql
INSERT INTO person [
   { id: "jaime", name: "Jaime", surname: "Morgan Hitchcock" },
   { id: "tobie", name: "Tobie", surname: "Morgan Hitchcock" },
   -- ... 1000 more records
];
```

## Insert relation tables

The `INSERT` statement can also be used to add records into relation tables. The `@what` part of the syntax can be either a single object or an array of objects.

Learn more about creating relationships between tables in the [RELATE](/docs/reference/query-language/statements/relate.md) statement. For example:

```surql
-- Insert records into the person table
INSERT INTO person [
	{ id: 1 },
	{ id: 2 },
	{ id: 3 },
];
-- Insert a single relation
INSERT RELATION INTO likes {
	in: person:1,
	id: 'object',
	out: person:2,
};

-- Insert multiple relations
INSERT RELATION INTO likes [
	{
		in: person:1,
		id: 'array',
		out: person:2,
	},
	{
		in: person:2,
		id: 'array_two',
		out: person:3,
	}
];

-- Insert a relation and return the value of the likes field
INSERT RELATION INTO likes (in, id, out)
	VALUES (person:1, 'values', person:2);

-- Select the value of the likes field
SELECT VALUE ->likes FROM person;

```

_(since v3.1.5)_

When `INSERT RELATION` specifies an explicit edge `id` that already exists, SurrealDB returns a record-exists error instead of updating the edge in place. You can use `ON DUPLICATE KEY UPDATE` to merge into the existing relation, in the same way as a normal [`INSERT`](/docs/reference/query-language/statements/insert.md#example-usage):

```surql
CREATE person:one, post:one;

INSERT RELATION INTO likes {
  in: person:one,
  out: post:one,
  id: [person:one, post:one],
  note: 'first',
};

INSERT RELATION INTO likes {
  in: person:one,
  out: post:one,
  id: [person:one, post:one],
  note: 'second',
};

INSERT RELATION INTO likes {
  in: person:one,
  out: post:one,
  id: [person:one, post:one],
  note: 'third',
} ON DUPLICATE KEY UPDATE note = 'updated';
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/kill

# KILL

The KILL statement is used to terminate a running live query.

The `KILL` statement is used to terminate a running live query.

While the `KILL` statement does accept a value type, this value must resolve to a UUID. Consequently, it will accept a string literal of a UUID or a param.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
KILL @value;
```

## Example usage

### Basic usage

The `KILL` statement expects the UUID of a running [live select](/docs/reference/query-language/statements/live-select.md) query to be passed. This UUID can be found in the output of the `LIVE` statement, and can thereafter be passed into a `KILL` statement once it is no longer needed.

```surql
-- Possible output, as every live query is given its own id
LIVE SELECT DIFF FROM person;
//- u'0189d6e3-8eac-703a-9a48-d9faa78b44b9'

-- Some time later...
KILL u"0189d6e3-8eac-703a-9a48-d9faa78b44b9";
```

The `KILL` statement also allows for parameters to be used.

```surql
-- Define the parameter
LET $live_query_id = u"0189d6e3-8eac-703a-9a48-d9faa78b44b9";
-- Use the parameter
KILL $live_query_id;
```

Using the `KILL` statement on a UUID that does not correspond to a running live query will generate an error.

```surql
LET $rand = rand::uuid();
KILL $rand;
KILL u'9276b05b-e59a-49cd-9dd1-17c6fd15c28f';
```

```surql title="Output"
"Can not execute KILL statement using id '$rand'"
"Can not execute KILL statement using id 'u'9276b05b-e59a-49cd-9dd1-17c6fd15c28f''"
```

## Kill notifications

_(since v3.0.0)_

A separate notification is sent out when a `KILL` statement is enacted on a live query ID.

```surql
-- Possible output, as every live query is given its own id
LIVE SELECT * FROM person;
//- u'cf447091-9463-4d75-b32a-08513eb2a07c'

KILL u'cf447091-9463-4d75-b32a-08513eb2a07c';
```

```surql title="Output"
-- Query 1
NONE

-- Notification (action: Killed, live query ID: cf447091-9463-4d75-b32a-08513eb2a07c)
NONE
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/let

# LET

The LET statement sets and stores a value which can then be used in a subsequent query.

The `LET` statement allows you to create parameters to store any value, including the results of queries or the outputs of expressions. These parameters can then be referenced throughout your SurrealQL code, making your queries more dynamic and reusable.

## Syntax

The syntax for the `LET` statement is straightforward. The parameter name is prefixed with a `$` symbol.

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
LET $parameter [: @type_name] = @value;
```

## Example usage

### Basic parameter assignment

You can use the `LET` statement to store simple values or query results. For example, storing a string value and then using it in a `CREATE` statement:

```surql
-- Define the parameter
LET $name = "tobie";
-- Use the parameter
CREATE person SET name = $name;
```

### Storing query results

The `LET` statement is also useful for storing the results of a query, which can then be used in subsequent operations:

```surql
-- Define the parameter
LET $adults = SELECT * FROM person WHERE age > 18;
-- Use the parameter
UPDATE $adults SET adult = true;
```

### Conditional logic with `IF ELSE`

SurrealQL allows you to define parameters based on conditional logic using `IF ELSE` statements:

```surql
LET $num = 10;

LET $num_type =
         IF type::is_int($num)     { "integer" }
    ELSE IF type::is_decimal($num) { "decimal" }
    ELSE IF type::is_float($num)   { "float"   };

RETURN $num_type;
```

```surql title="Output"
'integer'
```

## Anonymous functions

You can define anonymous functions also known as closures using the `LET` statement. These functions can be used to encapsulate reusable logic and can be called from within your queries. Learn more about [anonymous functions](/docs/reference/query-language/language-primitives/data-types/closures.md) in the Data model section.

## Pre-defined and protected parameters

SurrealDB comes with [pre-defined parameters](/docs/reference/query-language/language-primitives/parameters.md) that are accessible in any context. However, parameters created using `LET` are not accessible within the scope of these pre-defined parameters.

Furthermore, some pre-defined parameters are protected and cannot be overwritten using `LET`:

```surql
LET $before = "Before!";

-- Returns ["Before!"];
RETURN $before;

-- Returns the `person` records before deletion
DELETE person RETURN $before;

-- Returns "Before!" again
RETURN $before;
```

Attempting to redefine protected parameters will result in an error:

```surql
LET $auth = 1;
LET $session = 10;
```

```surql title="Output"
-------- Query 1 --------

"'auth' is a protected variable and cannot be set"

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

"'session' is a protected variable and cannot be set"
```

## Typed LET statements

Type safety in a `LET` statement can be ensured by adding a `:` (a colon) and the type name after the `LET` keyword.

```surql
LET $number: int = "9";
```

```surql title="Output"
"Tried to set `$number`, but couldn't coerce value: Expected `int` but found `'9'`"
```

### Typed literal statements

Multiple possible types can be specified in a `LET` statement by adding a `|` (vertical bar) in between each possible type.

```surql
LET $number: int | string = "9";
```

Even complex types such as objects can be included in a typed `LET` statement.

```surql
LET $error_info: 
  string | { error: string } = 
  { 
    error: "Something went wrong plz help" 
  };
```

For more information on this pattern, see the page on [literals](/docs/reference/query-language/language-primitives/data-types/literals.md).

## Conclusion

The `LET` statement in SurrealDB is versatile, allowing you to store values, results from subqueries, and even define anonymous functions. Understanding how to use `LET` effectively can help you write more concise, readable, and maintainable queries.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/live-select

# LIVE SELECT

The LIVE SELECT statement can be used to initiate a real-time selection from a table, including the option to apply filters.

Live Queries is a feature that allows you to listen for creations, updates and deletions to specific records you are interested in or entire tables.

The `LIVE SELECT` statement can be used to initiate a real-time selection from a table, including the option to apply filters.

In practical terms, when you execute a `LIVE SELECT` query, it triggers an ongoing session that captures any subsequent changes to the data in real-time. These changes are then immediately transmitted to the client, ensuring that the client is consistently updated with the latest data modifications.

> [!NOTE]
> Errors while evaluating a live query's `WHERE` clause or projection (for example a type mismatch) skip that notification; they do not roll back the write that triggered the live query. Live subscriptions are ended when the session is invalidated or its TTL expires, when you [`KILL`](/docs/reference/query-language/statements/kill.md) the live query, or when the underlying table is removed. Built-in live parameters (`$value`, `$before`, `$after`, `$event`, and related names) always take precedence over user variables captured at registration time.

> [!IMPORTANT]
> Currently, `LIVE SELECT` is only supported in single-node deployments, with multi-node support being actively developed.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
LIVE SELECT
	[
		[ VALUE ] @fields ... [ AS @alias ]
		| DIFF
	]
	FROM @targets
	[ WHERE @conditions ]
	[ FETCH @fields ... ]
;
```

## Example usage

### Basic usage

By default, SurrealDB will push the entire record over the websocket when created or updated, and just the record's ID when deleted.

```surql
LIVE SELECT * FROM person;
```

```surql title="Output"
'b1f1d115-ad0f-460d-8cbf-dbc7ce48851c'
```

The result of the above query will be a UUID. This UUID is the Live Query Unique ID, and is used to differentate between different Live Queries. You will want to keep track of this ID, so that you can differentiate between different notifications being received after this query. You can also use this UUID to [KILL](/docs/reference/query-language/statements/kill.md) (stop) the Live Query. The protocol will then send messages that are of a Notification format.

You can find an example of such a message in the [Live Query WebSocket protocol](/docs/reference/rest-api/rpc-protocol.md#live) description.

### Diff

When using the `DIFF` mode, updates will be sent in the form of an array with [JSON Patch](https://jsonpatch.com/) messages.

```surql
LIVE SELECT DIFF FROM person;
```

```surql title="Output"
'b87cbb0d-ca15-4f0a-8f86-caa680672aa5'
```

### Filter the live query

You can optionally apply filters with the `WHERE` clause.

```surql
LIVE SELECT * FROM person WHERE age > 18;
```

## Consistency guarantees

When using Live Queries, it is important to understand the ordering of messages and events when many clients and transactions are running in paralllel. Notifications on live queries are only published for committed transactions.

While a best effort is made to assure ordering is correct, a strict correctness is not yet in place for a full guarantee. As such that some messages may be received out of order from their commit order. However, transactions that are committed from the same client will always be in order.

Security enforcement is always evaluated per notification and will reflect the value of authorisation at the time of publishing the notification. This means that if a transaction is committed, after which the authorisation immediately changes for the live query receiver, the receiver will get the notification under the new rules.

### Changing a session's authentication ends its live queries

A live query records the authentication principal of the session that registered it. If that session becomes a different principal - through `signin`, `signup`, `authenticate`, `invalidate`, or a token refresh that resolves to a different identity - the session's live queries are ended and receive no further notifications. They must be registered again under the new principal if they are still needed.

A token refresh for the same identity does not change the principal, so its live queries continue.

> [!NOTE]
> Before SurrealDB 3.3.0, embedded engines (`mem://`, `rocksdb://`, `surrealkv://`) did not end live queries when the session's principal changed, so a subscription kept sending notifications under the access controls of the previous principal. Connections over `ws://` and `http://` behaved as described above.

## Fetching inside live queries

_(since v2.2.0)_

The `FETCH` clause can be used inside live queries as well.

```surql
LIVE SELECT * FROM person WHERE age > 18 FETCH friends;
```

## Other notes

Since SurrealDB 3.0, parameters can be used inside a `LIVE SELECT` statement, including in the `WHERE` clause (see [Parameters in `LIVE SELECT` statements](#parameters-in-live-select-statements) below). Parameter values are captured when the live query is registered; changing a parameter afterwards does not affect an already-registered live query - re-register the live query to apply a new value.

Note that a bare parameter cannot be used as the table reference. Use `type::table()` instead:

```surql
-- Does not work: bare parameter as the table reference
-- LIVE SELECT * FROM $table WHERE field > 50;

-- Works:
LIVE SELECT * FROM type::table($table) WHERE field > 50;
```

## Parameters in `LIVE SELECT` statements

_(since v3.0.0)_

Parameters can also be used inside a `LIVE SELECT` statement.

```surql
LET $table = 'measurement';
LET $location = 'Tallinn';
LIVE SELECT * FROM type::table($table) WHERE location == $location;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/overview

# Statements

Reference overview: SurrealQL statements for resources, control flow, transactions and CRUD or data operations.

SurrealDB provides statements to configure resources, control execution flow, and read or write data. The categories below group them by role.

## 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. They 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,
* [`REMOVE`](/docs/reference/query-language/statements/remove.md) statements to remove resources,
* [`REBUILD`](/docs/reference/query-language/statements/rebuild.md) to rebuild an index,
* [`ACCESS`](/docs/reference/query-language/statements/access.md) to manage access grants.

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,
* [`SHOW`](/docs/reference/query-language/statements/show.md) to see the changefeed for a table or database.

### 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. They 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.,
* [`IF` and `ELSE`](/docs/reference/query-language/statements/if-else.md) to describe what to do depending on a condition,
* [`SLEEP`](/docs/reference/query-language/statements/sleep.md) to halt all execution for a certain length of time,
* [`RETURN`](/docs/reference/query-language/statements/return.md) to break and return a value,
* [`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.

The statements that pertain to the handling of records 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,
* [`UPDATE`](/docs/reference/query-language/statements/update.md) to update records,
* [`UPSERT`](/docs/reference/query-language/statements/upsert.md) to update a record and create a new one if it does not exist,
* [`SELECT`](/docs/reference/query-language/statements/select.md) to select records (but also ad-hoc values),
* [`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,
* [`KILL`](/docs/reference/query-language/statements/kill.md) to cancel a `LIVE SELECT`.

The other statements used when executing a query are:

* [`LET`](/docs/reference/query-language/statements/let.md) to assign a value to a parameter for later use,
* [`RETURN`](/docs/reference/query-language/statements/return.md) when used in front of a value or expression, in which case it has no effect but is often used for readability.

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.

## Output when resource not defined

_(since v3.0.0)_

Many but not all statements in versions before 3.0 returned an empty array when a resource was not defined.

```surql
SELECT * FROM person;
DELETE person;
REMOVE TABLE person;
```

```surql title="Output before 3.0"
-------- Query 1 --------

[]

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

[]

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

"The table 'person' does not exist"
```

All statements return an error if this is the case, making it clear that the resource is not defined as opposed to defined and empty.

```surql
SELECT * FROM person;
DELETE person;
```

```surql title="Output"
-------- Query --------

"The table 'person' does not exist"

-------- Query --------

"The table 'person' does not exist"
```

However, statements that create a resource will not return an error unless the [database is defined](/docs/reference/query-language/statements/define/database.md) as `STRICT`. Instead, they will automatically define the needed table (and even use the [`$session`](/docs/learn/security/authentication/users.md#session) parameter to define the database and namespace if necessary) so that the query will work.

Note the output of the following queries, in which the value `[]` is returned after deleting the records of a defined table to show that zero records were returned. However, the final `REMOVE TABLE` statement returns a simple `NONE` to indicate that the statement succeeded.

```surql
SELECT * FROM person; -- Error
DELETE person;        -- Error
CREATE person:one;    -- Succeeds
DELETE person;        -- Empty array
REMOVE TABLE person;  -- NONE
```

The following chart can help to remember which output to expect depending on your SurrealDB version and database strictness.

| Example of statement  | STRICT database      | Non-STRICT database behaviour (default)                     | Behaviour in versions < 3.0       |
|-----------------------|----------------------|------------------------------------------------------------|----------------------------------|
| CREATE / UPDATE       | Error if not defined | Succeeds (defines table, database, namespace if necessary) | Identical                        |
| SELECT / DELETE       | Error if not defined | Returns error if table not defined                         | Empty array if table not defined |

---

Source: https://surrealdb.com/docs/reference/query-language/statements/rebuild

# REBUILD

The REBUILD statement is used to rebuild indexes.

The `REBUILD` statement is used to rebuild indexes in SurrealDB. It is usually used in relation to a specified [Index](/docs/reference/query-language/statements/define/indexes.md) to optimise performance. It is useful to rebuild indexes because sometimes [HNSW](/docs/reference/query-language/statements/define/indexes.md#hnsw-hierarchical-navigable-small-world) index performance can degrade due to frequent updates.

Rebuilding the index will ensure the index is fully optimised.

> [!NOTE]
> By default, `REBUILD INDEX` waits until the rebuild finishes before the statement returns (the same behaviour as `DEFINE INDEX` without `CONCURRENTLY`). Adding `CONCURRENTLY` on the rebuild statement will cause it to return immediately, after which progress can be monitored via [`INFO FOR INDEX`](/docs/reference/query-language/statements/info.md#index-information). Whether the index was originally created with `CONCURRENTLY` does not affect rebuilds. See the [`CONCURRENTLY` clause](/docs/reference/query-language/statements/define/indexes.md#using-concurrently-clause) on `DEFINE INDEX` for the same blocking vs non-blocking distinction when creating an index.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
REBUILD [
	INDEX [ IF EXISTS ] @name ON [ TABLE ] @table [ CONCURRENTLY ]
]
```

> [!NOTE]
> The `IF EXISTS` and TABLE clauses are optional.

## Example usage

For example, if you have a table called `book` and you have an index called `uniq_isbn` on the `isbn` field, you can rebuild the index using the following query:

```surql
REBUILD INDEX uniq_isbn ON book;
```

```surql
CREATE book:1 SET title = 'Rust Web Programming', isbn = '978-1803234694', author = 'Jon Doe';
// Define a unique index on the isbn field
DEFINE INDEX uniq_isbn ON book FIELDS isbn UNIQUE;
// Rebuild this index incase of more updates
REBUILD INDEX IF EXISTS uniq_isbn ON book;
// Check that the index has been created
INFO FOR TABLE book;
REBUILD INDEX IF EXISTS idx_author ON book;
REBUILD INDEX IF EXISTS ft_title ON book;
// Define index on the author field
DEFINE INDEX idx_author ON book FIELDS author;
// Define an analyzer which has blank and class Tokenizers and converts the tokens to lowercase
DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase;
DEFINE INDEX ft_title ON book FIELDS title SEARCH ANALYZER simple BM25 HIGHLIGHTS;
REBUILD INDEX uniq_isbn ON book;
REBUILD INDEX idx_author ON book;
REBUILD INDEX ft_title ON book;
// Check that the index has been created
INFO FOR TABLE book;
//Checks whether the term RUST IS found in a full-text indexed field.
SELECT * FROM book WHERE title @@ 'Rust';
```

### Using if exists clause

The following queries show an example of how to rebuild resources using the `IF EXISTS` clause, which will only rebuild the resource if it exists.

```surql
REBUILD INDEX IF EXISTS uniq_isbn ON book;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/relate

# RELATE

The RELATE statement can be used to generate graph edges between two records in the database.

The `RELATE` statement can be used to generate graph edges between two records in the database. This allows you to traverse related records efficiently without needing to pull data from multiple tables and merging that data together using SQL JOINs.

Edges created using the RELATE statement are nearly identical to tables created using other statements, and can contain data. The key differences are that:

- Edge tables are deleted once there are no existing relationships left.
- Edge tables have two required fields `in` and `out`, which specify the directions of the relationships. These cannot be modified in schema declarations except to specify that they must be of a certain record type or to [add assertions](/docs/reference/query-language/statements/define/field.md#asserting-rules-on-fields).

Otherwise, edge tables behave like normal tables in terms of [updating](/docs/reference/query-language/statements/update.md), [defining a schema](/docs/reference/query-language/statements/define/table.md) or [indexes](/docs/reference/query-language/statements/define/indexes.md).

Another option for connecting data is using [record links](/docs/reference/query-language/language-primitives/record-links.md). Record links consist of a field with record IDs that serve as unidirectional links by default, or bidirectional links if reference tracking is used. The key differences are that graph relations have the following benefits over record links:

- Graph relations are kept in a separate table as opposed to a field inside a record.
- Graph relations allow you to store data alongside the relationship.
- Graph relations have their own syntax that makes it easy to build and visualise edge queries.

Graph relations offer built-in bidirectional querying and referential integrity. Record links also offer these two advantages if they are defined inside a [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statement using the `REFERENCES` clause. For more information, see [the page on record references](/docs/reference/query-language/language-primitives/record-references.md).

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
RELATE [ ONLY ] [ OR UPDATE ] @from_record -> @table | @edge_record -> @to_record
	[ CONTENT @value
	  | SET @field = @value ...
	]
	[ RETURN NONE | RETURN BEFORE | RETURN AFTER | RETURN DIFF | RETURN @statement_param, ... | RETURN VALUE @statement_param ]
	[ TIMEOUT @duration ]
;
```

> [!NOTE]
> `RELATE` will create a relation regardless of whether the records to relate to exist or not. As such, it is advisable to [create the records](/docs/reference/query-language/statements/create.md) you want to relate to before using `RELATE`, or to at least ensure that they exist before making a query on the relation. If the records to relate to don't exist, a query on the relation will still work but will return an empty array. To override this behaviour and return an error if no records exist to relate, you can use a [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md) statement that includes the `ENFORCED` keyword.

> [!NOTE]
> If a `RELATE` on the create path sets `id` to an **existing** edge record (for example `RELATE a->edge->b SET id = edge:existing`), SurrealDB returns a record-exists error instead of overwriting that edge. Use [`UPDATE`](/docs/reference/query-language/statements/update.md) when you intend to modify an existing relation.

## Handling duplicate edge record IDs

_(since v3.1.5)_

You can supply the edge record ID directly in the `RELATE` path instead of letting SurrealDB generate one. This is useful when the ID should act like a built-in unique key for the relationship, for example a composite of the `in` and `out` records:

```surql
RELATE person:one->likes:[person:one, post:one]->post:one
  SET liked_at = time::now();

RELATE person:one->likes:[person:one, post:one]->post:one
  SET liked_at = time::now();
```

As of SurrealDB 3.1.5, redoing a `RELATE` statement on an existing ID will update the edge, but emit a warning. This will become a hard error in a later version of SurrealDB.

To specifically opt in to update semantics on an existing edge and not emit a warning, you can add `OR UPDATE` after `RELATE`.

```surql
RELATE person:one->likes:[person:one, post:one]->post:one
  SET liked_at = time::now();

-- Has `OR UPDATE`: will not emit a warning
RELATE OR UPDATE person:one->likes:[person:one, post:one]->post:one
  SET liked_at = time::now();
```

If you prefer a hard error at this point, [`INSERT RELATION`](/docs/reference/query-language/statements/insert.md#insert-relation-tables) can be used:

```surql
INSERT RELATION INTO likes {
  in: person:two,
  out: post:one,
  id: [person:two, post:one]
};

-- Returns an error
INSERT RELATION INTO likes {
  in: person:two,
  out: post:one,
  id: [person:two, post:one]
};
```

## Example usage

### Basic usage

The following query shows the basic structure of the `RELATE` statement, which creates a relationship between a record in the `person` table and a record in the `article` table.

```surql
CREATE person:aristotle, article:on_sleep_and_sleeplessness;
RELATE person:aristotle->wrote->article:on_sleep_and_sleeplessness;
```

```surql title="Output"
[
	{
		id: wrote:bpbrj5kd7smu3ahlf55r,
		in: person:aristotle,
		out: article:on_sleep_and_sleeplessness
	}
]
```

There is no relationship information stored in either the `person` or `article` table.

```surql
SELECT * FROM person, article;
```

```surql title="Output"
[
	{
		id: person:aristotle
	},
	{
		id: article:on_sleep_and_sleeplessness
	}
]
```

Instead, an edge table (in this case a table called `wrote`) stores the relationship information.

```surql
SELECT * FROM wrote;
```

The structure `in -> id -> out` mirrors the record IDs from the `RELATE` statement, with the addition of the automatically generated ID for the `wrote` edge table.

```surql title="Output"
[
	{
		id: wrote:bpbrj5kd7smu3ahlf55r,
		in: person:aristotle,
		out: article:on_sleep_and_sleeplessness
	}
]
```

The same structure can be used in a `SELECT` query, as well as directly from a record ID.

```surql
-- Aristotle's id and the articles he wrote
SELECT id, ->wrote->article FROM person:aristotle;
-- Every `person`'s id and written articles
-- Same output as above as the database has a single `person` record
SELECT id, ->wrote->article FROM person;
-- Directly follow the path from Aristotle to his written articles
RETURN person:aristotle->wrote->article;
```

```surql title="Output"
-------- Query --------

[
	{
		"->wrote": {
			"->article": [
				article:on_sleep_and_sleeplessness
			]
		},
		id: person:aristotle
	}
]

-------- Query --------

[
	article:on_sleep_and_sleeplessness
]
```

By default, the edge table gets created as a schemaless table when you execute the `RELATE` statement. You can make the table schemafull by [defining a schema](/docs/reference/query-language/statements/define/table.md).

A common use case is to make sure only unique relationships get created. You can do that by [defining an index](/docs/reference/query-language/statements/define/indexes.md).

```surql
DEFINE INDEX unique_relationships
    ON TABLE wrote
    COLUMNS in, out UNIQUE;
```

As edge tables are bidirectional by default, there is nothing stopping a query like the following in which an article writes a person instead of the other way around.

```surql
RELATE article:on_sleep_and_sleeplessness->wrote->person:aristotle;
```

To enforce unidirectional relationships, you can restrict the type definition using a [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) definition.

```surql
DEFINE FIELD in  ON TABLE wrote TYPE record<person>;
DEFINE FIELD out ON TABLE wrote TYPE record<article>;
```

### Always two records there are - no more, no less

An edge table will always include exactly one record for the `in` field and one record for the `out` field.

Knowing this, one would assume that a `RELATE` statement like the following would fail as it seems to be attempting to insert two `cat` records at the `in` field.

```surql
CREATE cat:mr_meow, cat:mrs_meow, cat:kitten;
RELATE [cat:mr_meow, cat:mrs_meow]->parent_of->cat:kitten;
```

However, the query works just fine. Instead of trying to create a single `parent_of` graph edge, it will create one for each record in the first array: one between `cat:mr_meow` and `cat:kitten`, and another between `cat:mrs_meow` and `cat:kitten`.

```surql title="Output"
[
	{
		id: parent_of:uahudi4qr68k640fcjbg,
		in: cat:mr_meow,
		out: cat:kitten
	},
	{
		id: parent_of:hi79yfazjppv8b3kyi36,
		in: cat:mrs_meow,
		out: cat:kitten
	}
]
```

Similarly, a `RELATE` statement that involves two arrays will return a number of graph edges equal to their product (2 * 2 in this case):

```surql
CREATE cat:kitten2;
RELATE [cat:mr_meow, cat:mrs_meow]->parent_of->[cat:kitten, cat:kitten2];
```

```surql title="Output"
[
	{
		id: parent_of:ysbab20nv5568ogba6ns,
		in: cat:mr_meow,
		out: cat:kitten
	},
	{
		id: parent_of:0ltm6xr94pkblyxf0m6c,
		in: cat:mr_meow,
		out: cat:kitten2
	},
	{
		id: parent_of:71cfl0nvj5frve0r1npv,
		in: cat:mrs_meow,
		out: cat:kitten
	},
	{
		id: parent_of:4gbid7nzo6cwr1t8k090,
		in: cat:mrs_meow,
		out: cat:kitten2
	}
]
```

## Adding data using `SET` and `CONTENT`

Graph edges are standalone tables that can hold other fields besides the default `in`, `out`, and `id`. These can be added during a `RELATE` statement or during an `UPDATE` in the same manner as any other SurrealDB table.

Let's look at the two ways you can add record data in the `RELATE` statement. Both of these queries will produce the same result. Use `CONTENT` to pass the record data as a single object, and `SET` to assign or compute each field on its own line.

```surql
RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
	CONTENT {
		metadata: {
			time_written: time::now(),
			location: "Tallinn"
		}
	};


RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
    SET 
		metadata.time_written = time::now(),
		metadata.location = "Tallinn";
```

```surql title="Output"
[
	{
		id: wrote:rva8hentypdu8lcgwjmf,
		in: person:l19zjikkw1p1h9o6ixrg,
		metadata: {
			location: 'Tallinn',
			time_written: d'2024-11-26T01:52:01.169Z'
		},
		out: article:8nkk6uj4yprt49z7y3zm
	}
]
```

Here is an example of the graph edge being updated in the same way as any other SurrealDB record:

```surql
-- Add a small synopsis composed of the table name and article ID
UPDATE wrote SET
    metadata.description = record::tb(out) + ' written by ' + <string>in;
```

```surql title="Output"
[
	{
		id: wrote:k9d8ynbfxgb8jqjv2ob5,
		in: person:l19zjikkw1p1h9o6ixrg,
		metadata: {
			description: 'article written by person:l19zjikkw1p1h9o6ixrg',
			location: 'Tallinn',
			time_written: d'2024-11-26T01:53:51.350Z'
		},
		out: article:8nkk6uj4yprt49z7y3zm
	}
]
```

## Passing variables in `CONTENT` and `SET`

You can also pass variables in the `CONTENT` block. This is useful when you want to pass a variable that is not a record ID.

```surql
LET $time = time::now();
RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
    CONTENT {
        time: {
            written: $time
        }
    };
```

```surql title="Output"
    {
        "id": "wrote:ctwsll49k37a7rmqz9rr",
        "in": "person:l19zjikkw1p1h9o6ixrg",
        "out": "article:8nkk6uj4yprt49z7y3zm",
        "time": {
            "written": "2021-09-29T14:00:00Z"
        }
    }
```

Below is an example of how you can pass a variable in the `SET` block:

```surql
LET $time = time::now();

RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
    SET time.written = $time;
```

```surql title="Output"
{
	"id": "wrote:ctwsll49k37a7rmqz9rr",
	"in": "person:l19zjikkw1p1h9o6ixrg",
	"out": "article:8nkk6uj4yprt49z7y3zm",
	"time": {
		"written": "2021-09-29T14:00:00Z"
	}
}
```

## Creating a single relation with the `ONLY` keyword

Using the ONLY keyword, just an object for the relation in question will be returned. This, instead of an array with a single object.

```surql
RELATE ONLY person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm;
```

```surql title="Output"
{
	id: wrote:k9f1rqn3oikolr1560u3,
	in: person:l19zjikkw1p1h9o6ixrg,
	out: article:8nkk6uj4yprt49z7y3zm
}
```

## Using [`LET`](/docs/reference/query-language/statements/let.md) parameters in RELATE statements

You can also use [parameters](/docs/reference/query-language/language-primitives/parameters.md) to specify the record IDs.

```surql
-- These two statements store the result of the subquery in a parameter
-- The subquery returns an array of IDs
LET $person =  SELECT VALUE id FROM person;
LET $article = SELECT VALUE id FROM article;

-- This statement creates a relationship record for every combination of Record IDs
-- Such that if we have 10 records each in the person and article table
-- We get 100 records in the wrote edge table (10*10 = 100)
-- In this case it would mean that each article would have 10 authors
RELATE $person->wrote->$article SET time.written = time::now();
```

## Modifying output with the [`RETURN`](/docs/reference/query-language/statements/return.md) clause

By default, the relate statement returns the record value once the changes have been made. To change the return value of each record, specify a RETURN clause, specifying either `NONE`, `BEFORE`, `AFTER`, `DIFF`, or a comma-separated list of specific fields to return.

```surql
-- Don't return any result
RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
    SET time.written = time::now()
    RETURN NONE;

-- Return the changeset diff
RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
    SET time.written = time::now()
    RETURN DIFF;

-- Return the record before changes were applied
RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
    SET time.written = time::now()
    RETURN BEFORE;

-- Return the record after changes were applied (the default)
RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
    SET time.written = time::now()
    RETURN AFTER;

-- Return a specific field only from the updated records
RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
    SET time.written = time::now()
    RETURN time;

-- Return only the value of a specific field without the field name
RELATE person:l19zjikkw1p1h9o6ixrg->wrote->article:8nkk6uj4yprt49z7y3zm
    SET time.written = time::now()
    RETURN VALUE time;
```

## Using the `TIMEOUT` clause

Adding the `TIMEOUT` keyword to specify a timeout duration for the statement can be useful when processing a large result set with many interconnected records. If the statement continues beyond this duration, then the transaction will fail, and the statement will return an error.

```surql
-- Cancel this conditional filtering based on graph edge properties
-- if not finished within 5 seconds
SELECT * FROM person
  WHERE ->knows->person->(knows
  WHERE influencer = true) TIMEOUT 5s;
```

Using a `TIMEOUT` is particularly useful when experimenting with complex queries with an extent that is difficult to imagine, especially if the query [is recursive](#recursive-graph-queries).

## Deleting graph edges

You can also delete graph edges between two records in the database by using the [DELETE statement](/docs/reference/query-language/statements/delete.md).

For example the graph edge below:

```surql
RELATE person:tobie->bought->product:iphone;
```

```surql title="Output"
[
	{
		id: bought:ctwsll49k37a7rmqz9rr,
		in: person:tobie,
		out: product:iphone
	}
]
```

Can be deleted by:

```surql
DELETE person:tobie->bought WHERE out=product:iphone RETURN BEFORE;
```

As mentioned above, a graph edge will also automatically be deleted if it is no longer connected to a record at both `in` and `out`.

```surql
-- Create three people
CREATE person:one, person:two, person:three;

-- And a love triangle involving them all
RELATE person:one  ->likes->person:two;
RELATE person:two  ->likes->person:three;
RELATE person:three->likes->person:one;

-- Person two moves to Venus permanently, so delete
DELETE person:two;

-- Only one `likes` relationship is left
SELECT * FROM likes;
```

```surql title="Output"
[
	{
		id: likes:55szjin5yfqwl4sbmy1f,
		in: person:three,
		out: person:one
	}
]
```

## Using RELATE on non-existent records

As mentioned at the top of the page, `RELATE` can be used for records that do not yet exist. While this behaviour can be overridden by using the `ENFORCED` keyword, it can be useful in certain situations.

For example, the `VALUE` clause inside a [`DEFINE FIELD`](/docs/reference/query-language/statements/define/field.md) statement is calculated every time a record is altered (that is, every time it is created or updated). If this value depends on a graph edge, creating the record first will cause `VALUE` to calculate it based on a nonexistent path.

In the following example, a `house` table has a field called `has_road_access` that depends on whether any `->has_road` paths return an output that is not empty. Meanwhile, the city has a new road under construction but no houses are present and their details have not been set yet.

```surql
-- Returns true if $this->has_road path is not empty
DEFINE FIELD has_road_access ON TABLE house VALUE !!$this->has_road->road;
CREATE road SET name = "Dalhurst Way", length = 10.5;
```

As the addresses of the upcoming houses have been decided, the `->has_road` path can be set ahead of time by giving the `house` records an ID based on their exact address.

```surql
LET $road = SELECT * FROM ONLY road WHERE name = "Dalhurst Way" LIMIT 1;
RELATE [
    house:["Dalhurst Way", 218],
    house:["Dalhurst Way", 222],
    house:["Dalhurst Way", 226],
]->has_road->$road;
```

Later on, two new houses are completed in the city and registered in the database. As the path to `house:["Dalhurst Way", 218]` has already been set up, the `has_road_access` field will evaluate to `true`, while the other house in the middle of nowhere will evaluate to `false`.

```surql
CREATE house:["Dalhurst Way", 218] SET floors = 2, bedrooms = 5;
CREATE house:["Middle of nowhere", 0] SET floors = 4, bedrooms = 12;
```

```surql
-------- Query --------

[
	{
		bedrooms: 5,
		floors: 2,
		id: house:[
			'Dalhurst Way',
			218
		],
		street: []
	}
]

-------- Query --------

[
	{
		bedrooms: 12,
		floors: 4,
		id: house:[
			'Middle of nowhere',
			0
		],
		street: []
	}
]
```

## Querying graphs

### Different ways to reach similar results

For the questions below, each of the queries will give you largely the same answer. Note that whether `->` and `<-` are parsed as `in` or `out` depends on their direction in relation to the graph edge `wrote`. An arrow pointing towards `wrote` corresponds to `in`, and vice versa.

The following examples show how to make similar queries in a number of different ways, in the context of a database with one person who wrote two articles.

```surql
CREATE 
	person:aristotle,
	article:on_sleep_and_sleeplessness,
	article:on_dreams;
RELATE person:aristotle->wrote->[
		article:on_sleep_and_sleeplessness,
		article:on_dreams
	]
	-- Written sometime around the year 330 BC
	SET time_written = d"-0330-01-01";
```

Who wrote the articles?

```surql
-- All queries lead to `person:artistotle` twice,
-- via different paths and thus different field names
-- and/or structure

-- Directly from the `wrote` table
SELECT in FROM wrote;

-- From a single `person` record
SELECT ->wrote.in FROM person;
SELECT ->wrote<-person FROM person;

-- From two `article` records
SELECT <-wrote.in FROM article;
SELECT <-wrote<-person FROM article;
```

Which articles did the person write?

```surql
SELECT out FROM wrote;

SELECT ->wrote.out FROM person;
SELECT ->wrote->article FROM person;

SELECT <-wrote.out FROM article;
SELECT <-wrote->article FROM article;
```

When was the article written?

```surql
SELECT time_written FROM wrote;
SELECT ->wrote.time_written as time_written FROM person;
SELECT <-wrote.time_written as time_written FROM article;
```

### Parsing graph queries

For a more complicated query like the one below you can use a simple rule of thumb:
Place the subject in front of the graph selection, then read it backward.

```surql
-- This query
SELECT ->purchased->product<-purchased<-person->purchased->product
  FROM person:tobie

-- Then becomes
person:tobie->purchased->product<-purchased<-person->purchased->product SELECT
```

Reading this backwards then makes more sense:

> Select every product that was purchased by a person who purchased a product that was also purchased by person Tobie.

Alternatively, you can break it down into steps over multiple lines.

```surql
-- Starting with Tobie
person:tobie
-- move on to his purchased products
->purchased->product
-- that were also purchased by persons...
<-purchased<-person
-- what are all of those persons' purchased products?
->purchased->product
```

Putting it all together it would be: based on all the products Tobie purchased, which person also purchased those products and what did they purchase? This sort of query could be used on a social network site to recommend to the user `person:tobie` a list of people that have similar interests.

### Using parentheses to refine graph query logic

Parentheses can be added at any step of a graph query to refine the logic, such as filtering relations based on specific conditions using the `WHERE` clause.

For example, suppose we want to limit the query to only take recent purchases into account. We can filter `purchased` graph edge to only include purchases made in last 3 weeks:

```surql
-- Select products purchased by people in the last 3 weeks who have purchased the same products that tobie purchased
SELECT 
	->purchased->product
	<-purchased<-person->(purchased WHERE created_at > time::now() - 3w)
	->purchased->product
FROM person:tobie;
```

If the `purchased` graph table can lead to both a `product` or a `subscription`, they can both be added to the query.

```surql
SELECT 
	->purchased->(product, subscription)
	<-purchased<-person
	->purchased->(product, subscription)
FROM person:tobie;
```

The `?` wildcard operator can also be used to search for any and all linked records. The following query will allow purchased `product`, `subscription`, `insurance`, or any other linked records to show up.

```surql
SELECT 
	->purchased->(?)
	<-purchased<-person
	->purchased->(?)
FROM person:tobie;
```

The `?` operator on its own can thus be used to see all of the relations that a record has.

```surql
CREATE person:hermann_hesse, person:abigail, city:calw, book:demian;
RELATE person:hermann_hesse->wrote->book:demian SET written_in = d'1919-01-01';
RELATE person:hermann_hesse->born_in->city:calw;
RELATE person:abigail->likes->person:hermann_hesse;

SELECT 
	-- all tables in which the record is at `in`
    ->(?).* AS what_hesse_did,
	-- all tables in which the record is at `out`
    <-(?).* AS what_others_did_to_hesse
FROM person:hermann_hesse;
```

```surql title="Output"
[
	{
		what_hesse_did: [
			{
				id: born_in:k3adylof24a2r5kio8l5,
				in: person:hermann_hesse,
				out: city:calw
			},
			{
				id: wrote:ncbo9w0d8t3xd7lvl4dx,
				in: person:hermann_hesse,
				out: book:demian,
				written_in: d'1919-01-01T00:00:00Z'
			}
		],
		what_others_did_to_hesse: [
			{
				id: likes:6gubmldm14gzasoyypay,
				in: person:abigail,
				out: person:hermann_hesse
			}
		]
	}
]
```

The `?` operator can also be used to find all the relations between one record and another. To do this, use the [`<-> operator`](#bidirectional-relation-querying) to see all relations in which the record ID in question is either at the `in` or the `out` of the graph edge. Follow this with `(?)` to avoid filtering by graph table name, then use a [`WHERE`](/docs/reference/query-language/language-primitives/data-types/arrays.md#mapping-and-filtering-on-arrays) filter on the output (an array of record IDs) to see if the record ID is present in either the `in` or the `out` field of the graph edge.

A small example of this using some of the relations between Anakin Skywalker (Darth Vader), Palpatine (the Emperor), and Luke Skywalker:

```surql
CREATE person:anakin_skywalker, person:luke_skywalker, person:the_emperor;
RELATE person:anakin_skywalker->served->person:the_emperor;
RELATE person:anakin_skywalker->attacked->person:the_emperor SET won = true;
RELATE person:the_emperor->attacked->person:luke_skywalker SET won = false;
RELATE person:luke_skywalker->son_of->person:anakin_skywalker;
RELATE person:the_emperor->fooled->person:anakin_skywalker SET date = "19 BBY";

-- As a SELECT statement
SELECT VALUE <->(?)[WHERE person:the_emperor IN [in, out]]
  FROM ONLY person:anakin_skywalker;
SELECT VALUE <->(?)[WHERE person:luke_skywalker IN [in, out]]
  FROM ONLY person:anakin_skywalker;

-- Or returned directly from the record ID
person:anakin_skywalker<->(?)[WHERE person:the_emperor IN [in, out]];
person:anakin_skywalker<->(?)[WHERE person:luke_skywalker IN [in, out]];
```

```surql title="Output"
-------- Anakin and Emperor relations --------

[
	{
		date: '19 BBY',
		id: fooled:irm2w6jvd1dmppjr7kh2,
		in: person:the_emperor,
		out: person:anakin_skywalker
	},
	{
		id: attacked:r8b4z5yr627wy9i73jkh,
		in: person:anakin_skywalker,
		out: person:the_emperor,
		won: true
	},
	{
		id: served:30oyjvv5uutnj255w4oy,
		in: person:anakin_skywalker,
		out: person:the_emperor
	}
]

-------- Anakin and Luke relations --------

[
	{
		id: son_of:h8oosl7s27n21kh3c2iq,
		in: person:luke_skywalker,
		out: person:anakin_skywalker
	}
]
```

Parentheses can be used at each point of a graph query. The example below includes `person` records (authors) connected to `book` records by the `wrote` table. As both the `person` and `book` tables have fields that can be useful when filtering, they can be isolated with parentheses at this point of the graph query in order to filter using the `WHERE` clause.

```surql
CREATE person:j_r_r_tolkien SET
	name = "J.R.R. Tolkien",
	born = d'1891-01-03';
-- Very approximate date of birth
CREATE person:plato SET 
	name = "Plato", 
	born = "-0428-06-01";

CREATE book:fotr SET 
	name = "The Fellowship of the Ring";
CREATE book:republic SET 
	name = "The Republic",
	original_name = "Πολιτεία";

RELATE person:j_r_r_tolkien->wrote->book:fotr SET written_at = "North Oxford";
RELATE person:plato->wrote->book:republic SET written_at = "Athens";

SELECT 
	name,
	-- Isolate 'wrote' to use WHERE
	->(wrote WHERE written_at = "Athens")->book.* AS books_written_in_athens
FROM person;

SELECT 
	name, 
	-- Isolate 'book' to use WHERE
	->wrote->(book WHERE "Ring" IN name).* AS books_about_rings
FROM person;
```

```surql title="Output"
-------- Query --------

[
	{
		books_written_in_athens: [],
		name: 'J.R.R. Tolkien'
	},
	{
		books_written_in_athens: [
			{
				id: book:republic,
				name: 'The Republic',
				original_name: 'Πολιτεία'
			}
		],
		name: 'Plato'
	}
]

-------- Query --------

[
	{
		books_about_rings: [
			{
				id: book:fotr,
				name: 'The Fellowship of the Ring'
			}
		],
		name: 'J.R.R. Tolkien'
	},
	{
		books_about_rings: [],
		name: 'Plato'
	}
]
```

[Destructuring](/docs/reference/query-language/language-primitives/idioms.md#destructuring) can also be used to pick and choose which fields to access inside a graph query. The following query will return the same output as above, except that `original_name: 'Πολιτεία'` will no longer show up.

```surql
SELECT 
	name, 
	->(wrote WHERE written_at = "Athens")->book.{ name, id } AS books_written_in_athens
FROM person;
```

### Bidirectional relation querying

All of the queries up to now have been clear about what sort of record is found at the `in` and `out` fields: `in` is the record that is doing something, while `out` is the record that has something done to it:

* A `person` who writes an `article`: the person **writes**, the article **is written**.
* A `person` who purchases a `product`: the person **purchases**, the product **is purchased**.

However, sometimes a relation is such that it is impossible to determine which record is located at the `in` part of a graph table, and which is located at the `out` part. This is the case when a relationship is truly bidirectional and equal, such as a friendship, marriage, or sister cities:

```surql
CREATE city:calgary, city:daejeon;
RELATE city:calgary->sister_of->city:daejeon;
```

This relation could just as well have been established with the statement `RELATE city:daejeon->sister_of->city:calgary`.

In such a case, a query on the relationship makes it appear as if one city has a twin city but the other does not.

```surql
SELECT id, ->sister_of->city AS sister_cities FROM city;
```

```surql title="Output"
[
	{
		id: city:calgary,
		sister_cities: [
			city:daejeon
		]
	},
	{
		id: city:daejeon,
		sister_cities: []
	}
]
```

To solve this, we can use the `<->` operator instead of `->`. Using `<->` will access both the `in` and `out` fields, instead of just one.

```surql
SELECT id, <->sister_of<->city AS sister_cities FROM city;
```

This brings up another issue in which a city now appears to be a sister city of itself.

```surql
[
	{
		id: city:calgary,
		sister_cities: [
			city:calgary,
			city:daejeon
		]
	},
	{
		id: city:daejeon,
		sister_cities: [
			city:calgary,
			city:daejeon
		]
	}
]
```

Here we can use the [`array::complement`](/docs/reference/query-language/functions/database-functions/array.md#arraycomplement) function to return only items from one array that are not present in another array.

```surql
SELECT id, array::complement(<->sister_of<->city, [id]) AS sister_cities
  FROM city;
```

```surql title="Output"
[
	{
		id: city:calgary,
		sister_cities: [
			city:daejeon
		]
	},
	{
		id: city:daejeon,
		sister_cities: [
			city:calgary
		]
	}
]
```

Adding a unique key is a good practice for this sort of relation, as it will prevent it from being created twice. This can be done by [defining a field](/docs/reference/query-language/statements/define/field.md) as a unique key based on the ordered record IDs involved, followed by a [`DEFINE INDEX`](/docs/reference/query-language/statements/define/field.md) statement.

```surql
DEFINE FIELD key ON TABLE sister_of VALUE <string>array::sort([in, out]);
DEFINE INDEX only_one_sister_city ON TABLE sister_of FIELDS key UNIQUE;
```

With the index in place, a relation set from one record to the other now cannot be created a second time.

```surql
RELATE city:calgary->sister_of->city:daejeon; -- OK
RELATE city:daejeon->sister_of->city:calgary;
```

```surql title="Output"
"Database index `only_one_sister_city` already contains '[city:calgary, city:daejeon]', with record `sister_of:npab0uoxogmrvpwsvfoa`"
```

### Refining the `in` and `out` fields of a relation

As mentioned above, the `in` and `out` fields of a graph table are mandatory but can be modified to specify their record type or make assertions.

Thus, the following field declarations will work:

```surql
DEFINE FIELD in ON TABLE wrote TYPE record<author>;
DEFINE FIELD out ON TABLE wrote TYPE record<book>;
```

But any attempt to outright redefine the `in` or `out` fields as a different type will be ignored.

```surql
DEFINE FIELD in ON TABLE wrote TYPE string;
DEFINE FIELD out ON TABLE wrote TYPE int;
```

An example of an assertion on one of the fields of a record table for a library which is not yet ready to handle non-English books:

```surql
DEFINE FIELD out
  ON TABLE wrote TYPE record<book> ASSERT $value.language = "English";

CREATE book:demian SET title = "Demian. Die Geschichte von Emil Sinclairs Jugend", language = "German";
CREATE author:hesse SET name = "Hermann Hesse";

RELATE author:hesse->wrote->book:demian;
```

```surql title="Output"
"Found book:demian for field `out`, with record `wrote:l4xjcgqkgm7vmqqt4iah`, but field must conform to: $value.language = 'English'"
```

### Structure of queries on relations

Using an alias is a common practice in both regular and relation queries in SurrealDB to make output more readable and collapse nested structures. You can create an alias using the `AS` clause.

```surql
CREATE cat:one, cat:two, cat:three;

RELATE cat:one->friends_with->cat:two;
RELATE cat:two->friends_with->cat:three;

SELECT ->friends_with->cat->friends_with->cat FROM cat:one;
-- create an alias for the result using the `AS` clause.
SELECT ->friends_with->cat->friends_with->cat AS friends_of_friends
  FROM cat:one;
```

```surql
-- Output without alias
{
	"->friends_with": {
		"->cat": {
			"->friends_with": {
				"->cat": [
					cat:three
				]
			}
		}
	}
}

-- Output with alias
{
	friends_of_friends: [
		cat:three
	]
}
```

However, an alias might not be preferred in a case where you have multiple graph queries that resolve to the fields of a large nested structure. Take the following data for example:

```surql
CREATE country:usa SET name = "USA";
CREATE state:pennsylvania SET population = 12970000;
CREATE state:michigan SET population = 10030000;
CREATE city:philadelphia, city:pittsburgh, city:detroit, city:grand_rapids;

RELATE country:usa->contains->[state:pennsylvania, state:michigan];
RELATE state:pennsylvania->contains->[city:philadelphia, city:pittsburgh];
RELATE state:michigan->contains->[city:detroit, city:grand_rapids];
```

A query on the states and cities of these records using aliases would return the data in a structure remade to fit the aliases declared in the query.

```surql
SELECT
    name,
    ->contains->state AS states,
    ->contains->state->contains->city AS cities
FROM country:usa;
```

```surql title="Output"
[
	{
		cities: [
			city:philadelphia,
			city:pittsburgh,
			city:grand_rapids,
			city:detroit
		],
		name: 'USA',
		states: [
			state:pennsylvania,
			state:michigan
		]
	}
]
```

However, opting to not use an alias will return the original graph structure which makes the levels of depth of the query clearer. In addition, the `population` field is clearly the population for the states.

```surql
SELECT
    id,
    ->contains->state.id,
    ->contains->state.population,
    ->contains->state->contains->city.id
FROM country:usa;
```

The [destructuring syntax](/docs/reference/query-language/language-primitives/idioms.md#destructuring) can be used to reduce some typing. Here is the same query as the last using destructuring syntax instead of one line for each field.

```surql
SELECT
    id,
	-- access id and population on a single line
    ->contains->state.{id, population},
    ->contains->state->contains->city.id
FROM country:usa;
```

```surql title="Output"
[
	{
		"->contains": {
			"->state": {
				"->contains": {
					"->city": {
						id: [
							city:philadelphia,
							city:pittsburgh,
							city:grand_rapids,
							city:detroit
						]
					}
				},
				id: [
					state:pennsylvania,
					state:michigan
				],
				population: [
					12970000,
					10030000
				]
			}
		},
		id: country:usa
	}
]
```

As the query that uses aliases does not maintain the original graph structure, adding `population` would require clever renaming such as `->contains->state.population AS state_populations` to make it clear that the numbers represent state and not city populations.

### Multiple graph tables vs. fields

Being able to set fields on graph tables opens up a large variety of custom query methods, one of which is explored here.

Imagine a database that holds detailed information on the relations between NPCs in a game that are made to be as realistic as possible. Two of the characters have a rocky past but finally end up married. During this period, we might have tracked their relationship by adding and removing graph edges between the two of them as they move from a stage of being friends, to dating, to hating each other, to finally ending up married.

```surql
CREATE person:one, person:two;
-- These three relations would end up deleted
RELATE person:one->friends_with->person:two;
RELATE person:one->dating->person:two;
RELATE person:one->hates->person:two;
-- Finally this would be the graph edge connecting the two
RELATE person:one->married->person:two;
```

This works well to track the current state of the relationship, but creating a more general table such as `knows` along with a number of fields can be a better method to track the changing relationship over time. The following shows the relationship between the two `person` records, along with a third record called `person:three` who went to the same school and once dated `person:one`.

```surql
CREATE person:one, person:two, person:three;
RELATE person:one->knows->person:two SET
    has_been_friends = true,
    has_dated = true,
    has_hated = true,
    married_to = true;

RELATE person:one->knows->person:three SET
    same_high_school = true,
    has_dated = true;
```

With these fields in place, it is possible to use a `WHERE` clause to do refined searches on relationships of a certain type.

```surql
SELECT 
	->knows->person AS knows,
	->knows[WHERE has_dated]->person AS has_dated,
	->knows[WHERE same_high_school
	  AND has_dated]->person AS dated_and_same_school
 FROM person:one;
```

```surql title="Output"
[
	{
		dated_and_same_school: [
			person:three
		],
		has_dated: [
			person:two,
			person:three
		],
		knows: [
			person:two,
			person:three
		]
	}
]
```

Because the `WHERE` clause simply checks for [truthiness](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness) (whether a value is present and not empty), these fields do not necessarily need to be booleans and can even be complex objects.

```surql
RELATE person:one->knows->person:two SET
	same_high_school = false,
    has_been_friends = true,
    has_dated = {
		from: d'2020-12-25',
		to: d'2023-12-25'
	},
    has_hated = {
		from: d'2023-12-25',
		to: d'2024-03-01'
	},
    married_to = {
		since: d'2024-03-01'
	};

RELATE person:one->knows->person:three SET
    same_high_school = true,
    has_dated = {
		from: d'2019-09-10',
		to: d'2020-12-31'
	};
```

With these objects, a jealous `person:two` could do a check on `person:one` to see how many relationships with `has_dated` have an end time that overlaps with the `has_dated` period of `person:one` and `person:two`.

```surql
SELECT id, ->knows[WHERE same_high_school
  AND has_dated.to > d'2020-12-25']->person FROM person:one;
```

### Recursive graph queries

Graph edges can also be queried recursively. For a full explanation of this syntax, see the page on [recursive paths](/docs/reference/query-language/language-primitives/idioms.md#recursive-paths).

Take the following example which creates five cities, each of which is connected to the next by some type of road of random length.

```surql
-- Note: 1..6 used to be inclusive until SurrealDB 3.0.0
-- Now creates 1 up to but not including 6
CREATE |city:1..=6| SET name = <string>id.id() + 'ville';
FOR $pair IN (<array>(1..=5)).windows(2) {
  	LET $city1 = type::record("city", $pair[0]);
    LET $city2 = type::record("city", $pair[1]);
    RELATE $city1->to->$city2 SET 
        type = rand::enum(["train", "road", "bike path"]),
        distance = <int>(rand::float() * 100).ceil()
};
```

While it is possible to manually move three levels down this road network, it involves a good deal of manual typing.

```surql
SELECT ->to->city->to->city->to->city AS fourth_city FROM city:1;
```

```surql title="Output"
[
	{
		fourth_city: [
			city:4
		]
	}
]
```

This can be replaced by a `@` to refer to the current record, followed by `.{3}` to represent three levels down the `to` graph edge. A level between 1 and 256 can be specified here.

```surql
SELECT @.{3}->to->city AS fourth_city FROM city:1;
```

A traditional query to show the final road info from `city:1` to the city three stops away would look like this.

```surql
SELECT ->to->city->to->city->to.* AS third_journey FROM city:1;
```

```surql title="Output"
[
	{
		fourth_city: [
			[
				{
					distance: 80,
					id: to:sw2pery99jomfhibzfrh,
					in: city:3,
					out: city:4,
					type: 'train'
				}
			]
		]
	}
]
```

To use the same query recursively, wrap the part that must be repeated (`->to->city`) inside parentheses. This will ensure that the `.{2}` part of the query only repeats `->to->city` twice, and not the final `->to.*` portion.

```surql
SELECT @.{2}(->to->city)->to.* AS third_journey FROM city:1;
```

A range can be added inside the `{}` braces. The following query that uses a range of 1 to 20 will follow the `->to->city` path up to 20 times, but will stop at the 5th and final depth because the next level returns an empty array.

```surql
city:1.{1..20}->to->city;
```

```surql title="Output"
[
	city:5
]
```

Ranges can be followed with the destructuring operator to collect fields on each depth, returning them in a single response. The following query goes five depths down the `to` graph table, returning each city and road along the way.

```surql
SELECT @.{1..5}.{ 
    id, 
    next_roads: ->to.*,
    next_cities: ->to->city
} FROM city;
```

```surql title="Output"
[
	{
		id: city:1,
		next_cities: [
			city:2
		],
		next_roads: [
			{
				distance: 33,
				id: to:bl6i9djau0pg24pqrwd9,
				in: city:1,
				out: city:2,
				type: 'road'
			}
		]
	},
	{
		id: city:2,
		next_cities: [
			city:3
		],
		next_roads: [
			{
				distance: 45,
				id: to:ybugfnlzv6kcrkaj49ig,
				in: city:2,
				out: city:3,
				type: 'road'
			}
		]
	},
	{
		id: city:3,
		next_cities: [
			city:4
		],
		next_roads: [
			{
				distance: 80,
				id: to:sw2pery99jomfhibzfrh,
				in: city:3,
				out: city:4,
				type: 'train'
			}
		]
	},
	{
		id: city:4,
		next_cities: [
			city:5
		],
		next_roads: [
			{
				distance: 29,
				id: to:42hlspf4z5lpqceyv68p,
				in: city:4,
				out: city:5,
				type: 'train'
			}
		]
	},
	{
		id: city:5,
		next_cities: [],
		next_roads: []
	}
]
```

As noted above, a `TIMEOUT` can be set for queries that may be computationally expensive. This is particularly useful when experimenting with recursive queries, which, if care is not taken, can run all the way to the maximum possible depth of 256.

Take the following example with two `person` records that like each other. Following the `likes` edge will run until the query recurses 256 times and gives up.

```surql
CREATE person:one, person:two;
RELATE person:one->likes->person:two;
RELATE person:two->likes->person:one;
-- Open-ended range
person:one.{..}->likes->person;
```

```surql title="Output"
'Exceeded the idiom recursion limit of 256.'
```

Take the following example in which three `person` records of created, each of which likes the other two `person` records. A query on the `->likes->person` path shows that the number of records doubles each time.

```surql
CREATE |person:1..4|;
FOR $person IN (SELECT * FROM person) {
  LET $others = (SELECT * FROM person WHERE id != $person.id);
    FOR $other IN $others {
        RELATE $person->likes->$other;
    }
};
RETURN [
	person:1.{2}->likes->person,
	person:1.{3}->likes->person,
	person:1.{4}->likes->person
];
```

```surql title="Output"
[
	[
		person:1,
		person:2,
		person:1,
		person:3
	],
	[
		person:3,
		person:2,
		person:1,
		person:3,
		person:3,
		person:2,
		person:1,
		person:2
	],
	[
		person:1,
		person:2,
		person:1,
		person:3,
		person:3,
		person:2,
		person:1,
		person:2,
		person:1,
		person:2,
		person:1,
		person:3,
		person:3,
		person:2,
		person:1,
		person:3
	]
]
```

Since an open-ended range can be specified in a recursive query, this would result in a full 256 attempts to recurse, multiplying the number of results by two each time for a total of 115792089237316195423570985008687907853269984665640564039457584007913129639936 records by the end.

When experimenting with recursive queries, especially open-ended ranges, it is thus recommended to use a timeout.

```surql
SELECT @.{..}.{ id, likes: ->likes->person.@ } FROM person TIMEOUT 1s;
```

### Graph clauses

_(since v2.2.0)_

The same clauses available to a `SELECT` statement can be used inside a graph query. Take the following relations for example:

```surql
CREATE person:one, person:two, person:three;
RELATE person:one->knows->person:two SET
	friends = true,
    dated = true,
    married_to = true;

RELATE person:one->knows->person:three SET
    dated = true;

RELATE person:two->knows->person:three SET
	friends = true;
```

At the `knows` path, parentheses can be used to insert clauses or an entirely new SELECT statement based on the records turned up at this point. In the following example, the `FROM knows` portion applies to all the records that a `person` knows, not the `knows` table as a whole.

```surql
SELECT 
	id, 
	->(SELECT out.id AS counterpart, !!dated AS dated
	  FROM knows) AS acquaintances
FROM person;
```

```surql title="Output"
[
	{
		acquaintances: [
			{
				counterpart: person:three,
				dated: true
			},
			{
				counterpart: person:two,
				dated: true
			}
		],
		id: person:one
	},
	{
		acquaintances: [],
		id: person:three
	},
	{
		acquaintances: [
			{
				counterpart: person:three,
				dated: false
			}
		],
		id: person:two
	}
]
```

In some cases, the dot or destructuring operator can produce the same output. The following queries are equivalent.

```surql
SELECT ->(SELECT * FROM knows) FROM person:one;
SELECT ->knows.* FROM person:one;
```

```surql title="Output"
[
	{
		"->knows": [
			{
				dated: true,
				id: knows:2tsz3aomelegp060ii7d,
				in: person:one,
				out: person:three
			},
			{
				dated: true,
				friends: true,
				id: knows:g54z9zapdkssxb4p4pjc,
				in: person:one,
				married_to: true,
				out: person:two
			}
		]
	}
]
```

However, clauses available in [`SELECT` statements](/docs/reference/query-language/statements/select.md) such as `WHERE`, `LIMIT`, `GROUP BY`, aliases and so on can be used, making a graph clause a most flexible option.

```surql
SELECT ->(SELECT *, time::now() AS queried_at
  FROM knows LIMIT 1)
  FROM person:one;
```

```surql title="Output"
[
	{
		"->knows": [
			{
				dated: true,
				id: knows:2tsz3aomelegp060ii7d,
				in: person:one,
				out: person:three,
				queried_at: d'2025-01-24T02:16:31.811Z'
			}
		]
	}
]
```

Some other examples of possible graph clauses:

```surql
CREATE |person:1..4|;

RELATE person:1->likes->person:2 SET like_strength = 20, know_in_person = true;
RELATE person:1->likes->person:3 SET like_strength = 5,  know_in_person = false;
RELATE person:2->likes->person:1 SET like_strength = 10, know_in_person = true;
RELATE person:2->likes->person:3 SET like_strength = 12, know_in_person = false;
RELATE person:3->likes->person:1 SET like_strength = 2,  know_in_person = false;
RELATE person:3->likes->person:2 SET like_strength = 9,  know_in_person = false;

SELECT ->likes AS likes FROM person;
SELECT ->(SELECT like_strength FROM likes) AS likes FROM person;
SELECT ->(SELECT like_strength FROM likes
  WHERE like_strength > 10) AS likes FROM person;
SELECT ->(likes WHERE like_strength > 10) AS likes FROM person;
SELECT ->(SELECT like_strength, know_in_person
  FROM likes ORDER BY like_strength DESC) AS likes
  FROM person;
SELECT ->(SELECT count() as count, know_in_person
  FROM likes GROUP BY know_in_person) AS likes
  FROM person;
SELECT ->(likes LIMIT 1) AS likes FROM person;
SELECT ->(likes START 1) AS likes FROM person;
```

Multiple graph tables can be selected by separating each table with a comma, in the same way as in any other `SELECT` statement. In addition, all tables can be selected by using `?` as a wildcard operator.

```surql
CREATE person:one SET name = "Þor";
CREATE dog:one SET name = "Fenrir";
CREATE cat:one SET name = "Jólakötturinn";
RELATE person:one->feeds->cat:one SET at = time::now();
RELATE dog:one->plays_with->cat:one SET at = time::now();

-- Select from both 'feeds' and 'plays_with'
SELECT <-(SELECT * FROM feeds, plays_with ORDER BY at) FROM cat:one;
-- Or any graph table
SELECT <-(SELECT * FROM ? ORDER BY at) FROM cat:one;
```

### Ranges inside graph queries

_(since v2.3.0)_

Range syntax can also be used on the edges of a graph query.

```surql
CREATE person:one, person:two, person:three, person:four;

RELATE person:one->likes:1->person:two;
RELATE person:one->likes:2->person:three;
RELATE person:one->likes:3->person:four;

person:one->likes:2..=4->person;
```

```surql title="Output"
[
	person:three,
	person:four
]
```

A common usage of range syntax on edges is when their ID has been defined as a ULID, making the `id` field random yet sortable and significant in terms of time.

```surql
RELATE character:one->speaks_to:ulid()->character:two SET content = "Greetings, adventurer!";
RELATE character:one->speaks_to:ulid()->character:two SET content = "Can you please help me? My sheep have run amok.";

SELECT
	-- Grab the latter part of the record ID, turn it into a datetime
    time::from_ulid(id.id()) AS at,
    content
FROM
    -- ULID from 2025-04-25, well before today's date
    character:one->speaks_to:01JSNG0KZSY3HJ5QSZ7JSMQMGR..;
```

```surql title="Output"
[
	{
		at: d'2025-04-25T03:37:53.246Z',
		content: 'Greetings, adventurer!'
	},
	{
		at: d'2025-04-25T03:37:53.248Z',
		content: 'Can you please help me? My sheep have run amok.'
	}
]
```

Array-based record IDs also work well inside range queries on edges.

```surql
CREATE planet:venus, telescope:one;

RELATE telescope:one->observed:[d'2025-04-24T02:02:18.204Z']->planet:venus CONTENT { 
      temperature_profile: {
        surface: 735.0,
        upper_atmosphere: 300.0
      },
      composition: {
        CO2: 96.5,
        N2: 3.5,
        SO2: 0.015
      },
};

RELATE telescope:one->observed:[d'2025-04-25T02:02:18.204Z']->planet:venus CONTENT {
      temperature_profile: {
        surface: 737.0,
        upper_atmosphere: 298.5
      },
      composition: {
        CO2: 96.6,
        N2: 3.4,
        SO2: 0.015
    }
};

SELECT id, (<-observed:[d'2025-04-24']..).{
    at: id[0], 
    surface: temperature_profile.surface,
    atmosphere: temperature_profile.upper_atmosphere
} AS observations FROM planet;
```

```surql title="Output"
[
	{
		id: planet:venus,
		observations: [
			{
				at: d'2025-04-24T02:02:18.204Z',
				atmosphere: 300,
				surface: 735
			},
			{
				at: d'2025-04-25T02:02:18.204Z',
				atmosphere: 298.5f,
				surface: 737
			}
		]
	}
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/remove

# REMOVE

The REMOVE statement is used to remove resources such as databases, tables, indexes, events and more.

The `REMOVE` statement is used to remove resources such as databases, tables, indexes, events and more.
Similar to an SQL DROP statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
REMOVE [
    ACCESS    [ IF EXISTS ] @name ON [ NAMESPACE | DATABASE ]
  | ANALYZER  [ IF EXISTS ] @name
  | API       [ IF EXISTS ] @name
  | CONFIG    [ IF EXISTS ] [ GRAPHQL | API | DEFAULT ]
  | DATABASE  [ IF EXISTS ] @name
  | EVENT     [ IF EXISTS ] @name ON [ TABLE ] @table
  | FIELD     [ IF EXISTS ] @name ON [ TABLE ] @table
  | FUNCTION  [ IF EXISTS ] @name
  | INDEX     [ IF EXISTS ] @name ON [ TABLE ] @table
  | NAMESPACE [ IF EXISTS ] @name
  | PARAM     [ IF EXISTS ] @name
  | TABLE     [ IF EXISTS ] @name
  | USER      [ IF EXISTS ] @name ON [ ROOT | NAMESPACE | DATABASE ]
]
```

**Railroad Diagram**

export const removeAst = {
  type: "Diagram",
  padding: [10, 20, 10, 20],
  children: [
    { type: "Sequence", children: [
      { type: "Terminal", text: "REMOVE" },
      { type: "Choice", index: 1, children: [

        { type: "Sequence", children: [ { type: "Terminal", text: "ACCESS" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" }, { type: "Terminal", text: "ON" }, { type: "Choice", index: 1, children: [ { type: "Terminal", text: "NAMESPACE" }, { type: "Terminal", text: "DATABASE" } ] } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "ANALYZER" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "API" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "CONFIG" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "Choice", index: 1, children: [ { type: "Terminal", text: "GRAPHQL" }, { type: "Terminal", text: "API" }, { type: "Terminal", text: "DEFAULT" } ] } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "DATABASE" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "EVENT" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" }, { type: "Terminal", text: "ON" }, { type: "Optional", child: { type: "Terminal", text: "TABLE" } }, { type: "NonTerminal", text: "@table" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "FIELD" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" }, { type: "Terminal", text: "ON" }, { type: "Optional", child: { type: "Terminal", text: "TABLE" } }, { type: "NonTerminal", text: "@table" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "FUNCTION" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "INDEX" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" }, { type: "Terminal", text: "ON" }, { type: "Optional", child: { type: "Terminal", text: "TABLE" } }, { type: "NonTerminal", text: "@table" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "NAMESPACE" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "PARAM" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "Terminal", text: "$" }, { type: "NonTerminal", text: "@name" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "TABLE" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" } ] },

        { type: "Sequence", children: [ { type: "Terminal", text: "USER" }, { type: "Optional", child: { type: "Sequence", children: [ { type: "Terminal", text: "IF" }, { type: "Terminal", text: "EXISTS" } ] } }, { type: "NonTerminal", text: "@name" }, { type: "Terminal", text: "ON" }, { type: "Choice", index: 1, children: [ { type: "Terminal", text: "ROOT" }, { type: "Terminal", text: "NAMESPACE" }, { type: "Terminal", text: "DATABASE" } ] } ] }
      ] }
    ]}
  ]
};

## Example usage
### Basic usage

The following queries show an example of how to remove resources.

```surql
REMOVE NAMESPACE surrealdb;

REMOVE DATABASE blog;

REMOVE USER writer ON NAMESPACE;

REMOVE USER writer ON DATABASE;

REMOVE ACCESS token ON NAMESPACE;

REMOVE ACCESS user ON DATABASE;

REMOVE EVENT new_post ON TABLE article;

-- Only works for Schemafull tables (i.e. tables with a schema)
REMOVE FIELD tags ON TABLE article;

REMOVE INDEX authors ON TABLE article;

-- Fails if a full-text index still references this analyzer (remove the index first)
REMOVE ANALYZER example_ascii;

REMOVE FUNCTION fn::update_author;

REMOVE PARAM $author;

REMOVE TABLE article;
```

### Using if exists clause

The following queries show an example of how to remove resources using the `IF EXISTS` clause, which will only remove the resource if it exists.

```surql
REMOVE NAMESPACE IF EXISTS surrealdb;

REMOVE DATABASE IF EXISTS blog;

REMOVE USER IF EXISTS writer ON NAMESPACE;

REMOVE USER IF EXISTS writer ON DATABASE;

REMOVE ACCESS IF EXISTS token ON NAMESPACE;

REMOVE ACCESS IF EXISTS user ON DATABASE;

REMOVE EVENT IF EXISTS new_post ON TABLE article;

REMOVE FIELD IF EXISTS tags ON TABLE article;

REMOVE INDEX IF EXISTS authors ON TABLE article;

REMOVE ANALYZER IF EXISTS example_ascii;

REMOVE FUNCTION IF EXISTS fn::update_author;

REMOVE PARAM IF EXISTS $author;

REMOVE TABLE IF EXISTS article;
```

### Usage in table views

_(since v3.0.0)_

A table used as a source for a table view cannot be removed until the table view itself has been removed.

```surql
DEFINE TABLE pc;
DEFINE TABLE pc_agg AS SELECT count(), class FROM pc GROUP BY class;
CREATE |pc:3| SET class = "Wizard";
CREATE |pc:10| SET class = "Warrior";
SELECT * FROM pc_agg;
//- Error: pc_agg requires pc to work
REMOVE TABLE pc;
REMOVE TABLE pc_agg;
-- pc_agg is now gone, pc can be removed too
REMOVE TABLE pc;
```

The `SELECT * FROM pc_agg` query shows that the table view is pulling data from the `pc` table. As long as the `pc` table exists, `pc_agg` cannot be removed.

```surql
-------- Query --------

[
  { 
    class: 'Warrior', 
    count: 10, 
    id: pc_agg:['Warrior'] 
  }, 
  { 
    class: 'Wizard', 
    count: 3, 
    id: pc_agg:['Wizard'] 
  }
]

-------- Query --------

'Invalid query: Cannot delete table `pc` on which a view is defined, table(s) `pc_agg` are defined as a view on this table.'
```

### Behaviour after removal

While all `REMOVE` statements remove the definition for a resource, some resources have additional actions when removed. They are:

* REMOVE DATABASE: This effectively deletes the database by removing the index stores, deleting all definitions, and clearing the cache. Since SurrealDB 3.2.0, this definition removes the database from the catalog immediately. This is followed by physical deletion of the underlying keys which is deferred to a background reaper (tune with [`SURREAL_RECLAIM_INTERVAL`](/docs/reference/cli/surrealdb-cli/environment-variables.md) and [`SURREAL_RECLAIM_GRACE`](/docs/reference/cli/surrealdb-cli/environment-variables.md)), allowing `REMOVE DATABASE` returns quickly even for large tenants. Recreating the same database name yields a fresh, empty database.
* REMOVE NAMESPACE: Same deferred reclaim as `REMOVE DATABASE` after SurrealDB 3.2.0, in addition to performing a remove on each database inside the namespace.
* REMOVE TABLE: Similar to the two previous statements but on a single table, and will fail if a table view depends on it. Removing a table will also send a [KILL](/docs/reference/query-language/statements/kill.md) notification for each live query defined on it.
* REMOVE INDEX: This statement also removes the index store cache and index data. Since 3.2.0, it removes the index definition immediately and thereafter reclaims index data in the background in the same way as namespace and database removal. If you are considering removing an index but want to test the behaviour out first, use an [ALTER INDEX PREPARE REMOVE](/docs/reference/query-language/statements/alter/indexes/#prepare-remove-clause) statement. This will decommission the index, after which you can test out queries to see their behaviour as they would function after the index is removed. If acceptable then the index can then be removed, or the change can be reverted by [rebuilding the index](/docs/reference/query-language/statements/rebuild.md).

Another `REMOVE` statement to note is `REMOVE FIELD`, as it does not remove any existing data. To remove the existing data, perform an `UPDATE` or `UPSERT` statement that uses `UNSET` on the field or sets the field's value to `NONE`.

For a schemaless table, the existing data will remain present until unset.

```surql
DEFINE FIELD name ON person TYPE string;
CREATE person:one SET name = "Billy";
REMOVE FIELD name ON person;

SELECT * FROM person; -- 'name' data is still there
UPDATE person; -- Does nothing
//- [{ id: person:one, name: 'Billy' }]
UPDATE person SET name = NONE; -- Must unset to remove 'name' data
```

For a schemafull table, read operations can be performed on a table that still contains data not defined in the schema. However, any updates will fail unless the field is unset to match the schema.

```surql
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name ON person TYPE string;
CREATE person:one SET name = "Billy";
REMOVE FIELD name ON person;

SELECT * FROM person; -- 'name' data is still there
UPDATE person; -- Found field 'name', but no such field exists for table 'person'
DEFINE FIELD created_at ON person TYPE datetime; -- Define a new field

-- Works because values matche schema: 'name' is set to NONE, 'created_at' has a datetime value
UPDATE person SET name = NONE, created_at = time::now();
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/return

# RETURN

The RETURN statement can be used to return an implicit value or the result of a query, and to set the return value for a transaction, block or function.

The `RETURN` statement can be used to return an implicit value or the result of a query, and to set the return value for a transaction, block, or function.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
RETURN @value
```

## Example usage
### Basic usage

`RETURN` is always followed by a value. As every data type in SurrealDB is a type of [value](/docs/reference/query-language/language-primitives/data-types/values.md), the `RETURN` statement can return anything from simple values to the result of queries.

```surql
-- Return a simple value
RETURN 123;
RETURN "I am a string!";
RETURN {
	prop: "value"
};

-- Return the result of a query
RETURN SELECT * FROM person;
RETURN (CREATE person).id;
```

Values on their own are treated as if they have an implicit `RETURN` in front. As such, the following queries return the same output as in the previous example.

```surql
123;
"I am a string!";
{
	prop: "value"
};
SELECT * FROM person;
(CREATE person).id;
```

## Transaction return value

`RETURN` statements can set the result of any transaction. This includes transactions, blocks and functions.

```surql title="Transaction return value"
BEGIN TRANSACTION;

-- We are executing quite a few queries here
LET $firstname = "John";
LET $lastname = "Doe";

LET $person = CREATE ONLY person CONTENT {
	firstname: $firstname,
	lastname: $lastname,
};

-- But because we end with a RETURN query, only the person's ID will be returned
-- The results of the other queries will be omitted.
RETURN $person.id;

-- One issue with this approach is that query errors are generic.
-- To get around that, use a block, which is executed as a transaction by itself.

COMMIT TRANSACTION;
```

## Return breaks execution

`RETURN` breaks execution of statements, functions and transactions.

```surql title="Function return value"
DEFINE FUNCTION fn::person::create($firstname: string, $lastname: string) {
	LET $person = CREATE person CONTENT {
		firstname: $firstname,
		lastname: $lastname,
	};

	-- The RETURN statement will set the return value of the custom function, and further queries will not be executed.
	RETURN $person.id;

    -- This query will never be executed
    CREATE person SET firstname = "Stephen", lastname = "Strange";
};

fn::person::create("Thanos", "Johnson");
SELECT * FROM person;
```

```surql title="Functions"
DEFINE FUNCTION fn::round::up($num: number) -> number {
    IF $num % 2 == 0 {
        RETURN $num; -- Breaks execution for the function
    };

    -- This is only executed if the RETURN inside the IF statement did not break execution
    RETURN $num + 1;
};
```

```surql title="Transactions"
BEGIN;
RETURN 1; -- Is executed
CREATE a; -- Is not executed
RETURN 2; -- Is not executed
COMMIT;
```

Lastly, if not executed inside a transaction or function, `RETURN` will break execution until the most top-level statement it is executed in. RETURN will **not** prevent top level statements from being executed, nor will it adjust their output.

```surql title="Statements"
LET $id = 123;
LET $id = {
    IF $id {
        RETURN type::record('table', $id);
    };

    RETURN table:rand();
};

-- This still executes. The `RETURN` statement only broke until the block in the variable assignment.
$id;
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/select

# SELECT

The SELECT statement can be used for selecting and querying data in a database.

The `SELECT` statement can be used for selecting and querying data in a database. Each SELECT statement supports selecting from multiple targets, which can include tables, records, edges, subqueries, parameters, arrays, objects, and other values.

The [Learn more](#learn-more) section has a video on selecting across document-style relationships.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
SELECT 
    VALUE @field | @fields [ AS @alias ] [ OMIT @fields ... ]
    FROM [ ONLY ] @targets
    [ WITH [ NOINDEX | INDEX @indexes ... ]]
    [ WHERE @conditions ]
    [ SPLIT [ ON ] @field, ... ]
    [ 
		GROUP [ ALL | [ BY ] @field, ... ] | 
		ORDER [ BY ] RAND() | @field [ COLLATE ] [ NUMERIC ] [ ASC | DESC ], ...
	]
    [ LIMIT [ BY ] @limit ]
    [ START [ AT ] @start 0 ]
    [ FETCH @fields ... ]
    [ TIMEOUT @duration ]
    [ FOR UPDATE ]
    [ TEMPFILES ]
    [ EXPLAIN [ FULL ] ]
;
```

## Example usage
### Basic usage

By default, SurrealDB returns an array of JSON-like objects called records instead of a tabular structure of rows and columns.

```surql
CREATE person:tobie SET
	name.first = "Tobie",
	address = "1 Bagshot Row",
	email = "tobie@surrealdb.com";

-- Select all fields from a table
SELECT * FROM person;

-- Select specific fields from a table
SELECT name, address, email FROM person;

-- Select all fields from a specific record
SELECT * FROM person:tobie;

-- Select specific fields from a specific record
SELECT name, address, email FROM person:tobie;

-- Select just a single record
-- Using the ONLY keyword, just an object
-- for the record in question will be returned.
-- This, instead of an array with a single object.
SELECT * FROM ONLY person:tobie;
```

An alias can be used to rename fields or change the structure of an object.

```surql
SELECT * FROM person;

-- Field `address` now shows up as "string::uppercase"
-- name.first structure now flattened into a simple field
SELECT
	name.first AS user_name,
	string::uppercase(address)
FROM person;

-- "Morgan Hitchcock" added to `name` field structure,
-- `angry_address` for field name instead of automatically
-- generated "string::uppercase(address) + '!!!'"
SELECT
	name.first,
	"Morgan Hitchcock" AS name.last,
	string::uppercase(address) + "!!!" AS angry_address
FROM person;
```

```surql title="Output"
-------- Query --------

[
	{
		address: '1 Bagshot Row',
		email: 'tobie@surrealdb.com',
		id: person:tobie,
		name: {
			first: 'Tobie'
		}
	}
]

-------- Query --------

[
	{
		"string::uppercase": '1 BAGSHOT ROW',
		user_name: 'Tobie'
	}
]

-------- Query --------

[
	{
		angry_address: '1 BAGSHOT ROW!!!',
		name: {
			first: 'Tobie',
			last: 'Morgan Hitchcock'
		}
	}
]
```

SurrealDB can also return specific fields as an array of values instead of the default array of objects. This only works if you select a single un-nested field from a table or a record.

```surql
-- Select the values of a single field from a table
SELECT VALUE name FROM person;

-- Select the values of a single field from a specific record
SELECT VALUE name FROM person:00e1nc508h9f7v63x72O;
```

### Advanced expressions

SELECT queries support advanced expression in the field projections.

```surql
-- Select nested objects/values
SELECT address.city FROM person;

-- Select all nested array values
-- note the .* syntax works to select everything from an array or object-like values
SELECT address.*.coordinates AS coordinates FROM person;
-- Equivalent to
SELECT address.coordinates AS coordinates FROM person;

-- Select one item from an array
SELECT address.coordinates[0] AS latitude FROM person;

-- Select unique values from an array
SELECT array::distinct(tags) FROM article;

-- Select unique values from a nested array across an entire table
SELECT array::group(tags) AS tags FROM article GROUP ALL;

-- Use mathematical calculations in a select expression
SELECT
	(( celsius * 1.8 ) + 32) AS fahrenheit
	FROM temperature;

-- Return boolean expressions with an alias
SELECT rating >= 4 as positive FROM review;

-- Select manually generated object structure
SELECT
	{ weekly: false, monthly: true } AS `marketing settings`
FROM user;

-- Select filtered nested array values
SELECT address[WHERE active = true] FROM person;

-- Select a person who has reacted to a post using a celebration
-- Path can be conceptualized as:
-- person->(reacted_to WHERE type='celebrate')->post
SELECT * FROM person WHERE ->(reacted_to WHERE type='celebrate')->post;

-- Select a remote field from connected out graph edges
SELECT ->likes->friend.name AS friends FROM person:tobie;

-- Use the result of a subquery as a returned field
SELECT *, (SELECT * FROM events
  WHERE type = 'activity' LIMIT 5) AS history FROM user;

-- Restructure objects in a select expression after `.` operator
SELECT address.{city, country} FROM person;
```

## Using parameters

Parameters can be used like variables to store a value which can then be used in a subsequent query.

More info on the `$parent` parameter in the second example can be seen on [the page for predefined variables](/docs/reference/query-language/language-primitives/parameters.md).

```surql
-- Store the subquery result in a variable and query that result.
LET $avg_price = (
	SELECT math::mean(price) AS avg_price FROM product GROUP ALL
).avg_price;

-- Find the name of the product where the price is higher than the avg price
SELECT name FROM product
WHERE [price] > $avg_price;

-- Use the parent instance's field in a subquery (predefined variable)
SELECT *, (SELECT * FROM events
  WHERE host == $parent.id) AS hosted_events FROM user;
```

## Numeric ranges in a `WHERE` clause

A numeric range inside a `WHERE` clause can improve performance if the range is able to replace multiple checks on a certain condition. The following code should show a modest but measurable improvement in performance between the first and second `SELECT` statement, as only one condition needs to be checked instead of two.

```surql
DELETE person;
CREATE |person:20000| SET age = (rand::float() * 120).round() RETURN NONE;

-- Assign output to a parameter so the SELECT output is not displayed
LET $_ = SELECT * FROM person WHERE age > 18 AND age < 65;
LET $_ = SELECT * FROM person WHERE age in 18..=65;
```

A numeric range inside a `WHERE` also tends to produce shorter code that is easier to read and maintain.

```surql
SELECT * FROM person WHERE age >= 18 AND age <= 65;
SELECT * FROM person WHERE age IN 18..=65;
```

## Record ranges

SurrealDB supports the ability to query a range of records, using the record ID. The record ID ranges, retrieve records using the natural sorting order of the record IDs. These range queries can be used to query a range of records in a timeseries context. You can see more here about [array-based Record IDs](/docs/reference/query-language/language-primitives/data-types/record-ids.md#array-based-record-ids).

```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', time::now()];
-- Select all temperature records with IDs less than a maximum value
SELECT * FROM temperature:..['London', '2022-08-29T08:09:31'];
-- Select all temperature records with IDs greater than a minimum value
SELECT * FROM temperature:['London', '2022-08-29T08:03:39']..;
-- 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'];
```

Using a record range is more performant than the `WHERE` clause, as it does not require a table scan.

```surql
-- Create 5000 `person` records
CREATE |person:1..5000| RETURN NONE;

-- Set the starting time
LET $now = time::now();
-- Put the output somewhere so it won't clutter the screen
LET $_ = SELECT * FROM person:1..5000;
-- Get the elapsed time
LET $time1 = time::now() - $now;

LET $now = time::now();
LET $_ = SELECT * FROM person WHERE id >= 1 and id <= 5000;
LET $time2 = time::now() - $now;
RETURN [$time1, $time2];
```

## Skip certain fields using the `OMIT` clause

Sometimes, especially with tables containing numerous columns, it is desirable to select all columns except a few specific ones. The `OMIT` clause can be used in this case.

```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;
```

## More on using the `FROM` clause

The `FROM` clause can be used on targets beyond just a single table or record name.

```surql
-- 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;
```

## Filter queries using the `WHERE` clause

As with traditional SQL queries, a SurrealDB SELECT query supports conditional filtering using a `WHERE` clause. If the expression in the `WHERE` clause [is truthy](/docs/reference/query-language/language-primitives/data-types/values.md#values-and-truthiness) (is present and not an empty value), then the respective record will be returned.

```surql
-- Simple conditional filtering
SELECT * FROM article WHERE published = true;

-- Conditional filtering based on graph edges
SELECT * FROM profile WHERE count(->experience->organisation) > 3;

-- Conditional filtering based on graph edge properties
SELECT * FROM person WHERE ->(reaction WHERE type='celebrate')->post;

-- Conditional filtering with boolean logic
SELECT * FROM user WHERE (admin AND active) OR owner = true;

-- Select filtered nested array values
SELECT address[WHERE active = true] FROM person;

-- Select names for 'person' records as long as 'name' is present
-- and not an empty string ""
SELECT name FROM person WHERE name;
```

## The `SPLIT` clause

As SurrealDB supports arrays and nested fields within arrays, it is possible to use the [`SPLIT`](/docs/reference/query-language/clauses/split.md) clause to split the result on a specific field name, returning each value in an array as a separate value, along with the record content itself. This is useful in data analysis contexts.

```surql
CREATE user SET
    name = "Name",
    emails = ["me@me.com", "longer_email@other_service.com"];

-- Split the results by each value in an array
SELECT * FROM user SPLIT emails;
```

```surql title="Output"
[
	{
		emails: 'me@me.com',
		id: user:tr5sxe8iygdco05faoh0,
		name: 'Name'
	},
	{
		emails: 'longer_email@other_service.com',
		id: user:tr5sxe8iygdco05faoh0,
		name: 'Name'
	}
]
```

Other examples using the `SPLIT` clause:

```surql
-- Split the results by each value in a nested array
SELECT * FROM country SPLIT locations.cities;

-- Filter the result of a subquery
SELECT * FROM (SELECT * FROM person SPLIT loggedin)
  WHERE loggedin > '2023-05-01';
```

## The `GROUP BY` and `GROUP ALL` clause

SurrealDB supports data aggregation and grouping, with support for multiple fields, nested fields, and aggregate functions. In SurrealDB, every field which appears in the field projections of the select statement (and which is not an aggregate function), must also be present in the [`GROUP BY`](/docs/reference/query-language/clauses/group.md) clause.

```surql
-- Group records by a single field
SELECT country FROM user GROUP BY country;

-- Group results by a nested field
SELECT settings.published FROM article GROUP BY settings.published;

-- Group results by multiple fields
SELECT gender, country, city FROM person GROUP BY gender, country, city;

-- Use an aggregate function to select unique values from a nested array across an entire table
SELECT array::group(tags) AS tags FROM article GROUP ALL;
```

A longer example of grouping using aggregate functions:

```surql
INSERT INTO person [
    { gender: "M", age: 20, country: "Japan" },
    { gender: "M", age: 25, country: "Japan" },
    { gender: "F", age: 23, country: "US" },
    { gender: "F", age: 30, country: "US" },
    { gender: "F", age: 25, country: "Korea" },
    { gender: "F", age: 45, country: "UK" },
];

SELECT
	count() AS total,
	math::mean(age) AS average_age,
	gender,
	country
FROM person
GROUP BY gender, country;

-- Get the total number of records in a table
SELECT count() AS number_of_records FROM person GROUP ALL;
```

```surql title="Output"
-------- Query --------

[
	{
		average_age: 25,
		country: 'Korea',
		gender: 'F',
		total: 1
	},
	{
		average_age: 45,
		country: 'UK',
		gender: 'F',
		total: 1
	},
	{
		average_age: 26,
		country: 'US',
		gender: 'F',
		total: 2
	},
	{
		average_age: 22,
		country: 'Japan',
		gender: 'M',
		total: 2
	}
]

-------- Query --------

[
	{
		number_of_records: 6
	}
]
```

### Bare `count()` implies `GROUP ALL`

_(since v3.3.0)_

When every expression in the projection is a bare zero-argument [`count()`](/docs/reference/query-language/functions/database-functions/count.md) (with or without an alias), SurrealDB implies `GROUP ALL`. Outside an aggregation, `count()` has nothing to count and would otherwise return the constant `1` once per record.

```surql
-- count() with explicit GROUP ALL
SELECT count() AS number_of_records FROM person GROUP ALL;

-- From 3.3.0, equivalent when the projection is only bare count()
SELECT count() AS number_of_records FROM person;
```

The implication does not apply when:

- The projection includes `count(field)` (counts a collection per record).
- The projection includes `*` or any non-`count()` field (for example the existence-probe idiom `SELECT *, count() AS c FROM person:1`).
- The query already has an explicit `GROUP BY` or `GROUP ALL`.
- The query uses `SPLIT`.
- The query is `SELECT VALUE count() …` (keeps its per-row meaning).

Write `GROUP ALL` explicitly when you want the aggregation to be obvious in the query text, when you need to support older releases, or whenever the projection is not a bare `count()`-only list.

To return the most recently modified record per group instead of aggregate totals, see [Latest record per group](/docs/learn/querying/concepts-and-guides/subqueries-and-advanced-patterns.md#latest-record-per-group).

### `GROUP` and `SPLIT` incompatibility

The `GROUP` and `SPLIT` clauses are incompatible with each other due to opposing behaviour: while `SPLIT` is a post-processing clause that multiplies the output of a query, `GROUP` works in the other way by collapsing the output.

Versions before 3.0.0 allowed these two clauses to be used together, after which attempting to do so results in a parsing error.

```surql
SELECT * FROM person SPLIT name GROUP BY name;
```

```surql title="Output"
'Parse error: SPLIT and GROUP are mutually exclusive
 //- [6:22]
  |
6 | SELECT * FROM person SPLIT name GROUP BY name;
  |                      ^^^^^^^^^^ SPLIT cannot be used with GROUP
 //- [6:33]
  |
6 | SELECT * FROM person SPLIT name GROUP BY name;
  |                                 ^^^^^^^^^^^^^ GROUP cannot be used with SPLIT
'
```

Disallowing the two clauses together forces a query that uses both to have one inside a subquery, which makes it clear which operation is to be performed first.

```surql
CREATE user SET
    name = "Jack",
    emails = ["my@firstemail.com", "another@builder.com"],
    age = 37;

CREATE user SET
    name = "Ellen",
    emails = ["ruler@forest.com", "wife@tom.com"],
    age = 50;

CREATE user SET
    name = "Phillip",
    emails = ["prior@kingsbridge.com", "boss@remigius.com"],
    age = 50;

SELECT age, emails FROM (SELECT * FROM user SPLIT emails) GROUP BY age;

SELECT age, emails
FROM (
  SELECT age, array::group(emails) AS emails
  FROM user
  GROUP BY age
)
SPLIT emails;
```

### Using a `COUNT` index to speed up `count()` in `GROUP ALL` queries

_(since v3.0.0)_

To speed up the `count()` function along with `GROUP ALL` to get the total number of records in a table, a `COUNT` index can be used. This keeps track of the total number of records as a single value as opposed to a dynamic iteration of the table to get the full count every time a query is run. From 3.3.0, a bare `count()` projection [implies `GROUP ALL`](#bare-count-implies-group-all); the examples below keep the explicit form.

```surql
DEFINE INDEX person_count ON person COUNT;
SELECT count() AS number_of_records FROM person GROUP ALL;
```

### `math::stddev()` and `math::variance()` in table views

_(since v3.0.0)_

The `math::stddev()` and `math::variance()` functions can also be used in table views.

```surql
DEFINE TABLE person SCHEMALESS;
DEFINE TABLE person_stats AS
	SELECT
		count(),
		age,
		math::stddev(score) AS score_stddev,
		math::variance(score) AS score_variance
	FROM person
	GROUP BY age;

INSERT INTO person [
    { id: person:alice,          age: 25, score: 80 },
    { id: person:alices_rival,   age: 25, score: 88 },
    { id: person:bob,            age: 24, score: 90 },
    { id: person:bobs_rival,     age: 24, score: 99 },
    { id: person:charlie,        age: 23, score: 70 },
    { id: person:charlies_rival, age: 23, score: 77 }
];

SELECT * FROM person_stats WHERE age >= 24;
```

Output:

```surql
[
	{
		age: 24,
		count: 2,
		id: person_stats:[
			24
		],
		score_stddev: 6.363961030678927719607599259dec,
		score_variance: 40.50dec
	},
	{
		age: 25,
		count: 2,
		id: person_stats:[
			25
		],
		score_stddev: 5.656854249492380195206754897dec,
		score_variance: 32dec
	}
]
```

## Sort records using the `ORDER BY` clause

To sort records, SurrealDB allows ordering on multiple fields and nested fields. Use the `ORDER BY` clause to specify a comma-separated list of field names that should be used to order the resulting records. 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.

_(since v3.2.5)_

Fields used only for sorting do not need to appear in the `SELECT` list. Sorting runs before projection, so the full record is available when the order keys are evaluated:

```surql
-- Return only event and subject, ordered by a field that is not selected
SELECT event, subject FROM audit_log ORDER BY at DESC;
```

```surql
-- Order records randomly
SELECT * FROM user ORDER BY rand();

-- Order records descending by a single field
SELECT * FROM song ORDER BY rating DESC;

-- Order records by multiple fields independently
SELECT * FROM song ORDER BY artist ASC, rating DESC;

-- Order text fields with Unicode collation
SELECT * FROM article ORDER BY title COLLATE ASC;

-- Order text fields with which include numeric values
SELECT * FROM article ORDER BY title NUMERIC ASC;
```

For more detail on collation, numeric ordering, and random order, see the [`ORDER` clause](/docs/reference/query-language/clauses/order.md).

## The `LIMIT` clause

To limit the number of records returned, use the `LIMIT` clause.

```surql
-- Select only the top 50 records from the person table
SELECT * FROM person LIMIT 50;
```

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. It is important to note that the `START` count starts from 0.

```surql
-- Start at record 50 and select the following 50 records
SELECT * FROM user LIMIT 50 START 50;
```

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;
```

```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
]
```

## Connect targets using the FETCH clause

Two of the most powerful features in SurrealDB are [record links](/docs/reference/query-language/language-primitives/record-links.md) and [graph connections](/docs/reference/query-language/statements/relate.md).

Instead of pulling data from multiple tables and merging that data together, SurrealDB allows you to traverse related records efficiently without needing to use JOINs.

To fetch and replace records with the remote record data, use the [`FETCH`](/docs/reference/query-language/clauses/fetch.md) clause to specify the fields and nested fields which should be fetched in-place, and returned in the final statement response output.

```surql
-- Select all the review information
-- and the artist's email from the artist table
SELECT *, artist.email FROM review FETCH artist;

-- Select all the article information
-- only if the author's age (from the author table) is under 30.
SELECT * FROM article WHERE author.age < 30 FETCH author;
```

## The `TIMEOUT` clause

When processing a large result set with many interconnected records, it is possible to use the `TIMEOUT` keyword to specify a timeout duration for the statement. If the statement continues beyond this duration, then the transaction will fail, and the statement will return an error.

```surql
-- Cancel this conditional filtering based on graph edge properties
-- if it's not finished within 5 seconds
SELECT * FROM person
  WHERE ->knows->person->(knows
  WHERE influencer = true) TIMEOUT 5s;
```

## The `FOR UPDATE` clause

_(since v3.3.0)_

Inside a transaction, a plain `SELECT` reads a consistent [snapshot](/docs/learn/querying/concepts-and-guides/transactions.md#snapshot-isolation), but the transaction can still commit successfully even if the records it read were modified by another transaction in the meantime. When later writes depend on a value that was only read, this allows the transaction to commit decisions based on stale data.

The `FOR UPDATE` clause registers each selected record for commit-time conflict detection: the enclosing transaction will only commit if none of those records were modified by another transaction after the snapshot was taken. If a concurrent modification is detected, `COMMIT` fails with a transaction conflict error, and the transaction can be retried.

```surql
BEGIN;

-- Read the account purely as a decision input; it is not written below
LET $account = SELECT * FROM ONLY account:one FOR UPDATE;

IF $account.status = "active" {
    CREATE order SET account = account:one, item = "widget";
};

-- Fails with a retryable conflict error if another transaction
-- modified account:one after this transaction's snapshot
COMMIT;
```

The clause takes no lock, and no transaction waits on another. A concurrent transaction can write a registered record and commit normally; the conflict surfaces on the transaction that read the record, whose `COMMIT` then fails. Where `SELECT ... FOR UPDATE` holds a row lock in other databases, here it adds a check, so plan for retries rather than for waiting.

`FOR UPDATE` is only needed for records that are read as decision inputs but never written within the transaction. A record that the transaction also writes is already protected, because the write itself is checked for conflicts at commit time.

The `FOR UPDATE` clause requires record ids as targets, and each selected record is protected individually. It does not lock tables, ranges, or query predicates, so it does not prevent new matching records from being created by concurrent transactions.

A target can be named literally or passed through a parameter, so `SELECT * FROM $record FOR UPDATE` behaves the same as naming the record id inline, as long as the parameter holds a record id rather than a table.

Reading a record id that does not exist registers that id too, so a concurrent transaction that creates it also causes this transaction's commit to fail. This is what makes the clause suitable for guarding a get-or-create path, where the decision rests on a record being absent.

The clause cannot be combined with the `VERSION`, `GROUP`, or `SPLIT` clauses, nor with `LIMIT` across multiple targets, and it cannot be used with targets other than record ids.

Because the conflict registration is only validated at commit time, `FOR UPDATE` needs a transaction that commits. A read nested inside an expression is accommodated by promoting the enclosing statement to a write, so the registration still reaches a committing transaction. A read-only transaction has no commit to validate against, and rejects the clause.

## The `TEMPFILES` clause

When processing a large result set with many records, it is possible to use the `TEMPFILES` clause to specify that the statement should be processed in temporary files rather than memory.

This significantly reduces memory usage in exchange for slower performance.

```surql
-- Select every person and order them by name using temporary files rather than memory.
SELECT * FROM person ORDER BY name TEMPFILES;
```

This requires the temporary directory to be set in the server configuration or when using the [`surreal start`](/docs/reference/cli/surrealdb-cli/commands/start.md) command.

## The `EXPLAIN` clause

When `EXPLAIN` is used, the `SELECT` statement returns an explanation, essentially revealing the execution plan to provide transparency and understanding of the query performance. `EXPLAIN` can be followed by `FULL` to see the number of executed records.

Here is the result when the field 'email' is not indexed. We can see that the execution plan will iterate over the whole table.

```surql
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 --------

[
	{
		detail: {
			table: 'person'
		},
		operation: 'Iterate Table'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	}
]

-------- Query --------

[
	{
		detail: {
			table: 'person'
		},
		operation: 'Iterate Table'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	},
	{
		detail: {
			count: 1
		},
		operation: 'Fetch'
	}
]
```

Here is the result when the 'email' field is indexed. We can see that the execution plan will proceed by utilizing the index.

```surql
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 --------

[
	{
		detail: {
			plan: {
				index: 'fast_email',
				operator: '=',
				value: 'tobie@surrealdb.com'
			},
			table: 'person'
		},
		operation: 'Iterate Index'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	}
]

-------- Query --------

[
	{
		detail: {
			plan: {
				index: 'fast_email',
				operator: '=',
				value: 'tobie@surrealdb.com'
			},
			table: 'person'
		},
		operation: 'Iterate Index'
	},
	{
		detail: {
			type: 'Memory'
		},
		operation: 'Collector'
	},
	{
		detail: {
			count: 1
		},
		operation: 'Fetch'
	}
]
```

## The `WITH` clause

The query planner can replace the standard table iterator with one or several index iterators based on the structure and requirements of the query. However, there may be situations where manual control over these potential optimizations is desired or required.

For instance, 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.

- `WITH INDEX @indexes ...` restricts the query planner to using only the specified index(es)
- `WITH NOINDEX` forces the query planner to use the table iterator.

```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';
```

## The `ONLY` clause

If you are selecting just one single resource, it's possible to use the `ONLY` clause to filter that result from an array.

```surql
SELECT * FROM ONLY person:john;
```

If you are selecting from a resource where it is possible that multiple resources are returned, it is required to `LIMIT` the result to just one.
This is needed, because the query would otherwise not be deterministic.

```surql
-- Fails
SELECT * FROM ONLY table_name;
-- Succeeds
SELECT * FROM ONLY table_name LIMIT 1;
```

## The `VERSION` clause

If you are using a versioned storage engine (in-memory SurrealMX, RocksDB where supported, or beta SurrealKV) with versioning enabled, write operations on a table will track the state of each record at the time at which the operation was performed.

You can then perform time-travel queries that query the state of a table at a specific point in time by using the `VERSION` clause in a `SELECT` query.

This clause can be used on startup by adding `versioned=true` to the [datastore configuration parameters](/docs/reference/cli/surrealdb-cli/commands/start.md#datastore-configuration) when executing the `surreal start` command.

```bash title="Examples of surreal start with versioning enabled"
# Start with a versioned in-memory datastore with a root user
surreal start --user root --pass secret "mem://?versioned=true"

# Or disable authentication for quick anonymous access
surreal start --unauthenticated "mem://?versioned=true"

# Start with a versioned RocksDB datastore with a root user
surreal start --user root --pass secret "rocksdb://my_db?versioned=true"
```

The `VERSION` clause is always followed by a [datetime](/docs/reference/query-language/language-primitives/data-types/datetimes.md). When the specified timestamp does not exist, an empty array is returned.

```surql
CREATE user:john SET name = 'John';

-- user:john did not exist two days ago, returns empty array
SELECT * FROM user:john VERSION time::now() - 2d;

-- Slee for five seconds
SLEEP 5s;

-- Returns user:john as the record existed three seconds ago
SELECT * FROM user:john VERSION time::now() - 3s;
```

The `VERSION` clause can be used via other dynamic expressions such as functions as long as they resolve to a datetime.

```surql
DEFINE FUNCTION fn::yesterday() -> datetime { time::now() - 1d };

CREATE user:john SET name = 'John';

SELECT * FROM user VERSION fn::yesterday();
```

## Selecting inside graph queries

_(since v2.2.0)_

A `SELECT` statement and/or its clauses can be used inside graph queries as well at the graph edge portion of the query.

```surql
-- Note: 1..4 used to be inclusive until SurrealDB 3.0.0
-- Now creates 1 up to but not including 4
CREATE |person:1..4|;

RELATE person:1->likes->person:2 SET like_strength = 20, know_in_person = true;
RELATE person:1->likes->person:3 SET like_strength = 5,  know_in_person = false;
RELATE person:2->likes->person:1 SET like_strength = 10, know_in_person = true;
RELATE person:2->likes->person:3 SET like_strength = 12, know_in_person = false;
RELATE person:3->likes->person:1 SET like_strength = 2,  know_in_person = false;
RELATE person:3->likes->person:2 SET like_strength = 9,  know_in_person = false;

SELECT ->likes AS likes FROM person;
SELECT ->(SELECT like_strength FROM likes) AS likes FROM person;
SELECT ->(SELECT like_strength FROM likes
  WHERE like_strength > 10) AS likes FROM person;
SELECT ->(likes WHERE like_strength > 10) AS likes FROM person;
SELECT ->(SELECT like_strength, know_in_person
  FROM likes ORDER BY like_strength DESC) AS likes
  FROM person;
SELECT ->(SELECT count() as count, know_in_person
  FROM likes GROUP BY know_in_person) AS likes
  FROM person;
SELECT ->(likes LIMIT 1) AS likes FROM person;
SELECT ->(likes START 1) AS likes FROM person;
```

For more examples, see the [graph clauses](/docs/reference/query-language/statements/relate.md#graph-clauses) section of the page on the `RELATE` statement.

## Learn more

This explainer video covers selecting across document-style relationships, including record links and nested data:

[Watch on YouTube](https://www.youtube.com/watch?v=TyX45cyZ-W0)

---

Source: https://surrealdb.com/docs/reference/query-language/statements/show

# SHOW

The SHOW statement can be used to replay changes made to a table.

Changefeeds allow you to retrieve and sync changes from SurrealDB to external systems and platforms using the `SHOW` statement.

For updates to existing records, the shape of each mutation depends on how the changefeed was defined on the table. When you use `INCLUDE ORIGINAL` with `CHANGEFEED`, stored differences are **reverse diffs**: they describe the changes required to go from the record’s state after the write **back** to the state immediately before it.

For updates to existing records, the shape of each mutation depends on how the changefeed was defined on the table. When you use [`INCLUDE ORIGINAL`](/docs/reference/query-language/statements/define/table.md#example-usage) with `CHANGEFEED`, stored differences are **reverse diffs**: they describe the changes required to go from the record’s state after the write **back** to the state immediately before it. See the [`DEFINE TABLE`](/docs/reference/query-language/statements/define/table.md#example-usage) examples for sample responses with and without `INCLUDE ORIGINAL`.

## Requirements

* You must first [`DEFINE`](/docs/reference/query-language/statements/define/table.md#example-usage) a changefeed on either a table or a database.

### Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
SHOW CHANGES FOR TABLE @tablename
	SINCE @timestamp | @versionstamp
	[ LIMIT @number ]
```

## Example usage

### Basic usage

The following expression shows usage of the SHOW statement.

```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"];
UPDATE reading SET story += ["there was a database"];

-- Replay changes to the reading table since a date
SHOW CHANGES FOR TABLE reading SINCE d"2023-09-07T01:23:52Z" LIMIT 10;
-- Replay changes to the reading table since a versionstamp
SHOW CHANGES FOR TABLE reading SINCE 1 LIMIT 10;
```

Assuming the datetime above matches with the one when the changefeed was established, the response for both queries will be as follows.

```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: 116395873313095680
	},
	{
		changes: [
			{
				update: {
					id: reading:kpqxnt8h4me84zed9fgf,
					story: [
						'Once upon a time'
					]
				}
			}
		],
		versionstamp: 116395873313161216
	},
	{
		changes: [
			{
				update: {
					id: reading:kpqxnt8h4me84zed9fgf,
					story: [
						'Once upon a time',
						'there was a database'
					]
				}
			}
		],
		versionstamp: 116395873313161217
	}
]
```

```surql title="Output if INCLUDE ORIGINAL set on changefeed"
[
	{
		changes: [
			{
				define_table: {
					changefeed: {
						expiry: 3d,
						original: true
					},
					drop: false,
					id: 0,
					kind: {
						kind: 'ANY'
					},
					name: 'reading',
					permissions: {
						create: false,
						delete: false,
						select: false,
						update: false
					},
					schemafull: false
				}
			}
		],
		versionstamp: 116395871166398464
	},
	{
		changes: [
			{
				update: {
					id: reading:q0lovlass9zgq19l1kfb,
					story: [
						'Once upon a time, '
					]
				}
			}
		],
		versionstamp: 116395871166464000
	},
	{
		changes: [
			{
				current: {
					id: reading:q0lovlass9zgq19l1kfb,
					story: [
						'Once upon a time, ',
						'there was a database'
					]
				},
				update: [
					{
						op: 'remove',
						path: '/story/1'
					}
				]
			}
		],
		versionstamp: 116395871166529536
	}
]
```

### Deletes with `INCLUDE ORIGINAL`

_(since v3.2.0)_

When a table changefeed is defined with `INCLUDE ORIGINAL`, delete events surface the record's pre-image under `delete.original`. Plain changefeeds (without `INCLUDE ORIGINAL`) still emit `{ delete: { id } }` only.

```surql
DEFINE TABLE original_tb CHANGEFEED 1h INCLUDE ORIGINAL;
CREATE original_tb:1 SET name = 'Tobie';
DELETE original_tb:1;
SHOW CHANGES FOR TABLE original_tb SINCE 0;
```

```surql title="Output"
[
	{
		changes: [
			{
				delete: {
					id: original_tb:1,
					original: {
						id: original_tb:1,
						name: 'Tobie'
					}
				}
			}
		],
		versionstamp: /* … */
	}
]
```

Note the following when working with the versionstamps of a changefeed:

* Changefeeds defined on tables are implemented via a single `CHANGEFEED` on the database level. As such, `SHOW CHANGES FOR TABLE sometable` will only show versionstamps in sequential order if `sometable` is the database's only table.
* The `versionstamp` output above is due to an extra two bytes needed for more detailed ordering needed in the FoundationDB distributed [SurrealDB backend](/docs/learn/data-models/architecture.md). To turn these versionstamps into a normal sequence of numbers, a right shift of sixteen bits (`>> 16`) can be used.
* A `SINCE <number` greater than the current sequential number will return an empty array.
* `SINCE <time>` needs to be a datetime after which the `CHANGEFEED` was defined.

Versionstamps carry the following two guarantees:

* Versionstamps monotonically increase.
* Versionstamp format is universal across various backends.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/sleep

# SLEEP

The SLEEP statement is used to introduce a delay or pause in the execution of a query or a batch of queries for a specific amount of time.

The `SLEEP` statement is used to introduce a delay or pause in the execution of a query or a batch of queries for a specific amount of time.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
SLEEP @duration;
```

## Example usage

The following query shows example usage of this statement.

```surql
-- Sleep one second
SLEEP 1s;
-- Sleep 100 milliseconds
SLEEP 100ms;
```

For more dynamic usage of sleep, see SurrealDB's [sleep](/docs/reference/query-language/functions/database-functions/sleep.md) function.

## SLEEP during parallel operations

A `SLEEP` statement 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:50000| 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;
SLEEP 50ms;
INFO FOR INDEX unique_name ON TABLE user;
SLEEP 50ms;
INFO FOR INDEX unique_name ON TABLE user;
SLEEP 50ms;
INFO FOR INDEX unique_name ON TABLE user;
```

```surql title="Possible output"
-------- Query 1 --------
{
	building: {
		count: 0,
		status: 'initial'
	}
}

-------- Query 2 --------
{
	building: {
		count: 17250,
		status: 'initial'
	}
}

-------- Query 3 --------
{
	building: {
		count: 33542,
		status: 'initial'
	}
}

-------- Query 4 --------
{
	building: {
		status: 'built'
	}
}
```

## Use cases

`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/statements/throw

# THROW

The THROW statement can be used to stop execution of a query and return information on the underlying problem

The `THROW` statement can be used to throw an error in a place where something unexpected is happening. Execution of the query will be aborted and the error will be returned to the client. While a string is most commonly seen after a `THROW` statement, any [value](/docs/reference/query-language/language-primitives/data-types/values.md) at all can be used as error output.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
THROW @error
```

## Example usage

The following query shows example usage of this statement.

```surql
-- Throw an error
THROW "some error message";
```
The following query shows the `THROW` statement being used to send back a custom error to the client.

```surql
-- In this example, we throw a custom error when a user provides invalid signin details
DEFINE ACCESS user ON DATABASE TYPE RECORD
	SIGNIN {
		LET $user = (SELECT * FROM user WHERE username = $username
		  AND crypto::argon2::compare(password, $password));
		IF !$user {
			THROW "You either provided invalid credentials, or a user with the username " + <string> $username + " might not exist.";
		};

		RETURN $user;
	}
	DURATION FOR SESSION 1w
;
```

`THROW` can contain any value: arrays, objects, and so on. It can even take the value of a separate `SELECT` statement:

```surql
CREATE event:one SET time = d'2025-10-08T07:15:04.994633Z';
CREATE event:two SET time = d'2025-10-08T07:15:04.996995Z';
THROW SELECT * FROM event;
```

```surql title="Output"
"An error occurred: [{ id: event:one, time: d'2025-10-08T07:15:04.994633Z' }, { id: event:two, time: d'2025-10-08T07:15:04.996995Z' }]"
```

`THROW` can also be used to cancel a transaction, usually inside an `IF` statement checking a condition.

```surql
BEGIN TRANSACTION;
LET $transfer_amount = 150;
CREATE account:one SET dollars =  100;
CREATE account:two SET dollars =  100;
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
	}
]
```

---

Source: https://surrealdb.com/docs/reference/query-language/statements/update

# UPDATE

The UPDATE statement can be used to update records in the database. If they already exist, they will be updated. If they do not exist, no records will be updated.

The `UPDATE` statement can be used to update existing records in the database. If the record does not exist, the statement will succeed but no records will be updated.

> [!NOTE]
> This statement can not be used to create graph relationships. For that, use the [`RELATE`](/docs/reference/query-language/statements/relate.md) or [`INSERT`](/docs/reference/query-language/statements/insert.md) statement.

> [!NOTE]
> `UPDATE` does not create records that do not exist. To update a record and create it if it does not exist, use the [`UPSERT`](/docs/reference/query-language/statements/upsert.md) statement.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
UPDATE [ ONLY ] @targets
	[ CONTENT @value
	  | MERGE @value
	  | PATCH @value
	  | REPLACE @value
	  | [ SET @field = @value, ... | UNSET @field, ... ]
	]
	[ WHERE @condition ]
	[ RETURN NONE | RETURN BEFORE | RETURN AFTER | RETURN DIFF | RETURN @statement_param, ... | RETURN VALUE @statement_param ]
	[ TIMEOUT @duration ]
	[ EXPLAIN [ FULL ]]
;
```

> [!NOTE]
> `@target` refers to either record output including an `id` field, or a [record ID](/docs/reference/query-language/language-primitives/data-types/record-ids.md) on its own.

## Example usage

Let's look at some examples of how to use the `UPDATE` statement. First we'll create two `person` records with the [`CREATE`](/docs/reference/query-language/statements/create.md) statement so that the examples will produce a meaningful output.

```surql
-- Create a Schemaless person table with a random id
CREATE person CONTENT {
    name: 'John',
    company: 'SurrealDB Studio',
    skills: ['JavaScript', 'Go' , 'SurrealQL']
};

-- Create another person with a specific id
CREATE person:tobie CONTENT {
    name: 'Tobie',
    company: 'SurrealDB',
    skills: ['JavaScript', 'Go' , 'SurrealQL']
};
```

Let's say we wanted to update the `person` table with a new field `enjoys` (an array), a new skill `breathing` to the existing `skills` field (another array), add a new numeric field called `dollars`, and a `last_name` field that relies on the existing `name` field to set its value.

To do this we would use the following query.

```surql
-- Update all records in a table
-- The `enjoys` field will also be an array.
-- The += operator alone is enough to infer the type
UPDATE person SET 
	dollars = 50,
	skills += 'breathing',
	enjoys += 'reading',
	full_name = name + ' Mc' + name + 'erson';
```

```surql title="Output"
[
	{
		company: 'SurrealDB Studio',
		dollars: 50,
		enjoys: [
			'reading'
		],
		full_name: 'John McJohnerson',
		id: person:j1qov2pxey3p8s6hqlev,
		name: 'John',
		skills: [
			'JavaScript',
			'Go',
			'SurrealQL',
			'breathing'
		]
	},
	{
		company: 'SurrealDB',
		dollars: 50,
		enjoys: [
			'reading'
		],
		full_name: 'Tobie McTobieerson',
		id: person:tobie,
		name: 'Tobie',
		skills: [
			'JavaScript',
			'Go',
			'SurrealQL',
			'breathing'
		]
	}
]
```

For more specific updates, you can specify a record ID to update a single record. The following query will update the record with the ID `person:tobie` to add "Rust" as a skill.

```surql
-- Update a record with a specific string id to add a new skill: 'Rust'
UPDATE person:tobie SET skills += 'Rust';
```

```surql title="Output"
[
	{
		company: 'SurrealDB',
		dollars: 50,
		enjoys: [
			'reading'
		],
		full_name: 'Tobie McTobieerson',
		id: person:tobie,
		name: 'Tobie',
		skills: [
			'JavaScript',
			'Go',
			'SurrealQL',
			'breathing',
			'Rust'
		]
	}
]
```

The `-=` operator can be used to remove an item from an array or reduce a numeric value by a certain value.

```surql
UPDATE person:tobie SET 
	skills -= 'Go', 
	dollars -= 1;
```

```surql title="Output"
[
	{
		company: 'SurrealDB',
		dollars: 49,
		enjoys: [
			'reading'
		],
		full_name: 'Tobie McTobieerson',
		id: person:tobie,
		name: 'Tobie',
		skills: [
			'JavaScript',
			'SurrealQL',
			'breathing',
			'Rust'
		]
	}
]
```

You can also remove a field from a record using the `UNSET` keyword or by setting the field to `NONE`.

```surql
-- Remove the company field by setting it to NONE or using the UNSET keyword
UPDATE person:tobie SET company = NONE;

UPDATE person:tobie UNSET company;
```

```surql title="Output"
[
	{
		dollars: 49,
		enjoys: [
			'reading'
		],
		full_name: 'Tobie McTobieerson',
		id: person:tobie,
		name: 'Tobie',
		skills: [
			'JavaScript',
			'SurrealQL',
			'breathing',
			'Rust'
		]
	}
]
```

## Conditional update with `WHERE` clause

The `UPDATE` statement supports conditional matching of records using a `WHERE` clause. If the expression in the `WHERE` clause evaluates to `true`, then the respective record will be updated.

```surql
-- Update all records which match the condition that `company` is not equal to "SurrealDB"
UPDATE person SET skills += "System design"
  WHERE company != "SurrealDB";
```

```surql title="Output"
[
	{
		company: 'SurrealDB Studio',
		dollars: 50,
		enjoys: [
			'reading'
		],
		full_name: 'John McJohnerson',
		id: person:i5z3i64cpqpo8jtr6jww,
		name: 'John',
		skills: [
			'JavaScript',
			'Go',
			'SurrealQL',
			'breathing',
			'System design'
		]
	},
	{
		dollars: 49,
		enjoys: [
			'reading'
		],
		full_name: 'Tobie McTobieerson',
		id: person:tobie,
		name: 'Tobie',
		skills: [
			'JavaScript',
			'SurrealQL',
			'breathing',
			'Rust',
			'System design'
		]
	}
]
```

### Evaluation order

_(since v3.3.0)_

The `WHERE` condition is checked before the data clause is evaluated, so a data clause with side effects only runs for the records the condition matches. The same applies to [`UPSERT`](/docs/reference/query-language/statements/upsert.md#evaluation-order) when it updates an existing record.

```surql
-- Updates no records, and creates no `audit` records
UPDATE person SET last_audit = (CREATE ONLY audit SET at = time::now()).id WHERE false;

-- No record matches, so the query effectively becomes this:
UPDATE person /* SET last_audit = (CREATE ONLY audit SET at = time::now()).id */ WHERE false;
```

Validation of the data clause is deferred in the same way, so a data clause that would be rejected for a record no longer raises an error when the condition excludes that record.

> [!NOTE]
> Before SurrealDB 3.3.0, the data clause was evaluated for every scanned record on a full table scan, so its side effects fired for records the condition rejected. On an index-backed plan they did not, which meant that adding an index changed how many times the side effects ran.

### One image of the record

_(since v3.3.0)_

The `WHERE` condition and the data clause read the same image of the record, taken before the statement changes anything. Reads therefore never observe the statement's own writes, and both clauses agree on what the record contains.

This has always held for ordinary fields - `SET a = a + 1, b = a + 1` assigns `b` from the old `a`. [Computed fields](/docs/reference/query-language/statements/define/field.md#restrictions-on-computed-fields) are now part of that image too, so a data clause and a condition both see their pre-mutation values.

```surql
DEFINE FIELD can_drive ON person COMPUTED age >= 18;
CREATE person:1 SET age = 17;

-- `can_drive` is false in all three: it reflects age 17, not the new age of 18
UPDATE person:1 SET age = 18, my_field = can_drive;
UPDATE person:1 SET my_field = can_drive, age = 18;
UPDATE person:1 SET my_field = can_drive, age = 18 WHERE can_drive = false;
```

A statement that creates a record has no earlier image, so a computed field reads `NONE` there - whether the record ID was named, generated, or reached through [`UPSERT`](/docs/reference/query-language/statements/upsert.md).

> [!NOTE]
> Before 3.3.0, a computed field read `NONE` in the data clause but held its value in the `WHERE` condition, so whichever clause ran first decided what the other saw. The first two statements above returned `NONE` and the third returned `false`.

## CONTENT clause

Instead of specifying record data using the `SET` clause, it is also possible to use the `CONTENT` keyword to specify the record data using a SurrealQL object.

```surql
-- Update all records with the same content
UPDATE person CONTENT {
	name: 'John',
	company: 'SurrealDB',
	skills: ['Rust', 'Go', 'JavaScript'],
};

-- Oops, now they are both named John.
-- Update a specific record with some content
UPDATE person:tobie CONTENT {
	name: 'Tobie',
	company: 'SurrealDB',
	skills: ['Rust', 'Go', 'JavaScript'],
};
```

A statement with a `CONTENT` clause bypasses `READONLY` fields instead of generating an error.

```surql
DEFINE FIELD created
  ON person TYPE datetime DEFAULT d'2024-01-01T00:00:00Z' READONLY;
CREATE person:gladys SET age = 90;
-- Does not try to modify `created` field, no error
UPDATE person:gladys CONTENT { age: 70 };
```

**Output before 2.1.0**

```surql
-------- Query --------
[
	{
		age: 90,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]

-------- Query --------
'Found changed value for field `created`,
  with record `person:gladys`,
  but field is readonly'
```

**Output after 2.1.0**

```surql
-------- Query --------
[
	{
		age: 90,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]

-------- Query --------
[
	{
		age: 70,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]
```

## REPLACE clause

Originally an alias for `CONTENT`, the `REPLACE` clause maintains the previous behaviour regarding `READONLY` fields. If the content following `REPLACE` does not match a record's `READONLY` fields, an error will be generated.

```surql
DEFINE FIELD created
  ON person TYPE datetime DEFAULT d'2024-01-01T00:00:00Z' READONLY;
CREATE person:gladys SET age = 90;
-- Attempts to change `created` field, error
UPDATE person:gladys REPLACE { age: 70 };
-- `created` equals current value, query works
UPDATE person:gladys REPLACE { age: 70,
  created: d'2024-01-01T00:00:00Z' };
```

```surql title="Output"
-------- Query --------
[
	{
		age: 90,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]

-------- Query --------
'Found changed value for field `created`,
  with record `person:gladys`,
  but field is readonly'

-------- Query --------
[
	{
		age: 70,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]
```

## MERGE clause

Instead of specifying the full record data using `CONTENT` or one field at a time using `SET`, it is also possible to merge-update only specific fields by using the `MERGE` keyword followed by on object containing the fields which are to be upserted.

```surql
-- Update certain fields on all records
UPDATE person MERGE {
	settings: {
		marketing: true,
	},
};

-- Update certain fields on a specific record
UPDATE person:tobie MERGE {
	settings: {
		marketing: true,
	},
};
```

```surql title="Output"
[
	{
		company: 'SurrealDB',
		id: person:i5z3i64cpqpo8jtr6jww,
		name: 'John',
		settings: {
			marketing: true
		},
		skills: [
			'Rust',
			'Go',
			'JavaScript'
		]
	},
	{
		company: 'SurrealDB',
		id: person:tobie,
		name: 'Tobie',
		settings: {
			marketing: true
		},
		skills: [
			'Rust',
			'Go',
			'JavaScript'
		]
	}
]
```

## PATCH clause

You can also specify changes to be applied to your query response, using the PATCH command which works similar to the [JSON Patch specification](https://jsonpatch.com/)

```surql
-- Patch the JSON response
UPDATE person:tobie PATCH [
	{
		"op": "add",
		"path": "Engineering",
		"value": "true"
	}
]
```

```surql title="Output"
[
	{
		Engineering: 'true',
		company: 'SurrealDB',
		id: person:tobie,
		name: 'Tobie',
		settings: {
			marketing: true
		},
		skills: [
			'Rust',
			'Go',
			'JavaScript'
		]
	}
]
```

## Alter the `RETURN` value

By default, the update statement returns the record value once the changes have been made. To change the return value of each record, use the `RETURN` clause, specifying `NONE`, `BEFORE`, `AFTER`, `DIFF`, a comma-separated list of specific fields or ad-hoc fields, or `VALUE` for a single field without its key name.

```surql
-- Don't return any result
UPDATE person SET skills += 'reading' RETURN NONE;

-- Return the changeset diff
UPDATE person SET skills += 'reading' RETURN DIFF;

-- Return the record before changes were applied
UPDATE person SET skills += 'reading' RETURN BEFORE;

-- Return the record after changes were applied (the default)
UPDATE person SET skills += 'reading' RETURN AFTER;

-- Return the value of the 'skills' field without the field name
UPDATE person SET skills += 'reading' RETURN VALUE skills;

-- Return a specific field only from the updated records
UPDATE person:tobie SET skills = ['skiing',
  'music'] RETURN name,
  interests;
```

## Using a timeout

When processing a large result set with many interconnected records, it is possible to use the `TIMEOUT` keyword to specify a timeout duration for the statement. If the statement continues beyond this duration, then the transaction will fail, no records will be updated in the database, and the statement will return an error.

```surql
UPDATE person 
	SET important = true 
	WHERE ->knows->person->(knows WHERE influencer = true) 
	TIMEOUT 5s;
```

## UPDATE inside database exports

As `UPDATE` before version 2.0.0 used to create a specified record ID if it did not exist, it was used in the `.surql` files generated by the [`surreal export`](/docs/reference/cli/surrealdb-cli/commands/export.md) command to export existing records in a database. As of version 2.0.0, the [`INSERT`](/docs/reference/query-language/statements/insert.md) statement is used instead.

## The `EXPLAIN` clause

When `EXPLAIN` is used:

1. The `UPDATE` statement returns an explanation, essentially revealing the execution plan to provide transparency and understanding of the query performance.
2. The records are not updated.

`EXPLAIN` can be followed by `FULL` to see the number of executed rows.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/upsert

# UPSERT

The UPSERT statement can be used to insert records or modify records that already exist.

The `UPSERT` statement can be used to insert records into the database, or to update them if they exist.

> [!NOTE]
> In versions of SurrealDB between 2.0.0 and 2.0.4, an UPSERT statement was treated as an "UPDATE, otherwise INSERT" statement. It has since been changed to a statement which defaults to insertion, and updates otherwise. Please see the examples below for details.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
UPSERT [ ONLY ] @targets
    [ CONTENT @value
      | MERGE @value
      | PATCH @value
	  | REPLACE @value
      | [ SET @field = @value, ... | UNSET @field, ... ]
    ]
    [ WHERE @condition ]
    [ RETURN NONE | RETURN BEFORE | RETURN AFTER | RETURN DIFF | RETURN @statement_param, ... | RETURN VALUE @statement_param ]
    [ TIMEOUT @duration ]
	[ EXPLAIN [ FULL ]]
;
```

## Example usage

Conceptually, an `UPSERT` statement can be thought of as an "`INSERT`, otherwise `UPDATE`" statement.

### `UPSERT` without a `WHERE` clause

As an `UPSERT` statement is primarily an `INSERT` statement, one without a `WHERE` clause will not perform an update.

```surql
UPSERT person SET name = 'Billy';
UPSERT person SET name = 'Bobby';
SELECT * FROM person;
```

As the output shows, the second `UPSERT` simply inserted another `person` record with the name "Bobby", rather than updating the existing record.

```surql title="Output"
-------- Query --------
[
	{
		id: person:c2bl54ahi551fcx9dqri,
		name: 'Billy'
	}
]

-------- Query --------
[
	{
		id: person:886dcoe1ayul217nl2fu,
		name: 'Bobby'
	}
]

-------- Query --------
[
	{
		id: person:886dcoe1ayul217nl2fu,
		name: 'Bobby'
	},
	{
		id: person:c2bl54ahi551fcx9dqri,
		name: 'Billy'
	}
]
```

### Using the `WHERE` clause

#### Without a specified ID

When using the `WHERE` clause and no specified ID, SurrealDB will check to see if any records match the clause. If nothing matches, a new record will be created.

As such, the following `UPSERT` statement will return a new record:

```surql
UPSERT person SET name = 'Jaime' WHERE name = 'Jaime';
```

```surql title="Output"
[
	{
		id: person:7ilunylkcjgbg9gf0tqn,
		name: 'Jaime'
	}
]
```

Since a record with the name 'Jaime' exists, an `UPSERT` followed by `WHERE name = 'Jaime'` will update the existing record instead of creating a new one.

```surql
UPSERT person SET name = 'Tobie' WHERE name = 'Jaime';
```

```surql title="Output"
[
	{
		id: person:7ilunylkcjgbg9gf0tqn,
		name: 'Tobie'
	}
]
```

Now that no records have the name `'Jaime'`, the same query as above will now create a new record because no records match the `WHERE` clause. The database will now have two `person` records.

```surql
UPSERT person SET name = 'Tobie' WHERE name = 'Jaime';
SELECT * FROM person;
```

```surql title="Output"
-- Query
[
	{
		id: person:0n0ddlkmhe6mdb6ikkui,
		name: 'Tobie'
	}
]

-- Query
[
	{
		id: person:0n0ddlkmhe6mdb6ikkui,
		name: 'Tobie'
	},
	{
		id: person:7ilunylkcjgbg9gf0tqn,
		name: 'Tobie'
	}
]
```

#### With a specified ID

`UPSERT` behaviour with a specific ID and a `WHERE` clause differs slightly from the examples above. In this case, there is the possibility that a record ID already exists but the `WHERE` clause does not match. As such, there is no way to create a new record as the statement only pertains to an ID for an already existing record.

The following query will return a record, because the `person:test` record does not yet exist. The `WHERE` clause makes no difference as there is no record to apply it to.

```surql
UPSERT person:test SET name = 'Jaime' WHERE name = 'Jaime';
```

```surql title="Output"
[
	{
		id: person:test,
		name: 'Jaime'
	}
]
```

The following query will update the `person:test` record, because the record exists and the `WHERE` clause matches. The `person:test` record will now have the name `'Tobie'`.

```surql
UPSERT person:test SET name = 'Tobie' WHERE name = 'Jaime';
```

```surql title="Output"
[
	{
		id: person:test,
		name: 'Tobie'
	}
]
```

However, this third query will return nothing. The `WHERE` clause does not match and thus `person:test` cannot be updated, and the statement itself only pertains to the `person:test` record, so a new record using a random ID will not be returned.

```surql
UPSERT person:test SET name = 'Billy' WHERE name = 'Jaime';
```

```surql title="Output"
[]
```

### Evaluation order

_(since v3.3.0)_

When an `UPSERT` updates an existing record, the `WHERE` condition is checked before the data clause is evaluated. A data clause with side effects therefore only runs for the records the condition matches, matching [`UPDATE`](/docs/reference/query-language/statements/update.md#evaluation-order).

```surql
CREATE person:jaime SET name = 'Jaime';

-- Updates nothing, and creates no `audit` record
UPSERT person:jaime SET last_audit = (CREATE ONLY audit SET at = time::now()).id WHERE false;

-- `person:jaime` does not match, so the query effectively becomes this:
UPSERT person:jaime /* SET last_audit = (CREATE ONLY audit SET at = time::now()).id */ WHERE false;
```

Validation of the data clause is deferred in the same way, so a data clause that would be rejected for a record no longer raises an error when the condition excludes that record.

The condition only gates the update path. When an `UPSERT` creates a record instead, the `WHERE` clause is not evaluated at all - as described in [With a specified ID](#with-a-specified-id) above - and the data clause runs.

```surql
-- `person:billy` does not exist, so it is created and an `audit` record is written
UPSERT person:billy SET last_audit = (CREATE ONLY audit SET at = time::now()).id WHERE false;
```

> [!NOTE]
> Before SurrealDB 3.3.0, the data clause was evaluated for every scanned record on a full table scan, so its side effects fired for records the condition rejected. On an index-backed plan they did not, which meant that adding an index changed how many times the side effects ran.

### Improved performance via UPSERT and a unique index

[Unique indexes](/docs/reference/query-language/statements/define/indexes.md#unique-index) can be used to ensure that no field or combination of fields is ever present more than once. For example, a game might have a rule that duplicate names can exist, but not within the same class.

```surql
DEFINE INDEX unique_key ON TABLE user FIELDS name, class UNIQUE;
DEFINE FIELD official_name ON TABLE user VALUE name + " the " + class;
CREATE user:billy SET name = "Billy",
  class = "wizard",
  metadata = { likes: ["strawberries"] };
CREATE user:billy_der_zweite SET name = "Billy",
  class = "wizard",
  metadata = { likes: ["strawberries",
  "fields"] };
```

As the output shows, the unique index prevents the creation of a second user with the name and class as the first.

```surql title="Output"
-------- Query --------
[
	{
		id: user:billy,
		metadata: {
			likes: [
				'strawberries'
			]
		},
		name: 'Billy',
		official_name: 'Billy the wizard',
		class: 'wizard'
	}
]

-------- Query --------
"Database index `unique_key` already contains ['Billy',
  'wizard'],
  with record `user:billy`"
```

To change an existing record's `metadata` field to the value `{ likes: ["strawberries", "fields"] }`, an `UPDATE` with a `WHERE` can be used. This performs a scan on the `user` table to check for all records that match the `WHERE` clause.

```surql
UPDATE user SET
	metadata = { likes: ["strawberries", "fields"] }
WHERE
	name = "Billy" AND
	class = "wizard";
```

However, a much more efficient method is available if you only need to update one record and have a unique index that can be used instead. This optimisation is available when using `UPSERT` and a unique index, because the statement will always access the index in any case to first see if the record is a duplicate.

```surql
-- Checks the index for ["Mandy", "wizard"], no existing
-- record found so no problem
UPSERT user SET name = "Mandy", class = "wizard";

-- Fails because statement tries to upsert a new user:mandy
-- on top of the previous randomly generated one
UPSERT user:mandy SET 
	name = "Mandy",
	class = "wizard",
	metadata = { likes: ["strawberries" ]};
```

```surql title="Output"
-------- Query --------

[
	{
		class: 'wizard',
		id: user:kdvh401gofckvvy6nbiw,
		name: 'Mandy',
		official_name: 'Mandy the wizard'
	}
]

-------- Query --------

"Database index `unique_key` already contains ['Mandy',
  'wizard'],
  with record `user:kdvh401gofckvvy6nbiw`"
```

Since an `UPSERT` statement already checks unique indexes by default, it uses this to recognize that the user with `name = "Billy"` and `the = "Wizard"` corresponds to the record `user:j2ecdb2tf4ou29mr0yp5` and update it without needing to scan the entire `user` table.

```surql
UPSERT user SET
	name = "Billy",
	class = "wizard",
	metadata = { likes: ["strawberries", "fields"] };
```

```surql title="Output"
-------- Query --------
[
	{
		id: user:j2ecdb2tf4ou29mr0yp5,
		metadata: {
			likes: [
				'strawberries'
			]
		},
		name: 'Billy',
		official_name: 'Billy the wizard',
		class: 'wizard'
	}
]
```

To compare the performance difference between using a `WHERE` clause and a unique index yourself, here is an example that creates a crowded field of 50000 `user` records, followed by one more `user` named "Billy". An `UPDATE` using `WHERE name = "Billy" AND class = "wizard"` requires a full table scan, while an `UPSERT` using the two fields used to build the index is much faster.

```surql
DEFINE INDEX unique_key ON TABLE user FIELDS name, class UNIQUE;
DEFINE FIELD official_name ON TABLE user VALUE name + " the " + class;

-- Add 50000 users to fill up the database
FOR $i IN <array>0..50000 { CREATE user SET name = <string>$i,
  class = <string>$i;
}

-- Create Billy, one of 50,001 records
CREATE user SET name = "Billy", class = "wizard";

-- Updating Billy requires a table scan
UPDATE user SET
	interests += "music"
WHERE
	name = "Billy" AND
	class = "wizard";

-- But UPSERT uses 'name' and 'class' to check the index anyway,
-- and thus can use it to access the record without a scan
UPSERT user SET
	name = "Billy",
	class = "wizard",
	interests += "travel";
```

## Using the ONLY clause

The `ONLY` clause can be used to return a single record instead of an array of records.

```surql
-- UPSERT just a single record
-- Using the ONLY keyword, just an object for the record in question will be returned.
-- This, instead of an array with a single object.
UPSERT ONLY person:tobie SET 
	name = 'Tobie', 
	company = 'SurrealDB', 
	skills = ['Rust', 'Go', 'JavaScript'];
```

## Type inference when using UPSERT

The `+=` operator in the following query is enough for SurrealDB to infer that the `interests` field must be an `array<string>`.

```surql
-- UPSERT a document and remove a tag from an array
UPSERT person:tobie SET interests += 'Java';
```

Type inference will also work with a numeric value such as the `click_count` field below, in which case it will infer the field to be of type `int` with a default value of 0.

```surql
-- UPSERT a document and increment a numeric value
UPSERT webpage:home SET click_count += 1;
```

Creating a record by default makes the `UPSERT` statement an ideal way to manage an incrementing field.

```surql
UPSERT event_for:[time::now().format("%Y-%m-%d")] SET
    number += 1;
```

```surql title="Possible output"
[
	{
		id: event_for:[
			'2024-09-18'
		],
		number: 1
	}
]
```

Doing the same with an `UPDATE` statement would require much more manual work.

```surql
IF (SELECT *
  FROM event_for:[time::now().format("%Y-%m-%d")]).is_empty() {
    CREATE event_for:[time::now().format("%Y-%m-%d")] SET number = 1;
} ELSE {
    UPDATE event_for:[time::now().format("%Y-%m-%d")] SET number += 1;
};
```

## CONTENT clause

Instead of specifying record data using the `SET` clause, it is also possible to use the `CONTENT` keyword to specify the record data using a SurrealQL object.

```surql
-- UPSERT all records with the same content
UPSERT person CONTENT {
	name: 'Tobie',
	company: 'SurrealDB',
	skills: ['Rust', 'Go', 'JavaScript'],
};

-- UPSERT a specific record with some content
UPSERT person:tobie CONTENT {
	name: 'Tobie',
	company: 'SurrealDB',
	skills: ['Rust', 'Go', 'JavaScript'],
};
```

A statement with a `CONTENT` clause bypasses `READONLY` fields instead of generating an error.

```surql
DEFINE FIELD created
  ON person TYPE datetime DEFAULT d'2024-01-01T00:00:00Z' READONLY;
UPSERT person:gladys SET age = 90;
-- Does not try to modify `created` field, no error
UPSERT person:gladys CONTENT { age: 70 };
```

**Output before 2.1.0**

```surql
-------- Query --------
[
	{
		age: 90,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]

-------- Query --------
'Found changed value for field `created`,
  with record `person:gladys`,
  but field is readonly'
```

**Output after 2.1.0**

```surql
-------- Query --------
[
	{
		age: 90,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]

-------- Query --------
[
	{
		age: 70,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]
```

## REPLACE clause

Originally an alias for `CONTENT`, the `REPLACE` clause maintains the previous behaviour regarding `READONLY` fields. If the content following `REPLACE` does not match a record's `READONLY` fields, an error will be generated.

```surql
DEFINE FIELD created
  ON person TYPE datetime DEFAULT d'2024-01-01T00:00:00Z' READONLY;
UPSERT person:gladys SET age = 90;
-- Attempts to change `created` field, error
UPSERT person:gladys REPLACE { age: 70 };
-- `created` equals current value, query works
UPSERT person:gladys REPLACE { age: 70,
  created: d'2024-01-01T00:00:00Z' };
```

```surql title="Output"
-------- Query --------
[
	{
		age: 90,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]

-------- Query --------
'Found changed value for field `created`,
  with record `person:gladys`,
  but field is readonly'

-------- Query --------
[
	{
		age: 70,
		created: d'2024-01-01T00:00:00Z',
		id: person:gladys
	}
]
```

## MERGE clause

Instead of specifying the full record data using the `SET` clause or the `CONTENT` keyword, it is also possible to merge-UPSERT only specific fields by using the `MERGE` keyword and specifying only the fields which are to be upserted.

```surql
-- Inserts a new record with a single field and random ID
UPSERT person MERGE {
	settings: {
		marketing: true,
	},
};

-- Updates certain fields on a specific record
UPSERT person:tobie MERGE {
	settings: {
		marketing: true,
	},
};
```

## PATCH clause

You can also specify changes to be applied to your query response, using the `PATCH` clause which works similar to the [JSON Patch specification](https://jsonpatch.com/)

```surql
-- Patch the JSON response
UPSERT person:tobie PATCH [
	{
		"op": "add",
		"path": "Engineering",
		"value": "true"
	}
];
```

## Alter the `RETURN` value

By default, the UPSERT statement returns the record value once the changes have been made. To change the return value of each record, specify a `RETURN` clause, specifying either `NONE`, `BEFORE`, `AFTER`, `DIFF`, or a comma-separated list of specific fields to return.

```surql
-- Don't return any result
UPSERT person:tobie SET interests += 'reading' RETURN NONE;

-- Return the changeset diff
UPSERT person:tobie SET interests += 'reading' RETURN DIFF;

-- Return the record before changes were applied
UPSERT person:tobie SET interests += 'reading' RETURN BEFORE;

-- Return the record after changes were applied (the default)
UPSERT person:tobie SET interests += 'reading' RETURN AFTER;

-- Return a specific field only from the upserted records
UPSERT person:tobie SET interests = ['skiing',
  'music'] RETURN name,
  interests;
```

When processing a large result set with many interconnected records, it is possible to use the `TIMEOUT` keywords to specify a timeout duration for the statement. If the statement continues beyond this duration, then the transaction will fail, no records will be upserted in the database, and the statement will return an error.

```surql
UPSERT person:3 SET important = true
  WHERE ->knows->person->(knows
  WHERE influencer = true) TIMEOUT 5s;
```

## The `EXPLAIN` clause

When `EXPLAIN` is used:

1. The `UPSERT` statement returns an explanation, essentially revealing the execution plan to provide transparency and understanding of the query performance.
2. The records are not updated.

`EXPLAIN` can be followed by `FULL` to see the number of executed rows.

---

Source: https://surrealdb.com/docs/reference/query-language/statements/use

# USE

The USE statement specifies a namespace and / or a database to use for the subsequent SurrealQL statements when switching between namespaces and databases.

The `USE` statement specifies a namespace and / or a database to use for the subsequent SurrealQL statements when switching between namespaces and databases. If you have a single namespace and database, you can define them in the [sql command](/docs/reference/cli/surrealdb-cli/commands/sql.md#example-usage).

Ensure that your database and namespace exist and you have [started your database](/docs/reference/cli/surrealdb-cli/commands/start.md) before using the Sql command option.

## Statement syntax

**SurrealQL Syntax**

```syntax title="SurrealQL Syntax"
USE [ NS @ns ] [ DB @db ];
```

## Example usage

The following query shows example usage of this statement if you have multiple namespaces and databases.

```surql
USE NS test; -- Switch to the 'main' Namespace
```

```surql
USE DB test; -- Switch to the 'main' Database
```

```surql
USE NS test DB test; -- Switch to the 'main' Namespace and 'main' Database
```

You can also use the [INFO Statement](/docs/reference/query-language/statements/info.md) to check the current namespace and database.

```surql
INFO FOR NS; -- Check the current Namespace
```

```surql
INFO FOR DB; -- Check the current Database
```

## `USE` statement behaviour when resource does not exist

_(since v3.0.0)_

The behaviour of the `USE` statement differs depending on which mode the database server is run in.

When run in regular mode, a `USE` statement will create the namespace or database indicated if it does not already exist.

```surql
USE NS ns; -- Output: NONE (success)
(INFO FOR ROOT).namespaces; -- Output: { ns: 'DEFINE NAMESPACE ns' }
```

In [strict mode](/docs/reference/cli/surrealdb-cli/commands/start.md#strict-mode), a resource will not be created unless it is already defined. In this case, the `USE` statement will return an error.

```surql
USE NS ns; -- Output: "The namespace 'ns' does not exist"
DEFINE NS ns;
USE NS ns; -- Now defined, no error
```

## Value returned by `USE` statement

_(since v3.0.0)_

Before SurrealDB 3.0.0, the output of a `USE` statement was `NONE`. Since then, each `USE` statement returns an object containing the current namespace and database.

```surql
USE NS main;
```

```surql title="Output"
{ database: 'main', namespace: 'main' }
```

---

Source: https://surrealdb.com/docs/reference/rest-api

# REST API

The SurrealDB REST API: executing queries over HTTP. Manage authentication and perform CRUD operations.

Any language or tool capable of making HTTP requests can interact with SurrealDB through this API. This is useful when an official SDK is not available for your stack, or when you need to integrate SurrealDB with infrastructure tooling, scripts, or third-party platforms.

## What the API provides

- **Query execution** - send SurrealQL statements over HTTP and receive results as JSON.
- **Authentication** - sign in, sign up, and manage tokens via dedicated endpoints.
- **CRUD operations** - create, read, update, and delete records using RESTful conventions.
- **Health and status** - check whether the server is running and accepting connections.

## Protocol details

The full HTTP protocol reference, including request and response formats, authentication headers, and endpoint specifications, is available in the [HTTP protocol](/docs/reference/rest-api/http-protocol.md) page.

## Alternative access methods

If you prefer a richer client experience, SurrealDB also supports:

- **WebSocket protocol** - persistent connections with real-time capabilities.
- **[Postgres wire protocol](/docs/reference/rest-api/postgres-protocol.md)** - connect with `psql`, JDBC, and other Postgres clients, then run SurrealQL or ISO GQL with tabular typed results.
- **Official SDKs** - language-specific clients for [JavaScript](/docs/languages/javascript.md), [Python](/docs/languages/python.md), [Rust](/docs/languages/rust.md), [Go](/docs/languages/golang.md), [Java](/docs/languages/java.md), [.NET](/docs/languages/dotnet.md), and [PHP](/docs/languages/php.md).

---

Source: https://surrealdb.com/docs/reference/rest-api/cbor-protocol

# CBOR protocol

SurrealDB supports a number of methods for connecting to the database and performing data queries.

SurrealDB extends the [CBOR](https://www.rfc-editor.org/rfc/rfc8949.html) protocol with a number of custom tags to support the full range of data types available in SurrealDB. This document provides an overview of the custom tags and their respective values.

## References:
- CBOR Protocol - [RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html)
- CBOR Official Tags - [Iana](https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml)

## Custom tags

<table>
    <thead>
        <tr>
            <th scope="col">Tag</th>
            <th scope="col">Value</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 0](#tag-0)
            </td>
            <td scope="row" data-label="Value">
                [Datetime](/docs/reference/query-language/language-primitives/data-types/datetimes.md) ([RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) string)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 6](#tag-6)
            </td>
            <td scope="row" data-label="Value">
                [`NONE`](/docs/reference/query-language/language-primitives/data-types/none-and-null.md#none-values)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 7](#tag-7)
            </td>
            <td scope="row" data-label="Value">
                Table name
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 8](#tag-8)
            </td>
            <td scope="row" data-label="Value">
                [Record ID](/docs/reference/query-language/language-primitives/data-types/record-ids.md)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 9](#tag-9)
            </td>
            <td scope="row" data-label="Value">
                [UUID](/docs/reference/query-language/language-primitives/data-types/uuids.md) (string)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 10](#tag-10)
            </td>
            <td scope="row" data-label="Value">
                [Decimal](/docs/reference/query-language/language-primitives/data-types/numbers.md#decimal-numbers) (string)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 12](#tag-12)
            </td>
            <td scope="row" data-label="Value">
                [Datetime](/docs/reference/query-language/language-primitives/data-types/datetimes.md) (compact)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 13](#tag-13)
            </td>
            <td scope="row" data-label="Value">
                [Duration](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) (string)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 14](#tag-14)
            </td>
            <td scope="row" data-label="Value">
                [Duration](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) (compact)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 15](#tag-15)
            </td>
            <td scope="row" data-label="Value">
                [Future](/docs/reference/query-language/language-primitives/data-types/futures.md) (compact)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 37](#tag-37)
            </td>
            <td scope="row" data-label="Value">
                [UUID](/docs/reference/query-language/language-primitives/data-types/uuids.md) (binary)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 49](#tag-49)
            </td>
            <td scope="row" data-label="Value">
                [Range](/docs/reference/query-language/language-primitives/data-types/ranges.md)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 50](#tag-50)
            </td>
            <td scope="row" data-label="Value">
                [Included Bound](/docs/reference/query-language/language-primitives/data-types/ranges.md)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 51](#tag-51)
            </td>
            <td scope="row" data-label="Value">
                [Excluded Bound](/docs/reference/query-language/language-primitives/data-types/ranges.md)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 88](#tag-88)
            </td>
            <td scope="row" data-label="Value">
                [Geometry Point](/docs/reference/query-language/language-primitives/data-types/geometries.md#point)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 89](#tag-89)
            </td>
            <td scope="row" data-label="Value">
                [Geometry Line](/docs/reference/query-language/language-primitives/data-types/geometries.md#linestring)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 90](#tag-90)
            </td>
            <td scope="row" data-label="Value">
                [Geometry Polygon](/docs/reference/query-language/language-primitives/data-types/geometries.md#polygon)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 91](#tag-91)
            </td>
            <td scope="row" data-label="Value">
                [Geometry MultiPoint](/docs/reference/query-language/language-primitives/data-types/geometries.md#multipoint)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 92](#tag-92)
            </td>
            <td scope="row" data-label="Value">
                [Geometry MultiLine](/docs/reference/query-language/language-primitives/data-types/geometries.md#multilinestring)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 93](#tag-93)
            </td>
            <td scope="row" data-label="Value">
                [Geometry MultiPolygon](/docs/reference/query-language/language-primitives/data-types/geometries.md#multipolygon)
            </td>
        </tr>
        <tr>
            <td scope="row" data-label="Tag">
                [Tag 94](#tag-94)
            </td>
            <td scope="row" data-label="Value">
                [Geometry Collection](/docs/reference/query-language/language-primitives/data-types/geometries.md)
            </td>
        </tr>
    </tbody>
</table>

### Tag 0

A [datetime](/docs/reference/query-language/language-primitives/data-types/datetimes.md) represented in an [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) string.

Adopted from the [Iana Specification](https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml).

**Note:** [Tag 12](#tag-12) is preferred and always sent back by SurrealDB.

### Tag 6

Represents a [`NONE`](/docs/reference/query-language/language-primitives/data-types/none-and-null.md#none-values) value. The value passed to the tagged value is `null`, as it cannot be empty.

### Tag 7

A table name, represented as a string.

### Tag 8

A [Record ID](/docs/reference/query-language/language-primitives/data-types/record-ids.md), represented as an two-value array, containing a table part (string) and an id part (string, number, object or array).

Instead of an two-value array, SurrealDB also accepts a string with a string-formatted Record ID. A string Record ID will never be sent back from SurrealDB, however.

### Tag 9

A [UUID](/docs/reference/query-language/language-primitives/data-types/uuids.md) represented in a string format.

**Note:** [Tag 37](#tag-37) is preferred and always sent back by SurrealDB.

### Tag 10

A [Decimal](/docs/reference/query-language/language-primitives/data-types/numbers.md#decimal-numbers) represented in a string format.

### Tag 12

A [Datetime](/docs/reference/query-language/language-primitives/data-types/datetimes.md) represented in a two-value array, containing seconds (number) and optionally nanoseconds (number).

### Tag 13

A [Duration](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) represented in a string format.

**Note:** [Tag 14](#tag-14) is preferred and always sent back by SurrealDB.

### Tag 14

A [Duration](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) represented in a two-value array, containing optionally seconds (number) and optionally nanoseconds (number). An empty array will be considered a duration of 0.

### Tag 15

A [Future](/docs/reference/query-language/language-primitives/data-types/futures.md) represented as a string containing the uncomputed SurrealQL query or expression. The value transported needs to be returned in an `Object` in a `{}`,  this will also be the format you receive it in from SurrealDB this will allow for it to be computed when accessed or used within a query.

### Tag 37

A [UUID](/docs/reference/query-language/language-primitives/data-types/uuids.md) represented in a binary format. Please reference (https://docs.rs/uuid/latest/uuid/struct.Uuid.html#method.as_bytes).

Adopted from the [Iana Specification](https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml).

### Tag 49

A [Range](/docs/reference/query-language/language-primitives/data-types/ranges.md) represented as a two-value array containing optional bounds. Each bound can be either null (for unbounded), or a tagged value using either Tag 50 (included bound) or Tag 51 (excluded bound).

The bounds follow SurrealQL's range syntax where `..` represents a range, `>..` represents an excluded lower bound, and `..=` represents an included upper bound.

### Tag 50

An included bound value used within [Range](/docs/reference/query-language/language-primitives/data-types/ranges.md) bounds. The tagged value represents an inclusive boundary (equivalent to `..=` for upper bounds in SurrealQL range syntax).

### Tag 51

An excluded bound value used within [Range](/docs/reference/query-language/language-primitives/data-types/ranges.md) bounds. The tagged value represents an exclusive boundary (equivalent to `>..` for lower bounds in SurrealQL range syntax).

### Tag 88

A [Geometry Point](/docs/reference/query-language/language-primitives/data-types/geometries.md#point) represented by a two-value array containing a longitude (float) and latitude (float).

### Tag 89

A [Geometry Line](/docs/reference/query-language/language-primitives/data-types/geometries.md#linestring) represented by an array with two or more points ([Tag 88](#tag-88)).

### Tag 90

A [Geometry Polygon](/docs/reference/query-language/language-primitives/data-types/geometries.md#polygon) represented by an array with one or more closed lines ([Tag 89](#tag-89)).

If the lines are not closed, meaning that the first and last point are equal, then SurrealDB will automatically suffix the line with it's first point.

### Tag 91

A [Geometry MultiPoint](/docs/reference/query-language/language-primitives/data-types/geometries.md#multipoint) represented by an array with one or more points ([Tag 88](#tag-88)).

### Tag 92

A [Geometry MultiLine](/docs/reference/query-language/language-primitives/data-types/geometries.md#multilinestring) represented by an array with one or more lines ([Tag 89](#tag-89)).

### Tag 93

A [Geometry MultiPolygon](/docs/reference/query-language/language-primitives/data-types/geometries.md#multipolygon) represented by an array with one or more polygons ([Tag 90](#tag-90)).

### Tag 94

A [Geometry Collection](/docs/reference/query-language/language-primitives/data-types/geometries.md) represented by an array with one or more geometry values ([Tag 88](#tag-88), [Tag 89](#tag-89), [Tag 90](#tag-90), [Tag 91](#tag-91), [Tag 92](#tag-92), [Tag 93](#tag-93) or [Tag 94](#tag-94)).

---

Source: https://surrealdb.com/docs/reference/rest-api/errors

# Errors

Every error SurrealDB returns carries a kind, a wire code, optional structured details and an optional cause, in the same shape across every protocol and SDK.

Every error SurrealDB returns has the same shape, whichever protocol carries it. The SDKs map that shape onto their own idioms, so the vocabulary on this page is the one behind every SDK's error type.

The preferred way to handle an error is to branch on `kind` first and `code` second, both of which are a stable contract. The `message` is written for a person reading it and is free to change between releases, so treat it as text to display rather than something to match on.

## Where an error appears

A request has two layers that can fail independently, and they report failure differently.

The **call** can fail as a whole: incorrect syntax, an unknown method, a rejected sign-in. In this case the response will carry an `error` object in place of a result.

```json title="Call-level error"
{
    "error": {
        "cause": null,
        "code": -32603,
        "details": { "kind": "InvalidParams" },
        "kind": "Validation",
        "message": "Expected (what, data)"
    }
}
```

Once the call succeeds, individual statements inside will be either successes or failures. Each statement reports its own `status`, and a failing one carries its `kind` with the message in `result`. Statements that ran before it keep their results.

Take these three statements for example which can be sent as one call. The field definition and the first `CREATE` statement are accepted, and only the third breaks the assertion:

```surql
DEFINE FIELD name ON user TYPE string ASSERT $value.len() <= 20;
CREATE user:short SET name = "Billy";
CREATE user:long SET name = "Mr. Muchtoolongname the Fourth";
```

The call itself succeeded, so there is no `error` object. The failure is reported against the one statement that caused it, and `user:short` is still created:

```json title="Statement-level error"
{
    "result": [
        { "result": null, "status": "OK", "time": "7.899708ms", "type": null },
        {
            "result": [{ "id": "user:short", "name": "Billy" }],
            "status": "OK",
            "time": "10.369375ms",
            "type": null
        },
        {
            "kind": "Internal",
            "result": "Found 'Mr. Muchtoolongname the Fourth' for field `name`, with record `user:long`, but field must conform to: $value.len() <= 20",
            "status": "ERR",
            "time": "1.351458ms",
            "type": null
        }
    ]
}
```

A failed statement does not stop the ones after it, and does not roll back the ones before it. Statements are independent unless a [transaction](/docs/reference/query-language/statements/begin.md) makes them otherwise.

### Inside a transaction

Wrapping the same two `CREATE` statements in `BEGIN` and `COMMIT` ties their fates together:

```surql
DEFINE FIELD name ON user TYPE string ASSERT $value.len() <= 20;

BEGIN;
CREATE user:short SET name = "Billy";
CREATE user:long SET name = "Mr. Muchtoolongname the Fourth";
COMMIT;
```

The call still succeeds, so there is still no `error` object. What changes is that the statement which would have worked on its own now reports `NotExecuted`, and the `COMMIT` refuses:

```json title="Statement errors inside a transaction"
{
    "result": [
        { "result": null, "status": "OK", "time": "8.304667ms", "type": null },
        { "result": null, "status": "OK", "time": "0ns", "type": null },
        {
            "details": { "kind": "NotExecuted" },
            "kind": "Query",
            "result": "The query was not executed due to a failed transaction",
            "status": "ERR",
            "time": "10.880333ms",
            "type": null
        },
        {
            "kind": "Internal",
            "result": "Found 'Mr. Muchtoolongname the Fourth' for field `name`, with record `user:long`, but field must conform to: $value.len() <= 20",
            "status": "ERR",
            "time": "923.041µs",
            "type": null
        },
        {
            "details": { "kind": "NotExecuted" },
            "kind": "Query",
            "result": "Cannot COMMIT: the transaction was aborted due to a prior error",
            "status": "ERR",
            "time": "0ns",
            "type": null
        }
    ]
}
```

Neither record exists afterwards, `user:short` included. The `DEFINE FIELD` is untouched, because it ran before `BEGIN`: only the statements inside the transaction are rolled back.

This split is why each SDK offers two ways to read a query result: one that surfaces the first failure, and one that reports every statement. The names differ per language, and each SDK's error page covers its own.

## The error object

| Field | Description |
| --- | --- |
| `kind` | The error category. The primary thing to branch on. |
| `code` | Numeric wire code, kept for backwards compatibility. |
| `message` | A human-readable description liable to change. Be sure not to match on it unless you are able to update the match when upgrading versions. |
| `details` | Structured detail for kinds that carry one, itself carrying a nested `kind`. |
| `cause` | The underlying error, where one was attached. Nested errors use this same shape. |

## Error kinds

| Kind | Meaning |
| --- | --- |
| `Validation` | Parse error, invalid request, or invalid parameters |
| `Configuration` | A feature or configuration is not supported |
| `Query` | A query timed out, was cancelled, or was not executed |
| `Serialization` | A value could not be serialised or deserialised |
| `NotAllowed` | A permission or authorisation check failed |
| `NotFound` | A resource does not exist |
| `AlreadyExists` | A resource already exists |
| `Connection` | A client-side connection failure |
| `Thrown` | A `THROW` statement ran in SurrealQL |
| `Internal` | An internal or unexpected failure |
| `Context` | A wrapper carrying context around another error |

`Internal` doubles as the catch-all for a kind the reader does not recognise, so code that handles the kinds it cares about and treats the rest as internal keeps working against a newer server.

## Wire codes

| Code | Name |
| --- | --- |
| `-32700` | Parse error |
| `-32600` | Invalid request |
| `-32601` | Method not found |
| `-32602` | Method not allowed |
| `-32603` | Invalid parameters |
| `-32604` | Live query not supported |
| `-32605` | Bad live query configuration |
| `-32606` | Bad GraphQL configuration |
| `-32000` | Internal error |
| `-32001` | Client-side error |
| `-32002` | Invalid authentication |
| `-32003` | Query not executed |
| `-32004` | Query timed out |
| `-32005` | Query cancelled |
| `-32006` | Thrown |
| `-32007` | Serialization error |
| `-32008` | Deserialization error |
| `-32009` | Query transaction conflict |

Two of these differ from the JSON-RPC conventions they resemble: `-32602` is method-not-allowed rather than invalid parameters, and invalid parameters is `-32603`. A code also does not always follow from the kind, since an error can be a `Validation` while carrying the generic `-32000`. Reading `kind` avoids both surprises.

## In the SDKs

Each SDK exposes these kinds through its own error type, and documents the two-layer behaviour in its own idiom.

- [Rust](/docs/reference/rust/concepts/error-handling.md)
- [JavaScript](/docs/reference/javascript/concepts/error-handling.md)
- [Python](/docs/reference/python/concepts/error-handling.md)
- [Go](/docs/reference/golang/concepts/error-handling.md)
- [Java](/docs/reference/java/concepts/error-handling.md)
- [Kotlin](/docs/reference/kotlin/concepts/error-handling.md)
- [PHP](/docs/reference/php/v2/concepts/error-handling.md)
- [Mojo](/docs/reference/mojo/concepts/error-handling.md)

---

Source: https://surrealdb.com/docs/reference/rest-api/http-protocol

# HTTP protocol

The HTTP endpoints enable selection and modification of data, along with custom SurrealQL queries, using traditional RESTful HTTP endpoints.

The HTTP endpoints exposed by SurrealDB instances provide a simple way to interact with the database over a traditional RESTful interface. This includes selecting and modifying one or more records, executing custom SurrealQL queries, and importing and exporting data.

The endpoints are designed to be simple and easy to use in stateless environments, making them ideal for lightweight applications where a persistent database connection is not required.

## Setup

The [`surreal start`](/docs/reference/cli/surrealdb-cli/commands/start.md) command without any arguments is all that is needed to start a server at the default `http://localhost:8000` address. Many examples below assume the flags `--user root` and `--pass secret` to create a root user with the name `root` and password `secret`. The `--unauthenticated` flag can be used when experimenting to turn off authentication, effectively allowing root access by any and all connections.

The [local database serving](/docs/explore/studio.md) functionality on the SurrealDB Studio can also be used to start a server.

## Querying via Postman

One convenient way to access these endpoints is via SurrealDB's Postman Collection. To do so, follow these steps:

1. Open Postman
2. Clone the [SurrealDB Postman Collection](https://postman.com/surrealdb/workspace/surrealdb/collection/19100500-3da237f3-588b-4252-8882-6d487c11116a)
3. Select the appropriate HTTP method (`GET /health`, `DEL /key/:table`, etc.).
4. Enter the endpoint URL.
5. If the endpoint requires any parameters or a body, make sure to include those in your request.

## Supported methods

You can use the HTTP endpoints to perform the following actions:

<br />

<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="#status"><code>GET /status</code></a></td>
            <td scope="row" data-label="Description">Checks whether the database web server is running</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#health"><code>GET /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="Function"><a href="#ready"><code>GET /ready</code></a></td>
            <td scope="row" data-label="Description">Checks whether the instance has finished startup and is ready to serve traffic</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#version"><code>GET /version</code></a></td>
            <td scope="row" data-label="Description">Returns the version of the SurrealDB database server</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#import"><code>POST /import</code></a></td>
            <td scope="row" data-label="Description">Imports data into a specific Namespace and Database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#export"><code>POST /export</code></a></td>
            <td scope="row" data-label="Description">Exports all data for a specific Namespace and Database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#signup"><code>POST /signup</code></a></td>
            <td scope="row" data-label="Description">Signs-up as a record user using a specific record access method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#signin"><code>POST /signin</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="Function"><a href="#get-table"><code>GET /key/:table</code></a></td>
            <td scope="row" data-label="Description">Selects all records in a table from the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#post-table"><code>POST /key/:table</code></a></td>
            <td scope="row" data-label="Description">Creates a record in a table in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#put-table"><code>PUT /key/:table</code></a></td>
            <td scope="row" data-label="Description">Updates all records in a table in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#patch-table"><code>PATCH /key/:table</code></a></td>
            <td scope="row" data-label="Description">Modifies all records in a table in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#delete-table"><code>DELETE /key/:table</code></a></td>
            <td scope="row" data-label="Description">Deletes all records in a table from the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#get-record"><code>GET /key/:table/:id</code></a></td>
            <td scope="row" data-label="Description">Selects the specific record from the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#post-record"><code>POST /key/:table/:id</code></a></td>
            <td scope="row" data-label="Description">Creates the specific record in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#put-record"><code>PUT /key/:table/:id</code></a></td>
            <td scope="row" data-label="Description">Updates the specified record in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#patch-record"><code>PATCH /key/:table/:id</code></a></td>
            <td scope="row" data-label="Description">Modifies the specified record in the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#delete-record"><code>DELETE /key/:table/:id</code></a></td>
            <td scope="row" data-label="Description">Deletes the specified record from the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#sql"><code>POST /sql</code></a></td>
            <td scope="row" data-label="Description">Allows custom SurrealQL queries</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#gql"><code>POST /gql</code></a></td>
            <td scope="row" data-label="Description">Runs ISO GQL graph pattern queries (`MATCH … RETURN …`, plus `INSERT` / `SET` / `REMOVE` / `DELETE`)</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#graphql"><code>POST /graphql</code></a></td>
            <td scope="row" data-label="Description">Allows custom GraphQL queries</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#ml-import"><code>POST /ml/import</code></a></td>
            <td scope="row" data-label="Description">Import a SurrealML model into a specific Namespace and Database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#ml-export"><code>GET /ml/export/:name/:version</code></a></td>
            <td scope="row" data-label="Description">Export a SurrealML model from a specific Namespace and Database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#custom"><code>/api/:namespace/:database/:endpoint</code></a></td>
            <td scope="row" data-label="Description">Create a custom API endpoint for any number of HTTP methods (GET, POST, etc.)</td>
        </tr>
    </tbody>
</table>

<br />

## Request size limits

Each endpoint caps the size of the request body it will accept. A request over the cap is rejected with `413 Payload Too Large`. The limits differ per endpoint because they are sized to the work each one does: `/sql` takes a query, `/import` takes a data file.

The defaults are the same in 2.x and 3.x:

| Endpoint | Default limit | Environment variable |
| -------- | ------------- | -------------------- |
| `POST /sql` | 1 MiB | `SURREAL_HTTP_MAX_SQL_BODY_SIZE` |
| `POST /rpc` | 4 MiB | `SURREAL_HTTP_MAX_RPC_BODY_SIZE` |
| `/api/:namespace/:database/:endpoint` | 4 MiB | `SURREAL_HTTP_MAX_API_BODY_SIZE` |
| `POST /gql` | 1 MiB | `SURREAL_HTTP_MAX_GQL_BODY_SIZE` |
| `/key` CRUD endpoints | 16 KiB | `SURREAL_HTTP_MAX_KEY_BODY_SIZE` |
| `POST /signin` | 1 KiB | `SURREAL_HTTP_MAX_SIGNIN_BODY_SIZE` |
| `POST /signup` | 1 KiB | `SURREAL_HTTP_MAX_SIGNUP_BODY_SIZE` |
| `POST /import` | 4 GiB | `SURREAL_HTTP_MAX_IMPORT_BODY_SIZE` |
| `POST /ml/import` | 4 GiB | `SURREAL_HTTP_MAX_ML_BODY_SIZE` |
| `POST /mcp` _(since v3.1.0)_ | 4 MiB | `SURREAL_HTTP_MAX_MCP_BODY_SIZE` |

The WebSocket `/rpc` endpoint is not bound by an HTTP body size. It allows up to 128 MiB per message (`SURREAL_WEBSOCKET_MAX_MESSAGE_SIZE`). As most SDKs use the WebSocket engine by default, they are able to send payloads that the plain HTTP endpoints reject. See [Message size limits](/docs/reference/rest-api/rpc-protocol.md#message-size-limits) for how the server ceiling interacts with the limit an SDK applies on its own side.

### Choosing an endpoint for a large payload

The 1 MiB ceiling that a large query runs into applies only to the raw HTTP `/sql` endpoint. Larger payloads have somewhere to go:

* For ordinary queries, connect through a client SDK such as [Rust](/docs/reference/rust.md) or [JavaScript](/docs/reference/javascript.md). The WebSocket engine raises the per-message ceiling to 128 MiB.
* To stay on plain HTTP, use [`POST /rpc`](/docs/reference/rest-api/rpc-protocol.md) for 4 MiB.
* For bulk data loading, use [`POST /import`](#import), which accepts up to 4 GiB per request.

### Tuning the limits

Every limit above is set by an [environment variable](/docs/reference/cli/surrealdb-cli/environment-variables.md#http-server-config) on a self-hosted server. From 3.0 those variables accept byte-size suffixes such as `16MiB` or `4GB`; on 2.x they take a raw byte count.

SurrealDB Cloud instances run the defaults listed above.

<br />

## `GET /status` {#status}

This HTTP RESTful endpoint checks whether the database web server is running, returning a 200 status code.

### Example usage

```bash title="Request"
curl -I http://localhost:8000/status
```

```bash title="Sample output"
HTTP/1.1 200 OK
vary: origin, access-control-request-method, access-control-request-headers
access-control-allow-origin: *
surreal-version: surrealdb/3.0.0
server: SurrealDB
x-request-id: fdb9bcdb-b085-4da0-80ef-a61105c432f9
content-length: 0
date: Tue, 03 Feb 2026 02:10:33 GMT
```

<br />

## `GET /health` {#health}

This HTTP RESTful endpoint checks whether the database server and storage engine are running.

The endpoint returns a `200` status code on success and a `500` status code on failure.

```bash title="Request"
curl -I http://localhost:8000/health
```

```bash title="Sample output"
HTTP/1.1 200 OK
vary: origin, access-control-request-method, access-control-request-headers
access-control-allow-origin: *
surreal-version: surrealdb/3.0.0
server: SurrealDB
x-request-id: 66938ec2-ad7c-4afb-928d-683e7a75433a
content-length: 0
date: Tue, 03 Feb 2026 02:15:08 GMT
```

<br />

## `GET /ready` {#ready}

_(since v3.2.0)_

This HTTP RESTful endpoint is the startup and readiness probe. It returns `200` once deferred startup work (such as import and credential initialisation) has completed and, when a heartbeat budget is configured, the node's cluster heartbeat is fresh. It returns `503` while still starting up or when the heartbeat is stale, and `500` if the heartbeat cannot be read.

Contrast with [`GET /status`](#status) (process and listener liveness only) and [`GET /health`](#health) (storage backend reachability only). Query and auth endpoints are gated with `503` until startup completes, while `/ready` stays reachable throughout so orchestrators can distinguish *starting* from *failed*.

The CLI [`surreal isready`](/docs/reference/cli/surrealdb-cli/commands/isready.md) command calls this endpoint.

```bash title="Request"
curl -I http://localhost:8000/ready
```

```bash title="Output (ready)"
HTTP/1.1 200 OK
vary: origin, access-control-request-method, access-control-request-headers
access-control-allow-origin: *
surreal-version: surrealdb/3.2.0
server: SurrealDB
content-length: 0
```

```bash title="Output (still starting)"
HTTP/1.1 503 Service Unavailable
retry-after: 1
content-length: 0
```

<br />

## `GET /version` {#version}

This HTTP RESTful endpoint returns the version of the SurrealDB database server.

### Example usage

```bash title="Request"
curl http://localhost:8000/version
```

```bash title="Sample output"
surrealdb-3.0.0
```

<br />

## `POST /import` {#import}

This HTTP RESTful endpoint imports a set of SurrealQL queries into a specific namespace and database.

The body is streamed: the server parses and applies statements as the bytes arrive rather than buffering the whole file. This is the endpoint to use for bulk data loading, since it accepts far more than [`/sql`](#sql) or [`/rpc`](/docs/reference/rest-api/rpc-protocol.md) do.

### Size limit and partial imports

The default cap is 4 GiB (`SURREAL_HTTP_MAX_IMPORT_BODY_SIZE`). Two details of how it is enforced matter when planning an import:

* **The cap is cumulative for one request, not per chunk.** The limiter decrements a single allowance across every frame of the stream. A request whose `Content-Length` exceeds the cap is rejected immediately with `413`; a chunked request is accepted and then fails partway through, once the total bytes received cross the cap. Split larger datasets across several files rather than sending one oversized request. The gRPC import path enforces the same cumulative cap.
* **A failed import is partially applied.** Statements are committed as they are parsed, so an import that trips the cap - or is interrupted for any other reason - leaves everything applied up to that point in place. There is no rollback.

> [!IMPORTANT]
> Because a failed import leaves partial data behind, plan for how you would retry. Either structure the file so that re-running it is safe, or import into a fresh namespace or database and swap it in once the import has finished successfully.

### 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">OPTIONAL</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the root, namespace, or database 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="Header">
                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="Header">
                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="Header">
                Sets the selected Database for queries.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

> [!NOTE]
> The `-u` in the example below is a shorthand used by curl to send an Authorization header (name and password), in this case assuming the username `root` and password `secret`.

```bash title="Request"
curl -X POST -u "root:secret" \
  -H "Surreal-NS: main" \
  -H "Surreal-DB: main" \
  -H "Accept: application/json" \
  -d file.surql \
  http://localhost:8000/import
```

<br />

## `POST /export` {#export}

This HTTP RESTful endpoint exports all data for a specific Namespace and Database.

### 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">OPTIONAL</label>
            </td>
            <td colspan="2" scope="row" data-label="Header">
                Sets the root, namespace, or database authentication data
            </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="Header">
                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="Header">
                Sets the selected Database for queries.
            </td>
        </tr>
    </tbody>
</table>

#### Export options

<table>
    <thead>
        <tr>
            <th>Arguments</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                `only`
                <label label="optional" />
            </td>
            <td>
                Whether only specific resources should be exported. When provided, only the resources specified will be exported.
            </td>
        </tr>
        <tr>
            <td>
                `users`
                <label label="optional" />
            </td>
            <td>
                Whether system users should be exported [possible values: true, false].
            </td>
        </tr>
        <tr>
            <td>
                `accesses`
                <label label="optional" />
            </td>
            <td>
                Whether access methods (Record or JWT) should be exported [possible values: true, false]
            </td>
        </tr>
        <tr>
            <td>
                `params`
                <label label="optional" />
            </td>
            <td>
                Whether databases parameters should be exported [possible values: true, false]
            </td>
        </tr>
        <tr>
            <td>
                `functions`
                <label label="optional" />
            </td>
            <td>
                Whether functions should be exported [possible values: true, false]
            </td>
        </tr>
        <tr>
            <td>
                `analyzers`
                <label label="optional" />
            </td>
            <td>
                Whether analyzers should be exported [possible values: true, false]
            </td>
        </tr>
        <tr>
            <td>
                `tables [tables]`
                <label label="optional" />
            </td>
            <td>
                Whether tables should be exported, optionally providing a list of tables
            </td>
        </tr>
        <tr>
            <td>
                `versions`
                <label label="optional" />
            </td>
            <td>
                Whether SurrealKV versioned records should be exported [possible values: true, false]
            </td>
        </tr>
        <tr>
            <td>
                `records`
                <label label="optional" />
            </td>
            <td>
                Whether records should be exported [possible values: true, false]
            </td>
        </tr>
    </tbody>
</table>

### Example usage

> [!NOTE]
> The `-u` in the example below is a shorthand used by curl to send an Authorization header (name and password), in this case assuming the username `root` and password `secret`. The `-o` allows the output to be written to a file.

```bash title="Request"
curl -X GET \
  -u "root:secret" \
  -H "Surreal-NS: main" \
  -H "Surreal-DB: main" \
  -H "Accept: application/json" \
  -o file.surql \
  http://localhost:8000/export
```

```bash title="Exporting specific parameters"
curl -X POST \
  -u "root:secret" \
  -H "Surreal-NS: main" \
  -H "Surreal-DB: main" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -o file.surql \
  -d '{
        "users": true,
        "accesses": false,
        "params": false,
        "functions": false,
        "analyzers": false,
        "versions": false,
        "tables": ["usersTable", "ordersTable"],
        "records": true
      }' \
  http://localhost:8000/export
```

<br />

## `POST /signin` {#signin}

```json title="Method and URL"
POST /signin
```

This HTTP RESTful endpoint is used to access an existing account inside the SurrealDB database server.

### 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>Accept</code>
                    <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the desired content-type of the response
            </td>
        </tr>
    </tbody>
</table>

### Data

<table>
    <thead>
        <tr>
            <th colspan="2">Data</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
    <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>ns</code>
                <label label="required">REQUIRED FOR DB & RECORD</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The namespace to sign in to this is required FOR DB & RECORD users
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>db</code>
                <label label="required">REQUIRED FOR RECORD</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The database to sign in to required for RECORD users
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>ac</code>
                <label label="required">REQUIRED FOR RECORD USER</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The record access method to use for signing in. required for RECORD users
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>user</code>
                <label label="required">REQUIRED FOR ROOT, NS & DB</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The username of the database user required for ROOT, NS & DB users
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>pass</code>
                <label label="required">REQUIRED FOR ROOT, NS & DB</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The password of the database user required for ROOT, NS & DB users
            </td>
        </tr>
    </tbody>
</table>

> [!IMPORTANT]
> The `ac` parameter is only required if you are signing in using an [access method](/docs/reference/query-language/statements/define/access.md) as a record user. For system users on the database, namespace, and root level, this parameter can be omitted.

### Example with a record user

The following example will work as long as as an access method has been defined and a record user has been signed up using the [`/signup`](#signup) endpoint.

```bash title="Request"
curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"users","user":"johndoe","pass":"123456"}' http://localhost:8000/signin
```

```json title="Response"
{
	"code": 200,
	"details": "Authentication succeeded",
	"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
```

### Example with root user

```bash title="Request"
curl -X POST -H "Accept: application/json" -d '{"user":"root","pass":"secret"}' http://localhost:8000/signin
```

```json title="Response"
{
	"code": 200,
	"details": "Authentication succeeded",
	"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
```

### Example with namespace user

To create the namespace user needed for the following query, use the following command.

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json"
  -d 'DEFINE USER johndoe ON NAMESPACE PASSWORD "123456" ROLES EDITOR' http://localhost:8000/sql
```

Once the user has been created, use this command to sign in.

```bash title="Request"
curl -X POST -H "Accept: application/json" -d '{"ns":"main","user":"johndoe","pass":"123456"}' http://localhost:8000/signin
```

```json title="Response"
{
	"code": 200,
	"details": "Authentication succeeded",
	"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
```

### Example usage via postman

After you have defined the users permissions for the record user, you can use the `POST /signin` endpoint to sign in as a user.

Using the [user credentials](/docs/learn/security/authentication/users.md#record-users) created add the following to the request body:
```json
{
    "ns": "main",
    "db": "main",
    "ac": "account",
    "email": "",
    "pass": "123456"
}
```

<br />

## `POST /signup` {#signup}

This HTTP RESTful endpoint is used to create an account inside the SurrealDB database server.

### Header

<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>Accept</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the desired content-type of the response
            </td>
        </tr>
    </tbody>
</table>

### Data

<table>
    <thead>
        <tr>
            <th colspan="2">Data</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>ns</code>
                <label label="required"></label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The namespace to sign up to. This data is `REQUIRED FOR DB & RECORD`
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>db</code>
                <label label="required"></label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The database to sign up to. This data is `REQUIRED FOR RECORD`
            </td>
        </tr>
                <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>access</code>
                <label label="required"></label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The record access method to use for signing up. This data is `REQUIRED FOR RECORD`
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>user</code>
                <label label="required"></label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The username of the database user. This data is `REQUIRED FOR ROOT, NS & DB`
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Header">
                <code>pass</code>
                <label label="required"></label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The password of the database user. This data is `REQUIRED FOR ROOT, NS & DB`
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```bash title="Request"
curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"users","user":"johndoe","pass":"123456"}' http://localhost:8000/signup
```

```json title="Response"
{
	"code": 200,
	"details": "Authentication succeeded",
	"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
```

The above example will only work if a record access method has already been set up.

### Setting up a record access method

Before you sign up a new [record user](/docs/learn/security/authentication/users.md#record-users), you must first [define a record access method](/docs/reference/query-language/statements/define/access/record.md) for the user. The following curl command will do so on the command line using the [`POST /sql`](#sql) endpoint.

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" \
  -H "Accept: application/json"
  -d 'DEFINE ACCESS users 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' http://localhost:8000/sql
```

To do the same using Postman, use the following steps:

1. Navigate to the `POST /sql` endpoint in Postman.
2. Enter the following query in the body of the request:
```surql
-- Enable authentication directly against a SurrealDB record
DEFINE ACCESS users 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 above query defines a record access method called `account` that allows users to sign up and sign in. The access method also defines the session duration to be 24 hours.

3. Click `Send` to send the request to the SurrealDB database server.
4. Navigate to the `POST /signup` endpoint in Postman.
5. Enter the following query in the body of the request:

```json
{
    "ns": "main",
    "db": "main",
    "ac": "users",
    "email": "",
    "pass": "123456"
}
```
6. In the header of the request, set the following key-value pairs:
    - `Accept: application/json`
    - namespace: `test`
    - database: `test`
    - access: `account`
6. Click `Send` to send the request to the SurrealDB database server. You will receive the following response.

```json
{
    "code": 200,
    "details": "Authentication succeeded",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MDY2MTA4MDMsIm5iZiI6MTcwNjYxMDgwMywiZXhwIjoxNzA2Njk3MjAzLCJpc3MiOiJTdXJyZWFsREIiLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJBQyI6Imh1bWFuIiwiSUQiOiJ1c2VyOjZsOTl1OWI0bzVoa3h0NnY3c3NzIn0.3jR8PHgS8iLefZDuPHBFcdUFNfuB3OBNqQtqxLVVzxAIxVj1RAkD5rCEZHH2QaPV-D2zNwYO5Fh_a8jD1l_cqQ"
}
```

<br />

## `GET /key/:table` {#get-table}

This HTTP RESTful endpoint selects all records in a specific table in the database.

### 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">OPTIONAL</label>
            </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>

### Translated query
```surql
SELECT * FROM type::table($table);
```

### Example usage

```bash
curl -X GET -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" http://localhost:8000/key/person
```

<br/>

## `POST /key/:table` {#post-table}

This HTTP RESTful endpoint creates a record in a specific table in the database.

> [!NOTE]
> This HTTP endpoint expects the HTTP body to be a **single inert value** (parsed with the SurrealQL value grammar and bound to **`$data`** in the translated statement). Literals, `$param` references, and constants are allowed; function calls, statements, and parenthesised executable forms are rejected. The body is not executed as a script. Use [`/sql`](/docs/reference/rest-api/http-protocol.md#sql) or RPC when you need to run queries in the request body. JSON-shaped objects such as `{ name: "Billy" }` are valid SurrealQL values and are the usual choice on the wire.

### 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">OPTIONAL</label>
            </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>

### Translated query
```surql
CREATE type::table($table) CONTENT $data;
```

### Example usage

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ name: "Billy" }' http://localhost:8000/key/person
```

```json title="Response"
[
	{
		"result": [
			{
				"id": "person:sf8l6ejkm6swdwoyx2mt",
				"name": "Billy"
			}
		],
		"status": "OK",
		"time": "160.375µs",
		"type": null
	}
]
```

<br />

## `PUT /key/:table` {#put-table}

This HTTP RESTful endpoint updates all records in a specific table in the database.

> [!NOTE]
> This HTTP endpoint expects the HTTP body to be a **single inert value** (SurrealQL literal / object syntax; not an executable statement). Use [`/sql`](/docs/reference/rest-api/http-protocol.md#sql) or RPC to run SurrealQL in the request body.

### 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">OPTIONAL</label>
            </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>

### Translated query
```surql
UPDATE type::table($table) CONTENT $data;
```

### Example usage

To use this example, first create a record using the `POST` endpoint:

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ name: "Billy" }' http://localhost:8000/key/person
```

Then use this `PUT` endpoint to modify the existing record.

```bash
curl -X PUT -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ name: "Not Billy anymore" }' http://localhost:8000/key/person
```

```json title="Response"
[
	{
		"result": [
			{
				"id": "person:f8i2ej4xluh5dgw2lgko",
				"name": "Not Billy anymore"
			}
		],
		"status": "OK",
		"time": "109.458µs",
		"type": null
	}
]
```

<br />

## `PATCH /key/:table` {#patch-table}

This HTTP RESTful endpoint modifies all records in a specific table in the database.

> [!NOTE]
> This HTTP endpoint expects the HTTP body to be a **single inert value** (SurrealQL literal / object syntax; not an executable statement). Use [`/sql`](/docs/reference/rest-api/http-protocol.md#sql) or RPC to run SurrealQL in the request body.

### 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">OPTIONAL</label>
            </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>

### Translated query
```surql
UPDATE type::table($table) MERGE $data;
```

### Example usage

To use this example, first create a record using the `POST` endpoint:

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ id: person:one, name: "Billy" }' http://localhost:8000/key/person
```

Then use this `PATCH` endpoint to modify the existing records.

```bash
curl -X PATCH -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ "name": "Not Billy anymore" }' http://localhost:8000/key/person
```

```json title="Response"
[
	{
		"result": [
			{
				"id": "person:one",
				"name": "Not Billy anymore"
			}
		],
		"status": "OK",
		"time": "162.167µs",
		"type": null
	}
]
```

<br />

## `DELETE /key/:table` {#delete-table}

This HTTP RESTful endpoint deletes all records from the specified table in the database.

### 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">OPTIONAL</label>
            </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>

### Translated query
```surql
DELETE FROM type::table($table) RETURN BEFORE;
```

### Example usage

To use this example, first create a record using the `POST` endpoint:

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ id: person:one, name: "Billy" }' http://localhost:8000/key/person
```

Then use this `DELETE` endpoint to delete and return the records that were just removed.

```bash
curl -X DELETE -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" http://localhost:8000/key/person
```

```json title="Response"
[
	{
		"result": [
			{
				"id": "person:one",
				"name": "Billy"
			}
		],
		"status": "OK",
		"time": "234.75µs",
		"type": null
	}
]
```

<br />

## `GET /key/:table/:id` {#get-record}

This HTTP RESTful endpoint selects a specific record from the database.

### 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">OPTIONAL</label>
            </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>

### Translated query

```surql
SELECT * FROM type::record($table, $id);
```

<br />

### Example usage

```bash
curl -X GET -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" http://localhost:8000/key/person/1
```

<br/>

## `POST /key/:table/:id` {#post-record}

This HTTP RESTful endpoint creates a specific record in a table in the database.

### 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">OPTIONAL</label>
            </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>

### Translated query

```surql
CREATE type::record($table, $id) CONTENT $data;
```

<br />

### Example usage

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ name: "Billy" }' http://localhost:8000/key/person/1
```

```json title="Response"
[
	{
		"result": [
			{
				"id": "person:1",
				"name": "Billy"
			}
		],
		"status": "OK",
		"time": "103.542µs",
		"type": null
	}
]
```

## `PUT /key/:table/:id` {#put-record}

This HTTP RESTful endpoint updates a specific record in a table in the database.

> [!NOTE]
> This HTTP endpoint expects the HTTP body to be a **single inert value** (SurrealQL literal / object syntax; not an executable statement). Use [`/sql`](/docs/reference/rest-api/http-protocol.md#sql) or RPC to run SurrealQL in the request body.

### 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">OPTIONAL</label>
            </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>

### Translated query

```surql
UPDATE type::record($table, $id) CONTENT $data;
```

<br />

## `PATCH /key/:table/:id` {#patch-record}

This HTTP RESTful endpoint modifies a specific record in a table in the database.

> [!NOTE]
> This HTTP endpoint expects the HTTP body to be a **single inert value** (SurrealQL literal / object syntax; not an executable statement). Use [`/sql`](/docs/reference/rest-api/http-protocol.md#sql) or RPC to run SurrealQL in the request body.

### 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">OPTIONAL</label>
            </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>

### Translated query

```surql
UPDATE type::record($table, $id) MERGE $data;
```

<br />

### Example usage

To use this example, first create a record using the `POST` endpoint:

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ name: "Billy" }' http://localhost:8000/key/person/1
```

### Example usage

To use this example, first create a record using the `POST` endpoint:

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ id: person:one, name: "Billy" }' http://localhost:8000/key/person
```

Then use this `PATCH` endpoint to modify the existing record.

```bash
curl -X PATCH -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ "name": "Not Billy anymore" }' http://localhost:8000/key/person/1
```

```json title="Response"
[
	{
		"result": [
			{
				"id": "person:one",
				"name": "Not Billy anymore"
			}
		],
		"status": "OK",
		"time": "162.167µs",
		"type": null
	}
]
```

<br/>

## `DELETE /key/:table/:id` {#delete-record}

This HTTP RESTful endpoint deletes a single specific record from the database.

### 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">OPTIONAL</label>
            </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>

### Translated query

```surql
DELETE FROM type::record($table, $id) RETURN BEFORE;
```

<br />

### Example usage

To use this example, first create a record using the `POST` endpoint:

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d '{ id: person:one, name: "Billy" }' http://localhost:8000/key/person/1
```

Then use this `DELETE` endpoint to delete and return the record that was just removed.

```bash
curl -X DELETE -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" http://localhost:8000/key/person/1
```

```json title="Response"
[
	{
		"result": [
			{
				"id": "person:one",
				"name": "Billy"
			}
		],
		"status": "OK",
		"time": "145.042µs",
		"type": null
	}
]
```

<br/>

## `POST /sql` {#sql}

The SQL endpoint enables use of SurrealQL queries.

> [!NOTE]
> 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">OPTIONAL</label>
            </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>

### Parameters

Query parameters can be provided via URL query parameters. These parameters will securely replace any parameters that are present in the query. This practise is known as prepared statements or parameterised queries, and [should be used](/docs/learn/security/best-practices/security-best-practices.md#query-safety) whenever untrusted inputs are included in a query to prevent injection attacks.

### Example usage

> [!NOTE]
> The `-u` in the example below is a shorthand used by curl to send an Authorization header (name and password), in this case assuming the username `root` and password `secret`.

**V2.x+**

```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
```

**V2.x with token**

```bash title="Request"
curl -X POST -H "Bearer: YourToken" -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
```

```json title="Response"
[
	{
		"time": "14.357166ms",
		"status": "OK",
		"result": [
			{
				"age": "23",
				"id": "person:6r7wif0uufrp22h0jr0o"
				"name": "Simon",
			},
			{
				"age": "28",
				"id": "person:6r7wif0uufrp22h0jr0o"
				"name": "Marcus",
			},
		]
	}
]
```

### Usage in importing data

_(since v3.0.4)_

As of SurrealDB 3.0.4, imports via the [`surreal import`](/docs/reference/cli/surrealdb-cli/commands/import.md) and [`/import`](#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 this endpoint instead.

<br />

## `POST /gql` {#gql}

_(since v3.2.0)_

The GQL endpoint runs [ISO GQL](/docs/learn/querying/gql/overview.md) graph pattern queries against your existing tables and `RELATE` edges - `MATCH … RETURN` reads and data-modifying `INSERT`, `SET`, `REMOVE`, and `DELETE` ([GQL mutations](/docs/learn/querying/gql/mutations.md)).

> [!NOTE]
> From **3.3.0**, GQL is enabled by default - no experimental capability is required. On **3.2.x**, enable it with [`--allow-experimental gql`](/docs/reference/cli/surrealdb-cli/commands/start.md#experimental-capabilities) (or `SURREAL_CAPS_ALLOW_EXPERIMENTAL=gql`). `--allow-all` does not enable experimental capabilities on 3.2.x.

> [!NOTE]
> This endpoint is **not** [GraphQL](/docs/learn/querying/graphql/overview.md). GraphQL queries belong on [`POST /graphql`](#graphql).

> [!NOTE]
> This HTTP endpoint expects the HTTP body to be a **single raw GQL query** (UTF-8 text), not JSON-wrapped. Use [`POST /sql`](#sql) for SurrealQL.

### 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">OPTIONAL</label>
            </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 (`application/json` or `application/cbor`)
            </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>

### Parameters

Pass GQL parameters through **WebSocket RPC** (`method: "gql"`, second element of `params` as a JSON object with typed values).

### Example usage

> [!NOTE]
> The `-u` in the example below is a shorthand used by curl to send an Authorization header (name and password), in this case assuming the username `root` and password `secret`.

```bash title="Request"
curl -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="Response"
[
	{
		"status": "OK",
		"result": [
			{ "name": "A" },
			{ "name": "B" },
			{ "name": "C" }
		],
		"time": "1.5ms"
	}
]
```

Parse errors return **HTTP 400** with an error payload. See [GQL via HTTP](/docs/learn/querying/gql/via-http.md) for enabling GQL, seeding data, and RPC examples.

<br />

## `POST /graphql` {#graphql}

The GraphQL endpoint enables use of GraphQL queries to interact with your data.

> [!NOTE]
> This endpoint is **not** [ISO GQL](/docs/learn/querying/gql/overview.md). GQL `MATCH` queries belong on [`POST /gql`](#gql).

> [!NOTE]
> This HTTP endpoint expects the HTTP body to be a GraphQL query.

### 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">OPTIONAL</label>
            </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

> [!NOTE]
> The `-u` in the example below is a shorthand used by curl to send an Authorization header (name and password), in this case assuming the username `root` and password `secret`.

First, use the `/sql` endpoint to send in a [`DEFINE CONFIG`](/docs/reference/query-language/statements/define/config.md#define-config-graphql) statement to set the database up to use GraphQL.

```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
```

With that done, a GraphQL query can now be performed.

```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 { id name age } }"}' \
  http://localhost:8000/graphql
```

```json title="Response"
{
	"data": {
		"person": [
			{
				"age": 28,
				"id": "person:marcus",
				"name": "Marcus"
			},
			{
				"age": 23,
				"id": "person:simon",
				"name": "Simon"
			}
		]
	}
}
```

<br />

## `POST /ml/import` {#ml-import}

This HTTP RESTful endpoint imports a SurrealML machine learning model into a specific Namespace and Database. It expects the file to be a SurrealML file packaged in the `.surml` file format. As machine learning files can be large, the endpoint expects a chunked HTTP request.

### 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">OPTIONAL</label>
            </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>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

> [!NOTE]
> The `-u` in the example below is a shorthand used by curl to send an Authorization header (name and password), in this case assuming the username `root` and password `secret`.

```bash title="Request"
curl -X POST \
  -u "root:secret" \
  -H "Surreal-NS: main" \
  -H "Surreal-DB: main" \
  -H "Accept: application/json" \
  -d file.surml \
  http://localhost:8000/ml/import
```

### Usage in Python

When using Python, the [surreaml](https://github.com/surrealdb/surrealml) package can be used to upload the model with the following code:

```python
from surrealml import SurMlFile

url = "http://0.0.0.0:8000/ml/import"
SurMlFile.upload("./linear_test.surml", url, 5)
```

<br />

## `GET /ml/export/:name/:version` {#ml-export}

This HTTP RESTful endpoint exports a SurrealML machine learning model from a specific Namespace and Database. The output file with be a SurrealML file packaged in the `.surml` file format. As machine learning files can be large, the endpoint outputs a chunked HTTP response.

### 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">OPTIONAL</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the root, namespace, or database authentication data
            </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

> [!NOTE]
> The `-u` in the example below is a shorthand used by curl to send an Authorization header (name and password), in this case assuming the username `root` and password `secret`. The `-o` allows the output to be written to a file.

```bash title="Request"
curl -X GET \
  -u "root:secret" \
  -H "Surreal-NS: main" \
  -H "Surreal-DB: main" \
  -H "Accept: application/json" \
  -o file.surml \
  http://localhost:8000/ml/export/prediction/1.0.0
```

## Custom endpoint at `/api/:ns/:db/:endpoint` {#custom}

_(since v2.2.0)_

A custom endpoint can be set using a [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) statement. The possible HTTP methods (GET, PUT, etc.) are set using the statement itself. The path begins with `/api`, continues with the namespace and database, and ends with a custom endpoint that can include both static and dynamic path segments.

### 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">OPTIONAL</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the root, namespace, or database authentication data
            </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

To begin, start a server with the `surreal start` command.

```bash
surreal start --user root --pass secret
```

A custom endpoint can first be set up using a `DEFINE API` statement via the `/sql` endpoint.

```bash
curl -X POST -u "root:secret" -H "Surreal-NS: main" -H "Surreal-DB: main" -H "Accept: application/json" -d 'DEFINE API "/custom_response" FOR get MIDDLEWARE api::res::body("json") THEN { { status: 200, body: { some: "info" } } }' http://localhost:8000/sql
```

Once this is set up, a simple curl command to the endpoint will suffice to see the response.

```bash title="Request"
curl http://localhost:8000/api/main/main/custom_response -H "Surreal-NS: ns" -H "Surreal-DB: db" -H "Accept: application/json"
```

```json title="Response"
{"some":"info"}
```

---

Source: https://surrealdb.com/docs/reference/rest-api/postgres-protocol

# Postgres wire protocol

Connect to SurrealDB with standard Postgres clients and drivers, run SurrealQL or ISO GQL, and receive tabular typed results over the Postgres v3 wire protocol.

_(since v3.3.0)_

> [!NOTE]
> The name “Postgres protocol” describes the **transport** layer, not the query dialect. Support for ANSI SQL is not yet present.

The Postgres wire protocol listener lets **any Postgres client** - `psql`, JDBC, `tokio-postgres`, Npgsql, and similar tools - connect to SurrealDB on a TCP port and run queries. The server speaks **Postgres protocol v3.0** (simple and extended query flows, prepared statements, interactive transactions, cancellation, optional TLS).

ANSI SQL is **not yet supported** over the Postgres wire protocol. Clients can currently send SurrealQL by default, or ISO GQL when the session dialect is chosen.

This feature ships with builds that include the `postgres` server feature (enabled in the default feature set). It is opt-in at runtime: the listener starts only when you pass [`--postgres-bind`](/docs/reference/cli/surrealdb-cli/commands/start.md) via the `surreal start` command.

## Why Postgres protocol on SurrealDB?

Postgres is the de facto wire standard for tabular database access. A huge ecosystem already knows how to connect, authenticate, run queries, and consume typed rows through it.

That matters even when the database is not Postgres:

- **`psql` as a REPL**: quick ad hoc SurrealQL without HTTP or a SurrealDB-specific shell.
- **Existing drivers and pools**: reuse JDBC / async-postgres / sqlx-style infrastructure in apps and jobs.
- **BI and SQL-oriented tools**: many products speak Postgres first, allowing a wire-compatible port to let them connect now (with SurrealQL in custom SQL mode).

## Included features

The following chart shows features currently available, along with those that are not yet implemented.

| Included | Not yet included |
| --- | --- |
| Simple query protocol (`Q` messages) | ANSI SQL translation |
| Extended query protocol (Parse / Bind / Execute / Describe / Sync) | `pg_catalog` emulation (needed for some GUIs such as DBeaver) |
| Cleartext password auth (existing IAM path) | MD5 auth (legacy Postgres; not planned) |
| Namespace/database via startup `database=ns/db` | `COPY` |
| Session `USE` / `LET` persistence | `LIVE` queries over Postgres |
| Interactive `BEGIN` / `COMMIT` / `ROLLBACK` | GQL inside an open interactive transaction |
| Dialect switch: SurrealQL (default) or ISO GQL | Full static typing for every `prepare` shape |
| SCRAM-SHA-256 auth over SASL (when the user has SCRAM verifier material - see [Authentication](#authentication)) | |
| Positional parameters (`$1` → `$_1` rewrite) | |
| Typed result columns inferred from values | |
| TLS via existing `--web-crt` / `--web-key` | |
| Query cancellation (`CancelRequest`) | |

## Start the listener

Bind a separate address from the HTTP server (default HTTP remains `127.0.0.1:8000`):

```bash
surreal start --user root --pass secret \
  --postgres-bind 127.0.0.1:5432 \
  memory
```

Environment variable equivalent:

**Bash**

```bash
export SURREAL_POSTGRES_BIND=127.0.0.1:5432
surreal start --user root --pass secret memory
```

**PowerShell**

```powershell
$env:SURREAL_POSTGRES_BIND = "127.0.0.1:5432"
surreal start --user root --pass secret memory
```

Connect with `psql`:

```bash
psql "host=127.0.0.1 port=5432 user=root password=secret dbname=main/main"
```

The startup parameter **`database`** selects namespace and database as **`ns/db`** (a single slash). This mirrors choosing `Surreal-NS` and `Surreal-DB` on HTTP.

## Authentication

The Postgres listener supports two authentication mechanisms. Clients that offer [SCRAM-SHA-256](https://datatracker.ietf.org/doc/html/rfc7677) (most modern drivers and `psql`) use SASL challenge - response auth when the user has SCRAM verifier material stored. Otherwise the server falls back to the Postgres **cleartext password** message and verifies against the existing Argon2 hash via **`iam::verify::basic`**.

**MD5** (legacy Postgres auth) is not supported.

### SCRAM credentials (automatic)

When you define or update a system user with a plaintext password, SurrealDB derives and stores **SCRAM-SHA-256 verifier material** alongside the Argon2 hash. No extra DDL or `crypto::` functions are required.

```surql
DEFINE USER analyst ON DATABASE PASSWORD 'secret' ROLES VIEWER;
ALTER USER analyst ON DATABASE PASSWORD 'new-secret';
```

Users defined with **`PASSHASH` only** have no SCRAM material (no plaintext was available at definition time). They can sign in over HTTP/RPC with the hash path, but Postgres clients must use **cleartext password** auth for those users until you run **`ALTER USER … PASSWORD`** to set a plaintext password and regenerate SCRAM verifiers.

Root credentials created via **`surreal start --user` / `--pass`** work on the Postgres port the same way as other IAM users once SCRAM material exists for that account.

### TLS

SCRAM avoids sending the password in the clear during authentication, but **TLS** (`--web-crt` and `--web-key`) is still recommended in production to encrypt the whole session. The server logs a warning when Postgres is served in plaintext without TLS configured.

Failed auth returns **`28P01`** without user enumeration.

## Coming from Postgres and SQL

The connection speaks **Postgres wire protocol**, but the query language is **SurrealQL** (with [ISO GQL](#iso-gql-optional-dialect) as an optional alternative). The server does **not** yet translate ANSI SQL. If you know Postgres or write SQL for BI tools every day, that background still helps (many SurrealQL queries look and behave like SQL) but you are learning **SurrealQL**, not sending Postgres queries verbatim.

### Try it with `psql`

Start a local instance and connect (see [Start the listener](#start-the-listener)):

```bash
psql "host=127.0.0.1 port=5432 user=root password=secret dbname=main/main"
```

Seed some data - these statements are SurrealQL, but familiar if you know SQL:

```sql
CREATE person:ada SET name = 'Ada', age = 36, city = 'London';
CREATE person:bob SET name = 'Bob', age = 28, city = 'Paris';
CREATE person:carl SET name = 'Carl', age = 41, city = 'London';
```

Queries that often work on the first guess for SQL users:

```sql
-- Filter, sort, limit
SELECT name, age, city FROM person WHERE age > 30 ORDER BY name LIMIT 10;

-- Aggregation
SELECT city, count() AS people FROM person GROUP BY city;

-- Update and delete
UPDATE person SET age += 1 WHERE city = 'London';
DELETE person WHERE age < 18;
```

When something fails, check the error message and compare with the [SurrealQL reference](/docs/reference/query-language.md) - the fix is usually a small syntax or model difference (record IDs, graph syntax, functions), not the connection itself.

Switch namespace or database on the same connection:

```sql
USE NS demo DB demo;
SELECT * FROM person;
```

Session variables persist for the connection:

```sql
LET $min_age = 30;
SELECT name, age FROM person WHERE age > $min_age;
```

### BI and analytics tools

Many BI products (Metabase, Superset, Grafana Postgres data sources, and similar) can add a **Postgres** connection with host, port, user, password, and database name. Use the same **`database=ns/db`** form as `psql`.

| BI workflow | Works now? | Notes |
| --- | --- | --- |
| **Native / custom SQL** query editor | Yes | Write **SurrealQL** in the tool’s SQL box. This is the main BI path today. |
| **`psql`-style exploration** | Yes | Ad hoc SELECT, GROUP BY, filters - good for learning SurrealQL. |
| **Drag-and-drop chart builder** | Limited | Tools that auto-generate SQL expect ANSI SQL and often query `information_schema` or `pg_catalog`. |
| **Schema browser / table picker** | No | [`pg_catalog` emulation](#included-features) is not yet available. |
| **Paste arbitrary Postgres SQL** | No | No SQL-to-SurrealQL translation yet. Similar-looking SELECTs may work; Postgres-specific syntax will not. |

Configure the Postgres data source with your SurrealDB host and `--postgres-bind` port, set database to `your_ns/your_db`, then open the tool’s **SQL** or **Native query** mode and paste SurrealQL.

## Query languages

### SurrealQL (default)

Every new connection uses the **SurrealQL** dialect unless configured otherwise. Send SurrealQL as you would on [`POST /sql`](/docs/reference/rest-api/http-protocol.md):

```sql
CREATE person SET name = 'A';
SELECT * FROM person;
```

`USE ns/db` and `LET` persist for the lifetime of the connection, as on other surfaces.

### ISO GQL (optional dialect)

GQL is **not** the default and is **not** what most Postgres users expect from a “Postgres” port. It is available so the **same connection** can run [ISO GQL](/docs/learn/querying/gql/overview.md) when you opt in - the same engine as [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql), with results encoded as Postgres rows instead of JSON.

**When GQL over Postgres is useful:**

- You already use a **Postgres driver or pool** for SurrealQL and want graph queries **without a second HTTP client**.
- A tool or script **only speaks Postgres** but you want to try GQL from it (for example `psql` with `SET dialect = 'gql'`).
- You standardise on **one TCP port and auth path** for both SurrealQL and GQL in internal tooling.

**When to use HTTP or RPC instead:** public APIs, browser clients, typed GQL variables over RPC, or anything that fits the JSON envelope and headers of [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql) more naturally.

Enable GQL at the server (same experimental gate as HTTP):

```bash
surreal start --user root --pass secret \
  --allow-experimental gql \
  --postgres-bind 127.0.0.1:5432 \
  memory
```

Select GQL for the session:

```sql
-- At connect time (many drivers pass startup options):
-- options=-c dialect=gql

-- Or after connect:
SET dialect = 'gql';
MATCH (n:person) RETURN n.name AS name ORDER BY name;

SET dialect = 'surrealql';
SELECT * FROM person;
```

> [!WARNING]
> **GQL inside an interactive transaction** (`BEGIN` … `COMMIT`) is **rejected**. Finish or roll back the transaction before switching to GQL, or use SurrealQL within the transaction.

## Results and typing

Result shape follows Postgres tabular conventions:

- **Object rows** (typical `SELECT` / `RETURN` output) become **one column per object key**, with types inferred from the values and promoted across rows (for example `int` and `float` widen to `float8` / `numeric`).
- **Scalars and non-object arrays** become a single **`value`** column.
- SurrealDB types map to Postgres OIDs where possible (`bool`, `int8`, `float8`, `numeric`, `text`, `timestamptz`, `interval`, `uuid`, `bytea`, `jsonb`). Record IDs and similar values encode as **text**; nested structures encode as **jsonb**.

### Extended query protocol and prepared statements

Drivers using **Parse / Bind / Execute** (extended protocol) get a hybrid typing model:

- **Driver-prepared** statements (no eager execute) advertise a single **`jsonb`** column - SurrealDB has no static schema for arbitrary prepared SurrealQL.
- **`prepare_typed` / portal describe** paths that execute eagerly return **true typed columns** matching the result.

Postgres positional parameters **`$1`, `$2`, …** are rewritten to SurrealQL **`$_1`, `$_2`, …** (lexer-safe, comment-aware) and bound as **`_1`, `_2`, …** in the session.

## Interactive transactions

Standalone **`BEGIN`**, **`COMMIT`**, and **`ROLLBACK`** open an interactive transaction on the connection. Ready-for-query status bytes follow Postgres semantics (`I` idle, `T` in transaction, `E` failed transaction). After an error in a transaction block, further commands receive **`25P02`** until `COMMIT` or `ROLLBACK`.

**Divergence from Postgres:** SurrealDB **auto-commits each top-level statement** outside an explicit `BEGIN` … `COMMIT` block. For all-or-nothing behaviour, wrap statements in an explicit transaction.

## Capabilities and security

Connections are gated like other query surfaces:

- **`RouteTarget::Postgres`** - controlled via [`--allow-http`](/docs/reference/cli/surrealdb-cli/commands/start.md) / [`--deny-http`](/docs/reference/cli/surrealdb-cli/commands/start.md) with the route name **`postgres`** (the capability helper is shared with HTTP route names).
- **Arbitrary query** - subject to [`--allow-arbitrary-query`](/docs/learn/security/authorization/capabilities.md#arbitrary-queries) / [`--deny-arbitrary-query`](/docs/learn/security/authorization/capabilities.md#arbitrary-queries) for `guest`, `record`, and `system` users.
- **GQL** - requires [`--allow-experimental gql`](/docs/reference/cli/surrealdb-cli/commands/start.md#experimental-capabilities) in addition to the above.

Authentication is described in [Authentication](#authentication). Resource limits include a connection cap, startup/auth timeout, message size limits, and prepared-statement / portal caps.

## Comparison with other surfaces

| Surface | Transport | Default language | Typical client |
| --- | --- | --- | --- |
| [`POST /sql`](/docs/reference/rest-api/http-protocol.md) | HTTP | SurrealQL | `curl`, scripts |
| [`POST /gql`](/docs/reference/rest-api/http-protocol.md#gql) | HTTP | ISO GQL | `curl`, HTTP clients |
| [RPC](/docs/reference/rest-api/rpc-protocol.md) | HTTP / WebSocket | SurrealQL (+ RPC methods) | Official SDKs |
| **Postgres wire** | TCP (Postgres v3) | SurrealQL | `psql`, JDBC, `tokio-postgres`, … |

## Related pages

- [SurrealQL reference](/docs/reference/query-language.md): statements, types, and clauses
- [GQL overview](/docs/learn/querying/gql/overview.md): ISO GQL concepts and wire surfaces
- [GQL via HTTP](/docs/learn/querying/gql/via-http.md): enable GQL and run queries with `curl`
- [`DEFINE USER`](/docs/reference/query-language/statements/define/user.md#scram-credentials-for-postgres-clients): SCRAM verifier material for Postgres clients
- [Capabilities](/docs/learn/security/authorization/capabilities.md): lock down arbitrary queries and routes
- [`surreal start`](/docs/reference/cli/surrealdb-cli/commands/start.md): CLI reference including `--postgres-bind`

---

Source: https://surrealdb.com/docs/reference/rest-api/rpc-protocol

# RPC protocol

The RPC protocol allows for easy bidirectional communication with SurrealDB.

The RPC protocol allows for network protocol agnostic communication with SurrealDB. It is used internally by our client SDKs, and supports both HTTP and WebSocket based communication. Combined with the power of our [CBOR protocol](./cbor) specification, the RPC protocol provides a fully type-safe and efficient way to interact with SurrealDB over the network.

## Session variables

SurrealDB's session variables provide a robust mechanism for managing session-specific data. Think of them as temporary storage tied directly to a user's active connection, ideal for tasks like maintaining application state, storing user preferences, or holding temporary data relevant only to the current session.

A key characteristic of session variables is their scope: they are strictly confined to the individual connection. This isolation ensures that one user's session data remains private and does not interfere with others, allowing for personalized experiences within a multi-user environment.
You can interact with session variables in the following ways:

1.  **Explicit Session-Wide Management:**
    *   Use the [`let`](#let) method to define a new variable or update an existing one within the current session. This variable will persist for the duration of the connection.
    *   Use the [`unset`](#unset) method to remove a previously defined variable from the session.
    *   The [`reset`](#reset) method, in addition to its other functions, clears *all* currently defined session variables, restoring the session's variable state.

2.  **Implicit Request-Scoped Management:**
    *   Methods [`query`](#query), [`select`](#select), [`insert`](#insert), [`create`](#create), [`upsert`](#upsert), [`update`](#update), [`relate`](#relate), and [`delete`](#delete), accept an optional `vars` parameter. This parameter is an object containing key-value pairs, where each key represents the variable name (without the leading `$`) and the value is the data to be assigned.
    *   Variables passed via this parameter are defined *only* for the execution context of that specific method call. They temporarily override any session-wide variable with the same name for that request but do not permanently alter the session state. These variables are automatically discarded once the method execution completes.

To utilize a session variable within a query or method, prefix its name with a dollar sign (`$`), for example, `$user_id`.
## Supported methods

You can use the RPC protocol to perform the following actions:

<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="#authenticate"><code>authenticate [ token ]</code></a></td>
            <td scope="row" data-label="Description">Authenticate a user against SurrealDB with a token</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#create"><code>create [ thing, data ]</code></a></td>
            <td scope="row" data-label="Description">Create a record with a random or specified ID</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#delete"><code>delete [ thing ]</code></a></td>
            <td scope="row" data-label="Description">Delete either all records in a table or a single record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#info"><code>info</code></a></td>
            <td scope="row" data-label="Description">Returns the record of an authenticated record user</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#insert"><code>insert [ thing, data ]</code></a></td>
            <td scope="row" data-label="Description">Insert one or multiple records in a table</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#insert_relation"><code>insert_relation [ table, data ]</code></a></td>
            <td scope="row" data-label="Description">Insert a new relation record into a specified table or infer the table from the data</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#invalidate"><code>invalidate</code></a></td>
            <td scope="row" data-label="Description">Invalidate a user's session for the current connection</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#kill"><code>kill [ queryUuid ]</code></a></td>
            <td scope="row" data-label="Description">Kill an active live query</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#let"><code>let [ name, value ]</code></a></td>
            <td scope="row" data-label="Description">Define a variable on the current connection</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#live"><code>live [ table, diff ]</code></a></td>
            <td scope="row" data-label="Description">Initiate a live query</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#merge"><code>merge [ thing, data ]</code></a></td>
            <td scope="row" data-label="Description">Merge specified data into either all records in a table or a single record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#patch"><code>patch [ thing, patches, diff ]</code></a></td>
            <td scope="row" data-label="Description">Patch either all records in a table or a single record with specified patches</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#ping"><code>ping</code></a></td>
            <td scope="row" data-label="Description">Sends a ping to the database</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#query"><code>query [ sql, vars ]</code></a></td>
            <td scope="row" data-label="Description">Execute a custom query with optional variables</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#relate"><code>relate [ in, relation, out, data? ]</code></a></td>
            <td scope="row" data-label="Description"> Create graph relationships between created records </td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#reset"><code>reset</code></a></td>
            <td scope="row" data-label="Description">Resets all attributes for the current connection</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#run"><code>run [ func_name, version, args ]</code></a></td>
            <td scope="row" data-label="Description">Execute built-in functions, custom functions, or machine learning models with optional arguments.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#select"><code>select [ thing ]</code></a></td>
            <td scope="row" data-label="Description">Select either all records in a table or a single record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#signin"><code>signin [NS, DB, AC, ... ]</code></a></td>
            <td scope="row" data-label="Description">Signin a root, NS, DB or record user against SurrealDB</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#signup"><code>signup [ NS, DB, AC, ... ]</code></a></td>
            <td scope="row" data-label="Description">Signup a user using the SIGNUP query defined in a record access method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#unset"><code>unset [ name ]</code></a></td>
            <td scope="row" data-label="Description">Remove a variable from the current connection</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#update"><code>update [ thing, data ]</code></a></td>
            <td scope="row" data-label="Description">Modify either all records in a table or a single record with specified data if the record already exists</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#upsert"><code>upsert [ thing, data ]</code></a></td>
            <td scope="row" data-label="Description">Replace either all records in a table or a single record with specified data</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#use"><code>use [ ns, db ]</code></a></td>
            <td scope="row" data-label="Description">Specifies or unsets the namespace and/or database for the current connection</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#version"><code>version</code></a></td>
            <td scope="row" data-label="Description">Returns version information about the database/server</td>
        </tr>
    </tbody>
</table>

<br />

## Message size limits

How much a single RPC call may carry depends on the transport it arrives over.

| Transport | Default limit | Environment variable |
| --------- | ------------- | -------------------- |
| WebSocket `/rpc` | 128 MiB per message | `SURREAL_WEBSOCKET_MAX_MESSAGE_SIZE` |
| HTTP `POST /rpc` | 4 MiB per request body | `SURREAL_HTTP_MAX_RPC_BODY_SIZE` |

The WebSocket ceiling is roughly thirty times the HTTP one, which is why the SDKs default to the WebSocket engine. If a payload is rejected over HTTP, moving the same call onto a WebSocket connection is usually enough.

### Server and client limits

The values above are what the **server** accepts. An SDK may also enforce its own limit on **outgoing** messages, and that limit is applied before anything reaches the network. The effective ceiling is therefore whichever of the two is lower.

The Rust SDK defaults to 64 MiB per message. Sending more than that produces a `Message too long` error from the client itself, not a rejection from the server. The limit is configurable:

```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::{Config, WebsocketConfig};
use surrealdb::Surreal;

let websocket = WebsocketConfig::new().max_message_size(128 << 20); // 128 MiB
let config = Config::new().websocket(websocket)?;
let db = Surreal::new::<Ws>(("127.0.0.1:8000", config)).await?;
```

Raising a client limit above the server's ceiling does not help, as the server still rejects the message. Raise both in this case, or keep messages under the lower of the two.

For request bodies on the other HTTP endpoints, see [Request size limits](/docs/reference/rest-api/http-protocol.md#request-size-limits).

<br />

## `authenticate`
This method allows you to authenticate a user against SurrealDB with a token.

```json title="Method Syntax"
authenticate [ token ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>token</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The token that authenticates the user
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```json title="Request"
{
    "id": 1,
    "method": "authenticate",
    "params": [ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJTdXJyZWFsREIiLCJpYXQiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMiwiZXhwIjoxODM2NDM5MDIyLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJTQyI6InVzZXIiLCJJRCI6InVzZXI6dG9iaWUifQ.N22Gp9ze0rdR06McGj1G-h2vu6a6n9IVqUbMFJlOxxA" ]
}
```

```json title="Response"
{
    "id": 1,
    "result": null
}
```

<br />

## `create`

This method creates a record either with a random or specified ID.

```json title="Method Syntax"
create [ thing, data ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The thing (Table or Record ID) to create. Passing just a table will result in a randomly generated ID
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>data</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The content of the record
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "create",
    "params": [
        "person",
        {
            "name": "Mary Doe"
        }
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": [
        {
            "id": "person:s5fa6qp4p8ey9k5j0m9z",
            "name": "Mary Doe"
        }
    ]
}
```

<br />

## `delete`

This method deletes either all records in a table or a single record.

```json title="Method Syntax"
delete [ thing ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>record_id</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The record_id (Table or Record ID) to delete
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "delete",
    "params": [ "person:8s0j0bbm3ngrd5c9bx53" ]
}
```

Notice how the deleted record is returned. This differs from a [`DELETE`](/docs/reference/query-language/statements/delete.md) statement via the CLI or SurrealDB Studio which returns nothing unless the `RETURN BEFORE` clause is used.

```json title="Response"
{
    "id": 1,
    "result": {
        "active": true,
        "id": "person:8s0j0bbm3ngrd5c9bx53",
        "last_updated": "2023-06-16T08:34:25Z",
        "name": "John Doe"
    }
}
```

<br />

## `info`

This method returns the record of an authenticated record user.

```json title="Method Syntax"
info
```

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "info"
}
```

The result property of the response is likely different depending on your schema and the authenticated user. However, it does represent the overall structure of the responding message.

```json title="Response"
{
    "id": 1,
    "result": {
        "id": "user:john",
        "name": "John Doe"
    }
}
```

<br />

## `insert`

This method creates a record either with a random or specified ID.

```json title="Method Syntax"
insert [ thing, data ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The table to insert in to
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>data</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            One or multiple record(s)
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```json title="Request"
{
    "id": 1,
    "method": "insert",
    "params": [
        "person",
        {
            "name": "Mary Doe"
        }
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": [
        {
            "id": "person:s5fa6qp4p8ey9k5j0m9z",
            "name": "Mary Doe"
        }
    ]
}
```

### Bulk insert

```json title="Request"
{
    "id": 1,
    "method": "insert",
    "params": [
        "person",
        [
            {
                "name": "Mary Doe"
            },
            {
                "name": "John Doe"
            }
        ]
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": [
        {
            "id": "person:s5fa6qp4p8ey9k5j0m9z",
            "name": "Mary Doe"
        },
        {
            "id": "person:xtbbojcm82a97vus9x0j",
            "name": "John Doe"
        }
    ]
}
```

<br />

## `insert_relation`

This method inserts a new relation record into the database. You can specify the relation table to insert into and provide the data for the new relation.

```json title="Method Syntax"
insert_relation [ table, data ]
```

### Parameters

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>table</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The name of the relation table to insert into. If `null` or `none`, the table is determined from the `id` field in the `data`.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>data</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            An object containing the data for the new relation record, including `in`, `out`, and any additional fields.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

**Inserting a Relation into a Specified Table**

```json title="Request"
{
    "id": 1,
    "method": "insert_relation",
    "params": [
        "likes",                   // (relation table)
        {                          // data
            "in": "user:alice",
            "out": "post:123",
            "since": "2024-09-15T12:34:56Z"
        }
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": {
        "id": "likes:user:alice:post:123",
        "in": "user:alice",
        "out": "post:123",
        "since": "2024-09-15T12:34:56Z"
    }
}
```

**Inserting a Relation Without Specifying the Table**

If you do not specify the `table` parameter (i.e., set it to `null` or `none`), the relation table is inferred from the `id` field within the `data`.

```json title="Request"
{
    "id": 2,
    "method": "insert_relation",
    "params": [
        null,                      // relation table is null
        {                          // data
            "id": "follows:user:alice:user:bob",
            "in": "user:alice",
            "out": "user:bob",
            "since": "2024-09-15T12:34:56Z"
        }
    ]
}
```

```json title="Response"
{
    "id": 2,
    "result": {
        "id": "follows:user:alice:user:bob",
        "in": "user:alice",
        "out": "user:bob",
        "since": "2024-09-15T12:34:56Z"
    }
}
```

### Notes

- **`table` parameter:**
  - Specifies the relation table into which the new relation record will be inserted.
  - If `table` is `null` or `none`, the method expects the `data` to contain an `id` from which it can infer the relation table.

- **`data` parameter:**
  - Must include at least the `in` and `out` fields, representing the starting and ending points of the relation.
  - Can include additional fields to store more information within the relation.

- **Relation IDs:**
  - If an `id` is provided in the `data`, it will be used as the identifier for the new relation record.
  - If no `id` is provided, the system may generate one based on the `table`, `in`, and `out` fields.

- **Single vs. multiple inserts:**
  - The method primarily handles single relation inserts.
  - The `one` variable in the code determines if the `table` parameter refers to a single item.

### Error handling

- **Invalid parameters:**
  - If you provide fewer than two parameters or incorrect parameter types, you will receive an `InvalidParams` error.
  - The method expects exactly two parameters: `table` and `data`.

**Example of invalid parameters:**

```json title="Request with missing parameters"
{
    "id": 3,
    "method": "insert_relation",
    "params": [
        "likes"  // Missing the data parameter
    ]
}
```

```json title="Response"
{
    "id": 3,
    "error": {
        "cause": null,
        "code": -32603,
        "details": { "kind": "InvalidParams" },
        "kind": "Validation",
        "message": "Expected (what, data)"
    }
}
```

See [Errors](/docs/reference/rest-api/errors.md) for the full error shape, the list of kinds, and the wire codes.

### Best practices

- **Include `in` and `out` Fields:**
  - Always provide the `in` and `out` fields in your `data` to define the relation endpoints.

- **Specifying the Relation Table:**
  - If possible, specify the `table` parameter to clearly indicate the relation table.
  - If not specified, ensure that the `id` in `data` correctly reflects the desired relation table.

- **Providing an `id` in `data`:**
  - If you want to control the `id` of the relation, include it in the `data`.
  - This is especially important when `table` is `null` or `none`.

### Additional examples

**Inserting a Relation with Auto-Generated ID**

```json title="Request"
{
    "id": 4,
    "method": "insert_relation",
    "params": [
        "friendship",              // table (relation table)
        {                          // data
            "in": "user:alice",
            "out": "user:bob",
            "since": "2024-09-15"
        }
    ]
}
```

```json title="Response"
{
    "id": 4,
    "result": {
        "id": "friendship:user:alice:user:bob",
        "in": "user:alice",
        "out": "user:bob",
        "since": "2024-09-15"
    }
}
```

**Notes:**

- The `id` is generated based on the `table`, `in`, and `out` fields.
- The relation is inserted into the `friendship` table.

The `insert_relation` method is a powerful way to insert new relation records into your database, allowing you to specify the relation table and include detailed data for each relation. By understanding the parameters and how the method operates, you can effectively manage relationships between records in your database.

> [!NOTE]
> This method is particularly useful in databases that support graph-like relations, enabling complex data modelling and querying capabilities.

<br />

## `invalidate`

This method will invalidate the user's session for the current connection.

```json title="Method Syntax"
invalidate
```

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "invalidate"
}
```

```json title="Response"
{
    "id": 1,
    "result": null
}
```

<br />

## `let` <label label="websocket only" /> {#let}

This method stores a variable on the current connection.

```json title="Method Syntax"
let [ name, value ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>name</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The name for the variable without a prefixed $ character
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>value</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The value for the variable
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "let",
    "params": [ "website", "https://surrealdb.com/" ]
}
```

```json title="Response"
{
    "id": 1,
    "result": null
}
```

<br />

## `live` <label label="websocket only" /> {#live}

This methods initiates a live query for a specified table name.

```json title="Method Syntax"
live[ table ]
```

> [!IMPORTANT]
> For more advanced live queries where filters are needed, use the Query method to initiate a custom live query.

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>table</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table to initiate a live query for
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>diff</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                If set to true, live notifications will contain an array of [JSON Patches](https://jsonpatch.com) instead of the entire record
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "live",
    "params": [ "person" ]
}
```

```json title="Response"
{
    "id": 1,
    "result": "0189d6e3-8eac-703a-9a48-d9faa78b44b9"
}
```

### Live notification
For every creation, update or deletion on the specified table, a live notification will be sent. Live notifications do not have an ID attached, but rather include the Live Query's UUID in the result object.

```json
{
    "result": {
        "action": "CREATE",
        "id": "0189d6e3-8eac-703a-9a48-d9faa78b44b9",
        "result": {
            "id": "person:8s0j0bbm3ngrd5c9bx53",
            "name": "John"
        }
    }
}
```

<br />

## `kill` <label label="websocket only" /> {#kill}

This method kills an active live query, stopping any further notifications for it.

```json title="Method Syntax"
kill [ queryUuid ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>queryUuid</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The UUID of the live query to kill, as returned by the [live](#live) method
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "kill",
    "params": [ "0189d6e3-8eac-703a-9a48-d9faa78b44b9" ]
}
```

```json title="Response"
{
    "id": 1,
    "result": null
}
```

## `merge`

This method merges specified data into either all records in a table or a single record.

```json title="Method Syntax"
merge [ thing, data ]
```

> [!NOTE]
> This function merges the current document / record data with the specified data. If no merge data is passed it will simply trigger an update.

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The thing (Table or Record ID) to merge into
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>data</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The content of the record
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "merge",
    "params": [
        "person",
        {
            "active": true
        }
    ]
}
```

```json title="Response"
{
  "id": 1,
  "result": [
      {
          "active": true,
          "id": "person:8s0j0bbm3ngrd5c9bx53",
          "name": "John Doe"
      },
      {
          "active": true,
          "id": "person:s5fa6qp4p8ey9k5j0m9z",
          "name": "Mary Doe"
      }
  ]
}
```

<br />

## `patch`

This method patches either all records in a table or a single record with specified patches.

```json title="Method Syntax"
patch [ thing, patches, diff ]
```

> [!NOTE]
> This function patches the current document / record data with the specified [JSON Patch](https://jsonpatch.com) data.

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The thing (Table or Record ID) to patch
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>patches</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            An array of patches following the [JSON Patch specification](https://jsonpatch.com)
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>diff</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            A boolean representing if just a diff should be returned.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "patch",
    "params": [
        "person",
        [
            { "op": "replace", "path": "/last_updated", "value": "2023-06-16T08:34:25Z" }
        ]
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": [
        [
            {
                "op": "add",
                "path": "/last_updated",
                "value": "2023-06-16T08:34:25Z"
            }
        ],
        [
            {
                "op": "add",
                "path": "/last_updated",
                "value": "2023-06-16T08:34:25Z"
            }
        ]
    ]
}
```

<br />

## `ping`

```json title="Method Syntax"
ping
```

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "ping",
}
```

```json title="Response"
{
  "id": 1,
  "result": null
}
```

<br />

## `query`

This methods sends a custom SurrealQL query.

```json title="Method Syntax"
query [ sql, vars ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2">Parameter</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>sql</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The query to execute against SurrealDB
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>vars</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                A set of variables used by the query
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "query",
    "params": [
        "CREATE person SET name = 'John'; SELECT * FROM type::table($tb);",
        {
            "tb": "person"
        }
    ]
}
```

```json title="Response"
{
  "id": 1,
  "result": [
      {
          "status": "OK",
          "time": "152.5µs",
          "result": [
              {
                  "id": "person:8s0j0bbm3ngrd5c9bx53",
                  "name": "John"
              }
          ]
      },
      {
          "status": "OK",
          "time": "32.375µs",
          "result": [
              {
                  "id": "person:8s0j0bbm3ngrd5c9bx53",
                  "name": "John"
              }
          ]
      }
  ]
}
```

## `relate`

This method relates two records with a specified relation.

```json title="Method Syntax"
relate [ in, relation, out, data? ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>in</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The record to relate to
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>relation</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The relation table
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>out</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The record to relate from
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>data</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The content of the record
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "relate",
    "params": [
        "person:12s0j0bbm3ngrd5c9bx53",
        "knows",
        "person:8s0j0bbm3ngrd5c9bx53"
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": {
        "id": "knows:12s0j0bbm3ngrd5c9bx53:8s0j0bbm3ngrd5c9bx53",
        "in": "person:12s0j0bbm3ngrd5c9bx53",
        "out": "person:8s0j0bbm3ngrd5c9bx53"
    }
}
```

### Creating a relation with additional data

```json title="Request"
{
    "id": 2,
    "method": "relate",
    "params": [
        "person:john_doe",          // in
        "knows",                    // relation
        "person:jane_smith",        // out
        { "since": "2020-01-01" }   // data
    ]
}
```

```json title="Response"
{
    "id": 2,
    "result": {
        "id": "knows:person:john_doe:person:jane_smith",
        "in": "person:jane_smith",
        "out": "person:john_doe",
        "since": "2020-01-01"
    }
}
```

<br />

## `reset`

This method will reset all attributes for the current connection. It clears authentication (much like invalidate), unsets the selected NS/DB, unsets any defined connection params, and aborts any active live queries. On WebSocket connections it also cancels open client-managed transactions for the session being reset and frees their slots under [`SURREAL_MAX_TRANSACTIONS_PER_CONNECTION`](/docs/reference/cli/surrealdb-cli/environment-variables.md#websocket-config) / [`SURREAL_MAX_TRANSACTIONS_PER_SESSION`](/docs/reference/cli/surrealdb-cli/environment-variables.md#websocket-config).

```json title="Method Syntax"
reset
```

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "reset"
}
```

```json title="Response"
{
    "id": 1,
    "result": null
}
```

<br />

## `run`

This method allows you to execute built-in functions, custom functions, or machine learning models with optional arguments.

```json title="Method Syntax"
run [ func_name, version?, args? ]
```

### Parameters

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>func_name</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The name of the function or model to execute. Prefix with `fn::` for custom functions or `ml::` for machine learning models.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>version</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The version of the function or model to execute.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>args</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The arguments to pass to the function or model.
            </td>
        </tr>
    </tbody>
</table>

### Executing a built-in function

```json title="Request"
{
    "id": 1,
    "method": "run",
    "params": [ "time::now" ]
}
```

```json title="Response"
{
    "id": 1,
    "result": "2024-09-15T12:34:56Z"
}
```

### Executing a custom function

```json title="Request"
{
    "id": 1,
    "method": "run",
    "params": [ "fn::calculate_discount", null, [ 100, 15 ] ]
}
```

```json title="Response"
{
    "id": 1,
    "result": 85
}
```

### Executing a machine learning model

```json title="Request"
{
    "id": 1,
    "method": "run",
    "params": [ "ml::image_classifier", "v2.1", [ "image_data_base64" ] ]
}
```

```json title="Response"
{
    "id": 1,
    "result": "cat"
}
```

> [!IMPORTANT]
> When using a machine learning model (prefixed with `ml::`), the `version` parameter is **required**.

<br />

## `select`

This method selects either all records in a table or a single record.

```json title="Method Syntax"
select [ thing ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The thing (Table or Record ID) to select
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "select",
    "params": [ "person" ]
}
```

```json title="Response"
{
    "id": 1,
    "result": [
        {
            "id": "person:8s0j0bbm3ngrd5c9bx53",
            "name": "John"
        }
    ]
}
```

<br />

## `signin`

This method allows you to sign in as a root, namespace, or database user, or with a record access method.

The object returned will contain a `token` property and an optional `refresh` property.

```json title="Method Syntax"
signin [ NS, DB, AC, ... ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>NS</code>
                <label label="required"></label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The namespace to sign in to. Only required for `DB & RECORD` authentication
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>DB</code>
                <label label="required"></label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The database to sign in to. Only required for `RECORD` authentication
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>AC</code>
                <label label="required"></label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the access method. Only required for `RECORD` authentication
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>user</code>
                <label label="required">REQUIRED FOR ROOT, NS & DB</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
            	The username of the database user. Only required for `ROOT, NS & DB` authentication
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>pass</code>
                <label label="required">REQUIRED FOR ROOT, NS & DB</label>
            </td>
            <td colspan="2" scope="row" data-label="Description">
            	The password of the database user. Only required for `ROOT, NS & DB` authentication
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>...</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies any variables to pass to the `SIGNIN` query. Only relevant for `RECORD` authentication
            </td>
        </tr>
    </tbody>
</table>

### Example with root user

```json title="Request"
{
    "id": 1,
    "method": "signin",
    "params": [
        {
            "user": "tobie",
            "pass": "3xtr3m3ly-s3cur3-p@ssw0rd"
        }
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": null
}
```

### Example with record user

```json title="Request"
{
    "id": 1,
    "method": "signin",
    "params": [
        {
            "NS": "surrealdb",
            "DB": "docs",
            "AC": "commenter",

            "username": "johndoe",
            "password": "SuperStrongPassword!"
        }
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJTdXJyZWFsREIiLCJpYXQiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMiwiZXhwIjoxODM2NDM5MDIyLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJTQyI6InVzZXIiLCJJRCI6InVzZXI6dG9iaWUifQ.N22Gp9ze0rdR06McGj1G-h2vu6a6n9IVqUbMFJlOxxA"
}
```

<br />

## `signup`

This method allows you to sign a user up using the `SIGNUP` query defined in a record access method.

The object returned will contain an optional `token` property and an optional `refresh` property.

```json title="Method Syntax"
signup [ NS, DB, AC, ... ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>NS</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the namespace of the record access method
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>DB</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the database of the record access method
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>AC</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the access method
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>...</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies any variables used by the SIGNUP query of the record access method
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "signup",
    "params": [
        {
            "NS": "surrealdb",
            "DB": "docs",
            "AC": "commenter",

            "username": "johndoe",
            "password": "SuperStrongPassword!"
        }
    ]
}
```

```json title="Response"
{
  "id": 1,
  "result": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJTdXJyZWFsREIiLCJpYXQiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMiwiZXhwIjoxODM2NDM5MDIyLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJTQyI6InVzZXIiLCJJRCI6InVzZXI6dG9iaWUifQ.N22Gp9ze0rdR06McGj1G-h2vu6a6n9IVqUbMFJlOxxA"
}
```

<br />

## `unset` <label label="websocket only" /> {#unset}

This method removes a variable from the current connection.

```json title="Method Syntax"
unset [ name ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>name</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The name of the variable without a prefixed $ character
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "unset",
    "params": [ "website" ]
}
```

```json title="Response"
{
    "id": 1,
    "result": null
}
```

<br />

## `update`

This method replaces either all records in a table or a single record with specified data.

```json title="Method Syntax"
update [ thing, data ]
```

> [!NOTE]
> This function replaces the current document / record data with the specified data if that document / record has already been created. If no document has been created this will return an empty array. Also, if no replacement data is passed it will simply trigger an update.

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The thing (Table or Record ID) to update
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>data</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The content of the record
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "update",
    "params": [
        "person:8s0j0bbm3ngrd5c9bx53",
        {
            "name": "John Doe"
        }
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": {
        "id": "person:8s0j0bbm3ngrd5c9bx53",
        "name": "John Doe"
    }
}
```

<br />

## `upsert`

```json title="Method Syntax"
upsert [ thing, data ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>thing</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The thing (Table or Record ID) to upsert
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Parameter">
                <code>data</code>
               <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
            The content of the record
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```json title="Request"
{
    "id": 1,
    "method": "upsert",
    "params": [
        "person:12s0j0bbm3ngrd5c9bx53",
        {
            "name": "John Doe",
            "job": "Software developer",
        }
    ]
}
```

```json title="Response"
{
    "id": 1,
    "result": {
        "id": "person:12s0j0bbm3ngrd5c9bx53",
        "name": "John Doe",
        "job": "Software developer"
    }
}
```

<br />

## `use`

This method specifies or unsets the namespace and/or database for the current connection.

```json title="Method Syntax"
use [ ns, db ]
```

### Parameters
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Parameter</th>
            <th colspan="2" scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Function">
                <code>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="Function">
                <code>DB</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Sets the selected Database for queries
            </td>
        </tr>
    </tbody>
</table>

### Accepted values

For either the namespace or database, a string will change the value, `null` will unset the value, and `none` will cause the value to not be affected.

### Example usage

```json title="Request"
{
    "id": 1,
    "method": "use",
    "params": [ "surrealdb", "docs" ]
}
```

```json title="Response"
{
    "id": 1,
    "result": null
}
```

```surql title="Example Combinations"
[none, none]     -- Won't change ns or db
["test", none]   -- Change ns to test
[none, "test"]   -- Change db to test
["test", "test"] -- Change ns and db to test

[none, null]     -- Will only unset the database
[null, none]     -- Will throw an error, you cannot unset only the database
[null, null]     -- Will unset both ns and db
["test", null]   -- Change ns to test and unset db
```

<br />

## `version`

This method returns version information about the database/server.

```json title="Method Syntax"
version
```

### Parameters

This method does not accept any parameters.

### Example usage

```json title="Request"
{
    "id": 1,
    "method": "version"
}
```

```json title="Response"
{
    "id": 1,
    "result": {
        "version": "3.2.0",
        "build": "abc123",
        "timestamp": "2024-09-15T12:34:56Z"
    }
}
```

### Notes

- **Parameters:** Providing any parameters will result in an `InvalidParams` error.
- **Result Fields:**
  - `version`: The version number of the database/server.
  - `build`: The build identifier.
  - `timestamp`: The timestamp when the version was built or released.

> [!NOTE]
> The actual values in the response will depend on your specific database/server instance.

<br />

---

Source: https://surrealdb.com/docs/reference/rust

# Rust SDK

The official SurrealDB SDK for Rust. Use SurrealDB from client-side and server-side applications, systems, APIs, embedded systems, and IoT devices.

The SurrealDB SDK for Rust enables you to interact with SurrealDB from client-side, server-side applications, systems, APIs, embedded systems, and IoT devices. The Rust SDK has support for robust error handling and type-safe operations, using an asynchronous API for efficient concurrent database interactions. You can use the Rust SDK to interact with your SurrealDB database instances, or to run SurrealDB as an embedded database within your Rust 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.

> [!IMPORTANT]
> The SDK requires Rust version `1.89` or greater, and is available as a [crate](https://crates.io/crates/surrealdb).

> [!NOTE]
> The latest version of the SDK is `3.2.4`.
> The SDK works with SurrealDB versions `v2.0.0` and later, including the current release, `v3.2.4`.

## Getting started

- [Getting started guide](/docs/languages/rust.md) - Connect to SurrealDB and run your first queries.

## Learn

- [Concepts](/docs/reference/rust/concepts.md) - Guides for connecting, authenticating, querying, and working with data.

- [API Reference](/docs/reference/rust/methods.md) - Complete reference for the SDK's methods, types, and errors.

- [Error handling](/docs/reference/rust/concepts/error-handling.md) - Match on error kinds, inspect structured details, and follow the cause chain.

## Frameworks

Each framework has a worked example; the [frameworks overview](/docs/reference/rust/frameworks.md) lists them together.

- [Actix](/docs/reference/rust/frameworks/actix.md)

- [Axum](/docs/reference/rust/frameworks/axum.md)

- [Egui](/docs/reference/rust/frameworks/egui.md)

- [Rocket](/docs/reference/rust/frameworks/rocket.md)

## Contributing

To contribute to the SDK code, submit an Issue or Pull Request in the [Rust SDK](https://github.com/surrealdb/surrealdb/tree/main/surrealdb) 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/tree/main/surrealdb)
- [Crates.io package](https://crates.io/crates/surrealdb)
- [Docs.rs documentation](https://docs.rs/surrealdb/latest/surrealdb/)

---

Source: https://surrealdb.com/docs/reference/rust/concepts

# SDK Concepts

The SurrealDB SDK for Rust enables simple and advanced querying of a remote or embedded database.

This section covers the core concepts of the SurrealDB SDK for Rust: connecting to an instance, managing authentication, and working with data and types.

- [Fetching linked records](/docs/reference/rust/concepts/fetch.md)
- [Manual transactions](/docs/reference/rust/concepts/transaction.md)
- [Flexible typing](/docs/reference/rust/concepts/flexible-typing.md)
- [Live queries](/docs/reference/rust/concepts/live.md)
- [Authenticating users](/docs/reference/rust/concepts/authenticating-users.md)
- [Improving performance with concurrency](/docs/reference/rust/concepts/concurrency.md)
- [Vector embeddings](/docs/reference/rust/concepts/vector-embeddings.md)
- [Working with types](/docs/reference/rust/concepts/working-with-types.md)
- [SurrealValue attributes](/docs/reference/rust/concepts/surrealvalue-attributes.md)
- [Multi-tenancy](/docs/reference/rust/concepts/multi-tenancy.md)
- [Error handling](/docs/reference/rust/concepts/error-handling.md)

---

Source: https://surrealdb.com/docs/reference/rust/concepts/authenticating-users

# Authenticating users

The Rust SDK for SurrealDB supports a number of methods for authenticating users and securing the database.

**3.x**

The [`.signup()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signup) and [`.signin()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signin) methods are used for both system users (users created with a [DEFINE USER](/docs/reference/query-language/statements/define/user.md) statement) and record users (users created with a [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md) statement). These two methods take any type that implements the [`Credentials`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/trait.Credentials.html) trait, namely the structs [`Root`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Root.html), [`Namespace`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Namespace.html), [`Database`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Database.html), and [`Record`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Record.html).

```rust
pub struct Root {
    pub username: String,
    pub password: String,
}

pub struct Namespace {
    pub namespace: String,
    pub username: String,
    pub password: String,
}

pub struct Database {
    pub namespace: String,
    pub database: String,
    pub username: String,
    pub password: String,
}

pub struct Record<P: SurrealValue> {
    pub namespace: String,
    pub database: String,
    pub access: String,
    pub params: P,
}
```

The `access` and `params` fields of the `Record` struct are the only one of the four that requires extra explanation.

The `access` field comes from the name of the [access method](/docs/reference/query-language/statements/define/access/record.md) used to create a record user. In our case, we will use an access method called 'account'. An access method will generally create a record on signup, and select a record on signin, which is the case with our access method as well.

```surql
DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h;
```

Since the `params` of a `Record` struct used to sign up and sign can be any type that implements `SurrealValue`, we can put our own struct together and pass them into these methods. On signup, the database will CREATE a `user` record that holds the name and password, and on signin it will SELECT the user that has a matching name and password.

```rust
#[derive(SurrealValue)]
struct Params {
    name: String,
    pass: String,
}

db.signup(Record {
    access: "account".to_string(),
    namespace: "namespace".to_string(),
    database: "database".to_string(),
    params: Params {
        name: "username".to_string(),
        pass: "Str0ngpAASSword!".to_string(),
    },
})
```

All of the definitions for this example are as follows.

```surql
DEFINE TABLE person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD name ON TABLE person TYPE string;
DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
DEFINE ACCESS account ON DATABASE TYPE RECORD
SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
DURATION FOR TOKEN 15m, FOR SESSION 12h
```

Whenever a record user is signed into the database, the `$auth` parameter will be populated with its record ID. The `DEFINE TABLE` statement then uses this to give the record user permissions to `CREATE` and `SELECT` any `person` record, but to `UPDATE` and `DELETE` only when the `created_by` field matches the ID in `$auth`. This `created_by` field is automatically set and is `READONLY`. It will show up as `None` when a system user is signed in.

The `.signup()` and `.signin()` methods return an `AccessToken`, which can be printed out if needed using the `.into_insecure_token()` or `.as_insecure_token()` methods. As the method names imply, great care should be taken care with them. The `AccessToken` struct redacts the token so printing out a `AccessToken` on its own will not display it.

The sample code below uses a crate called [`faker_rand`](https://docs.rs/faker_rand/latest/faker_rand/index.html) to generate a random name and password to make it easy to rerun the code and experiment with the behaviour.

```rust
async fn make_new_user(db: &Surreal<Client>) -> Result<RecordUser, Error> {
    let name = rand::random::<FirstName>().to_string();
    let pass = rand::random::<FirstName>().to_string();
    println!("Signing in as user {name} and password {pass}");
    let jwt = db
        .signup(Record {
            access: "account".to_string(),
            namespace: "namespace".to_string(),
            database: "database".to_string(),
            params: Params {
                name: name,
                pass: pass,
            },
        })
        .await?
        .into_insecure_token();
    println!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --namespace namespace --database database --pretty --token \"{jwt}\"\n");
    Ok(RecordUser { name, pass })
}

async fn get_new_token(db: &Surreal<Client>, user: &RecordUser) -> Result<(), Error> {
    let jwt = db
        .signin(Record {
            access: "account".to_string(),
            namespace: "namespace".to_string(),
            database: "database".to_string(),
            params: Params {
                name: user.name,
                pass: user.pass,
            },
        })
        .await?
        .into_insecure_token();
    println!("New token! Sign in with surreal sql --namespace namespace --database database --pretty --token \"{jwt}\"\n");
    Ok(())
}
```

The entire code is as follows. Inside `main()`, the client first logs in as a root user to define the schema and create a `person` record. It then signs up as a new record user, and then signs in again to demonstrate that a new token is returned each time this method is called (as long as the name and password match). The record user then creates a `person` record - which it is permitted to do - and then tries to delete all of the `person` records in the database. However, the `person` record created by the root user will remain untouched.

```rust
use fake::{Fake, faker::name::en::FirstName};
use surrealdb::{
    Error, Surreal,
    engine::any::{Any, connect},
    opt::auth::{Record, Root},
    types::{RecordId, SurrealValue},
};

#[derive(Debug, SurrealValue)]
struct Person {
    name: String,
    id: RecordId,
    created_by: Option<RecordId>,
}

#[derive(SurrealValue)]
struct Params {
    name: String,
    pass: String,
}

#[derive(SurrealValue)]
struct RecordUser {
    name: String,
    pass: String,
}

async fn make_new_user(db: &Surreal<Any>) -> Result<RecordUser, Error> {
    let name: String = FirstName().fake();
    let pass: String = FirstName().fake();
    println!("Signing in as user {name} and password {pass}");
    let jwt = db
        .signup(Record {
            access: "account".to_string(),
            namespace: "main".to_string(),
            database: "main".to_string(),
            params: Params {
                name: name.clone(),
                pass: pass.clone(),
            },
        })
        .await?
        .access
        .into_insecure_token();
    println!(
        "New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --namespace namespace --database database --pretty --token \"{jwt}\"\n"
    );
    Ok(RecordUser { name, pass })
}

async fn get_new_token(db: &Surreal<Any>, user: RecordUser) -> Result<(), Error> {
    let jwt = db
        .signin(Record {
            access: "account".to_string(),
            namespace: "main".to_string(),
            database: "main".to_string(),
            params: Params {
                name: user.name,
                pass: user.pass,
            },
        })
        .await?;
    println!(
        "New token! Sign in with surreal sql --pretty --token \"{}\"\n",
        jwt.access.into_insecure_token()
    );
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db = connect("ws://localhost:8000").await?;

    db.use_ns("main").use_db("main").await?;

    db.signin(Root {
        username: "root".into(),
        password: "secret".into(),
    })
    .await?;

    db.query(
        "DEFINE TABLE person SCHEMALESS
        PERMISSIONS FOR
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD name ON TABLE person TYPE string;
    DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;

    db.query("CREATE person SET name = 'Created by root'")
        .await?;

    let user = make_new_user(&db).await?;

    get_new_token(&db, user).await?;

    db.query("CREATE person SET name = 'Created by record user'")
        .await?;

    println!(
        "Two `person` records: {:?}\n",
        db.select::<Vec<Person>>("person").await?
    );

    db.query("DELETE person").await?;

    println!(
        "`person` created by root is still there: {:?}\n",
        db.select::<Vec<Person>>("person").await?
    );

    Ok(())
}
```

Example output:

```text
Signing in as user Emmett and password Izaiah
New user created!

Name: Emmett
Password: Izaiah
Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MzA4NjQ2NDAsIm5iZiI6MTczMDg2NDY0MCwiZXhwIjoxNzMwODY1NTQwLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiIwNmYwOGFhZi0zYzIyLTQ5N2UtYWRmNC0zNDMxMzA5YWYxOGEiLCJOUyI6Im5hbWVzcGFjZSIsIkRCIjoiZGF0YWJhc2UiLCJBQyI6ImFjY291bnQiLCJJRCI6InVzZXI6YW92bHN0dzBvN2R1Y2J4ZWpqZWsifQ.tHCVlubg3G2j05-LsEaE6jRHMwrBtccJcR6uC9Z6Lo-egrYlBybEEfOZh020OxWxKvUt8eA92-6TjwBvSpN5KA

To log in, use this command:

surreal sql --pretty --token "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MzA4NjQ2NDAsIm5iZiI6MTczMDg2NDY0MCwiZXhwIjoxNzMwODY1NTQwLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiIwNmYwOGFhZi0zYzIyLTQ5N2UtYWRmNC0zNDMxMzA5YWYxOGEiLCJOUyI6Im5hbWVzcGFjZSIsIkRCIjoiZGF0YWJhc2UiLCJBQyI6ImFjY291bnQiLCJJRCI6InVzZXI6YW92bHN0dzBvN2R1Y2J4ZWpqZWsifQ.tHCVlubg3G2j05-LsEaE6jRHMwrBtccJcR6uC9Z6Lo-egrYlBybEEfOZh020OxWxKvUt8eA92-6TjwBvSpN5KA"

New token! Sign in with surreal sql --pretty --token "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MzA4NjQ2NDAsIm5iZiI6MTczMDg2NDY0MCwiZXhwIjoxNzMwODY1NTQwLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiIyZjExZTQzZi04ODg1LTRmNzAtOGI2Zi0zNGZmZGZlZWY4MDUiLCJOUyI6Im5hbWVzcGFjZSIsIkRCIjoiZGF0YWJhc2UiLCJBQyI6ImFjY291bnQiLCJJRCI6InVzZXI6YW92bHN0dzBvN2R1Y2J4ZWpqZWsifQ.0GWwMiAjn5kKUoNAw2TxAdVLhWIHeJsVWvlAzw1QZ91qdIhkazygdG5uFl5DHVmmoYC-cLo-ko27jiRCid5xDg"

Two `person` records: [Person { name: "Created by record user", id: RecordId { table: "person", key: String("i2sv6bqk0mso0udgzmdx") }, created_by: Some(RecordId { table: "user", key: String("aovlstw0o7ducbxejjek") }) }, Person { name: "Created by root", id: RecordId { table: "person", key: String("u6mry7agmsmtbwl94yui") }, created_by: None }]

`person` created by root is still there: [Person { name: "Created by root", id: RecordId { table: "person", key: String("u6mry7agmsmtbwl94yui") }, created_by: None }]
```

## See also

Each of the crates featured in the Rust SDK also use the schema above. Three of them (Actix, Axum, Rocket) are web servers, while Egui is a visual UI. See the mini tutorials for each of these crates here:

* [Actix](/docs/reference/rust/frameworks/actix.md)
* [Axum](/docs/reference/rust/frameworks/axum.md)
* [Rocket](/docs/reference/rust/frameworks/rocket.md)
* [Egui](/docs/reference/rust/frameworks/egui.md)

Learn more about authentication in SurrealDB in our [security best practices](/docs/learn/security/best-practices/security-best-practices.md#authentication) documentation and in the [security](/docs/learn/security/authentication/users.md#expiration) section of the SurrealDB documentation.

**2.x**

The [`.signup()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signup) and [`.signin()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signin) methods are used for both system users (users created with a [DEFINE USER](/docs/reference/query-language/statements/define/user.md) statement) and record users (users created with a [DEFINE ACCESS](/docs/reference/query-language/statements/define/access.md) statement). These two methods take any type that implements the [`Credentials`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/trait.Credentials.html) trait, namely the structs [`Root`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Root.html), [`Namespace`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Namespace.html), [`Database`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Database.html), and [`Record`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Record.html).

```rust
pub struct Root<'a> {
    pub username: &'a str,
    pub password: &'a str,
}

pub struct Namespace<'a> {
    pub namespace: &'a str,
    pub username: &'a str,
    pub password: &'a str,
}

pub struct Database<'a> {
    pub namespace: &'a str,
    pub database: &'a str,
    pub username: &'a str,
    pub password: &'a str,
}

// P: any type that implements Serialize
pub struct Record<'a, P> {
    pub namespace: &'a str,
    pub database: &'a str,
    pub access: &'a str,
    pub params: P,
}
```

The `access` and `params` fields of the `Record` struct are the only one of the four that requires extra explanation.

The `access` field comes from the name of the [access method](/docs/reference/query-language/statements/define/access/record.md) used to create a record user. In our case, we will use an access method called 'account'. An access method will generally create a record on signup, and select a record on signin, which is the case with our access method as well.

```surql
DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h;
```

Since the `params` of a `Record` struct used to sign up and sign can be any type that implements `Serialize`, we can put our own struct together and pass them into these methods. On signup, the database will CREATE a `user` record that holds the name and password, and on signin it will SELECT the user that has a matching name and password.

```rust
#[derive(Serialize, Deserialize)]
struct Params<'a> {
    name: &'a str,
    pass: &'a str,
}

db.signup(Record {
    access: "account",
    namespace: "namespace",
    database: "database",
    params: Params {
        name: "username",
        pass: "Str0ngpAASSword!",
    },
})
```

All of the definitions for this example are as follows.

```surql
DEFINE TABLE person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD name ON TABLE person TYPE string;
DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
DEFINE ACCESS account ON DATABASE TYPE RECORD
SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
DURATION FOR TOKEN 15m, FOR SESSION 12h
```

Whenever a record user is signed into the database, the `$auth` parameter will be populated with its record ID. The `DEFINE TABLE` statement then uses this to give the record user permissions to `CREATE` and `SELECT` any `person` record, but to `UPDATE` and `DELETE` only when the `created_by` field matches the ID in `$auth`. This `created_by` field is automatically set and is `READONLY`. It will show up as `None` when a system user is signed in.

The `.signup()` and `.signin()` methods return a [`Jwt`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Jwt.html), which can be printed out if needed using the `.into_insecure_token()` or `.as_insecure_token()` methods. As the method names imply, great care should be taken care with them. The `Jwt` struct redacts the token so printing out a `Jwt` on its own will not display it.

The code uses a crate called [`faker_rand`](https://docs.rs/faker_rand/latest/faker_rand/index.html) to generate a random name and password to make it easy to rerun the code and experiment with the behaviour.

```rust
async fn make_new_user(db: &Surreal<Client>) -> Result<RecordUser, Error> {
    let name = rand::random::<FirstName>().to_string();
    let pass = rand::random::<FirstName>().to_string();
    println!("Signing in as user {name} and password {pass}");
    let jwt = db
        .signup(Record {
            access: "account",
            namespace: "namespace",
            database: "database",
            params: Params {
                name: &name,
                pass: &pass,
            },
        })
        .await?
        .into_insecure_token();
    println!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --namespace namespace --database database --pretty --token \"{jwt}\"\n");
    Ok(RecordUser { name, pass })
}

async fn get_new_token(db: &Surreal<Client>, user: &RecordUser) -> Result<(), Error> {
    let jwt = db
        .signin(Record {
            access: "account",
            namespace: "namespace",
            database: "database",
            params: Params {
                name: &user.name,
                pass: &user.pass,
            },
        })
        .await?
        .into_insecure_token();
    println!("New token! Sign in with surreal sql --namespace namespace --database database --pretty --token \"{jwt}\"\n");
    Ok(())
}
```

The entire code is as follows. Inside `main()`, the client first logs in as a root user to define the schema and create a `person` record. It then signs up as a new record user, and then signs in again to demonstrate that a new token is returned each time this method is called (as long as the name and password match). The record user then creates a `person` record - which it is permitted to do - and then tries to delete all of the `person` records in the database. However, the `person` record created by the root user will remain untouched.

```rust
use surrealdb::engine::remote::ws::Client;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

use faker_rand::en_us::names::FirstName;
use surrealdb::opt::auth::Record;

use serde::{Deserialize, Serialize};
use surrealdb::{Error, RecordId};

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    id: RecordId,
    created_by: Option<RecordId>,
}

#[derive(Serialize, Deserialize)]
struct Params<'a> {
    name: &'a str,
    pass: &'a str,
}

#[derive(Serialize, Deserialize)]
struct RecordUser {
    name: String,
    pass: String,
}

async fn make_new_user(db: &Surreal<Client>) -> Result<RecordUser, Error> {
    let name = rand::random::<FirstName>().to_string();
    let pass = rand::random::<FirstName>().to_string();
    println!("Signing in as user {name} and password {pass}");
    let jwt = db
        .signup(Record {
            access: "account",
            namespace: "namespace",
            database: "database",
            params: Params {
                name: &name,
                pass: &pass,
            },
        })
        .await?
        .into_insecure_token();
    println!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --namespace namespace --database database --pretty --token \"{jwt}\"\n");
    Ok(RecordUser { name, pass })
}

async fn get_new_token(db: &Surreal<Client>, user: &RecordUser) -> Result<(), Error> {
    let jwt = db
        .signin(Record {
            access: "account",
            namespace: "namespace",
            database: "database",
            params: Params {
                name: &user.name,
                pass: &user.pass,
            },
        })
        .await?
        .into_insecure_token();
    println!("New token! Sign in with surreal sql --namespace namespace --database database --pretty --token \"{jwt}\"\n");
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("namespace").use_db("database").await?;

    db.query(
        "DEFINE TABLE person SCHEMALESS
        PERMISSIONS FOR 
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD name ON TABLE person TYPE string;
    DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;

    db.query("CREATE person SET name = 'Created by root'")
        .await?;

    let user = make_new_user(&db).await?;

    get_new_token(&db, &user).await?;

    db.query("CREATE person SET name = 'Created by record user'")
        .await?;

    println!(
        "Two `person` records: {:?}\n",
        db.select::<Vec<Person>>("person").await?
    );

    db.query("DELETE person").await?;

    println!(
        "`person` created by root is still there: {:?}\n",
        db.select::<Vec<Person>>("person").await?
    );

    Ok(())
}
```

Example output:

```text
Signing in as user Emmett and password Izaiah
New user created!

Name: Emmett
Password: Izaiah
Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MzA4NjQ2NDAsIm5iZiI6MTczMDg2NDY0MCwiZXhwIjoxNzMwODY1NTQwLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiIwNmYwOGFhZi0zYzIyLTQ5N2UtYWRmNC0zNDMxMzA5YWYxOGEiLCJOUyI6Im5hbWVzcGFjZSIsIkRCIjoiZGF0YWJhc2UiLCJBQyI6ImFjY291bnQiLCJJRCI6InVzZXI6YW92bHN0dzBvN2R1Y2J4ZWpqZWsifQ.tHCVlubg3G2j05-LsEaE6jRHMwrBtccJcR6uC9Z6Lo-egrYlBybEEfOZh020OxWxKvUt8eA92-6TjwBvSpN5KA

To log in, use this command:

surreal sql --namespace namespace --database database --pretty --token "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MzA4NjQ2NDAsIm5iZiI6MTczMDg2NDY0MCwiZXhwIjoxNzMwODY1NTQwLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiIwNmYwOGFhZi0zYzIyLTQ5N2UtYWRmNC0zNDMxMzA5YWYxOGEiLCJOUyI6Im5hbWVzcGFjZSIsIkRCIjoiZGF0YWJhc2UiLCJBQyI6ImFjY291bnQiLCJJRCI6InVzZXI6YW92bHN0dzBvN2R1Y2J4ZWpqZWsifQ.tHCVlubg3G2j05-LsEaE6jRHMwrBtccJcR6uC9Z6Lo-egrYlBybEEfOZh020OxWxKvUt8eA92-6TjwBvSpN5KA"

New token! Sign in with surreal sql --namespace namespace --database database --pretty --token "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MzA4NjQ2NDAsIm5iZiI6MTczMDg2NDY0MCwiZXhwIjoxNzMwODY1NTQwLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiIyZjExZTQzZi04ODg1LTRmNzAtOGI2Zi0zNGZmZGZlZWY4MDUiLCJOUyI6Im5hbWVzcGFjZSIsIkRCIjoiZGF0YWJhc2UiLCJBQyI6ImFjY291bnQiLCJJRCI6InVzZXI6YW92bHN0dzBvN2R1Y2J4ZWpqZWsifQ.0GWwMiAjn5kKUoNAw2TxAdVLhWIHeJsVWvlAzw1QZ91qdIhkazygdG5uFl5DHVmmoYC-cLo-ko27jiRCid5xDg"

Two `person` records: [Person { name: "Created by record user", id: RecordId { table: "person", key: String("i2sv6bqk0mso0udgzmdx") }, created_by: Some(RecordId { table: "user", key: String("aovlstw0o7ducbxejjek") }) }, Person { name: "Created by root", id: RecordId { table: "person", key: String("u6mry7agmsmtbwl94yui") }, created_by: None }]

`person` created by root is still there: [Person { name: "Created by root", id: RecordId { table: "person", key: String("u6mry7agmsmtbwl94yui") }, created_by: None }]
```

## See also

Each of the crates featured in the Rust SDK also use the schema above. Three of them (Actix, Axum, Rocket) are web servers, while Egui is a visual UI. See the mini tutorials for each of these crates here:

* [Actix](/docs/reference/rust/frameworks/actix.md)
* [Axum](/docs/reference/rust/frameworks/axum.md)
* [Rocket](/docs/reference/rust/frameworks/rocket.md)
* [Egui](/docs/reference/rust/frameworks/egui.md)

Learn more about authentication in SurrealDB in our [security best practices](/docs/learn/security/best-practices/security-best-practices.md#authentication) documentation and in the [security](/docs/learn/security/authentication/users.md#expiration) section of the SurrealDB documentation.

---

Source: https://surrealdb.com/docs/reference/rust/concepts/concurrency

# Concurrency

Multiple threads or asynchronous tasks can be used to speed up queries to a SurrealDB database

While the Rust SDK for SurrealDB uses the tokio async runtime, the operation of the database itself will only take place concurrently if the code itself uses concurrency. The following example shows how to do this and a comparison of the performance between synchronous and asynchronous usage.

## Getting started

Start a running database using the following command:

```bash
surreal start --user root --pass secret 
```

Then use the `cargo add` command to add the `surrealdb` and `tokio` crates.

## The code

The SurrealDB client by nature has no maximum capacity for the number of channels that can be made to perform queries. To change this setting, the [`.with_capacity()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Connect.html#method.with_capacity) method can be used. As tokio's documentation notes, this method is useful if your client is running so many queries concurrently that the machine used to execute them is running out of memory.

> This is used to set bounds of the channels used internally as well set the capacity of the HashMap used for routing responses in case of the WebSocket client. Setting this capacity to 0 (the default) means that unbounded channels will be used. If your queries per second are so high that the client is running out of memory, it might be helpful to set this to a number that works best for you.

To experiment with this, we will create three clients: one with no maximum capacity, a second with a maximum capacity of 1, and a third with a maximum capacity of 1000. To avoid any code duplication, we'll use an enum to set these clients up.

```rust
enum DbType {
    Standard,
    With1,
    With1000,
}

impl DbType {
    async fn generate(self) -> Result<Surreal<Client>, Error> {
        let db = match self {
            DbType::Standard => Surreal::new::<Ws>("localhost:8000").await,
            DbType::With1 => Surreal::new::<Ws>("localhost:8000").with_capacity(1).await,
            DbType::With1000 => {
                Surreal::new::<Ws>("localhost:8000")
                    .with_capacity(1000)
                    .await
            }
        }?;
        db.use_ns("main").use_db("main").await?;
        db.signin(Root {
            username: "root",
            password: "secret",
        })
        .await?;
        Ok(db)
    }
}
```

Each of these clients will be put into a `for` loop 50000 times, in which they will simply return the index number of the current iteration of the loop. Note that the [`.take()`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html#method.take) method returns a query result as anything that can be deserialised, including primitive types like `usize`. It will also take a `test_num` field so that we can see which test is running as the index numbers fly by on the terminal.

```rust
async fn select_index(db: &Surreal<Client>, idx: usize, test_num: &'static str) {
    let mut result = db
        .query("SELECT * FROM $idx")
        .bind(("idx", idx))
        .await
        .unwrap();

    let db_idx: Option<usize> = result.take(0).unwrap();
    if let Some(db_idx) = db_idx {
        println!("{test_num} - {idx}: {db_idx}");
    }
}
```

The first test inside main will be done synchronously, returning the `Duration` taken by the test once it is done.

```rust
let start = std::time::Instant::now();
for idx in 0..=MAX {
	select_index(&db, idx).await;
}
let res_1 = format!("Regular DB: {:?}", Instant::now() - start);
```

The other three tests will all be done asynchronously, so we'll put them into their own function. Each one will spawn a tokio task which will execute and return the result whenever it is finished. Each task will be put into a `Vec` of `JoinHandle`s that will be awaited on at the end to ensure that we don't exit the function before they have finished.

```rust
async fn async_test(db: Arc<Surreal<Client>>, test_num: &'static str) -> std::time::Duration {
    let start = std::time::Instant::now();
    let mut handles = vec![];
    for idx in 0..=MAX {
        let db = db.clone();
        handles.push(tokio::spawn(async move {
            select_index(&db, idx, test_num).await;
        }));
    }
    for h in handles {
        h.await.unwrap();
    }
    Instant::now() - start
}
```

We will then make a `String` variable from the output of each test, and print them out at the end when everything has run.

The entire code is as follows:

```rust
use std::sync::Arc;
use std::time::Instant;
use surrealdb::engine::remote::ws::{Client, Ws};
use surrealdb::opt::auth::Root;
use surrealdb::{Error, Surreal};

const MAX: usize = 50_000;

enum DbType {
    Standard,
    With1,
    With1000,
}

impl DbType {
    async fn generate(self) -> Result<Surreal<Client>, Error> {
        let db = match self {
            DbType::Standard => Surreal::new::<Ws>("localhost:8000").await,
            DbType::With1 => Surreal::new::<Ws>("localhost:8000").with_capacity(1).await,
            DbType::With1000 => {
                Surreal::new::<Ws>("localhost:8000")
                    .with_capacity(1000)
                    .await
            }
        }?;
        db.use_ns("main").use_db("main").await?;
        db.signin(Root {
            username: "root",
            password: "secret",
        })
        .await?;
        Ok(db)
    }
}

async fn select_index(db: &Surreal<Client>, idx: usize, test_num: &'static str) {
    let mut result = db
        .query("SELECT * FROM $idx")
        .bind(("idx", idx))
        .await
        .unwrap();

    let db_idx: Option<usize> = result.take(0).unwrap();
    if let Some(db_idx) = db_idx {
        println!("{test_num} - {idx}: {db_idx}");
    }
}

async fn async_test(db: Arc<Surreal<Client>>, test_num: &'static str) -> std::time::Duration {
    let start = std::time::Instant::now();
    let mut handles = vec![];
    for idx in 0..=MAX {
        let db = db.clone();
        handles.push(tokio::spawn(async move {
            select_index(&db, idx, test_num).await;
        }));
    }
    for h in handles {
        h.await.unwrap();
    }
    Instant::now() - start
}

#[tokio::main]
async fn main() -> Result<(), Error> {

    let db_standard = Arc::new(DbType::Standard.generate().await?);
    let db_with_1 = Arc::new(DbType::With1.generate().await?);
    let db_with_1000 = Arc::new(DbType::With1000.generate().await?);

    let start = std::time::Instant::now();
    for idx in 0..=MAX {
        select_index(&db_standard, idx, "Test1").await;
    }

    let res_1 = format!("Regular DB: {:?}", Instant::now() - start);
    let res_2 = format!("Async with capacity 1: {:?}", async_test(db_with_1, "Test2").await);
    let res_3 = format!(
        "Async with capacity 1000: {:?}",
        async_test(db_with_1000, "Test3").await
    );
    let res_4 = format!(
        "Async with unbounded capacity: {:?}",
        async_test(db_standard, "Test4").await
    );

    println!("{res_1}\n{res_2}\n{res_3}\n{res_4}");

    Ok(())
}
```

Running the code, you should see the following:

1) The first test runs one index and one query at a time, taking by far the longest time.
2) The second test will run much faster. Despite only having a capacity of 1, it still runs concurrently and does not need to wait for the output of the previous query to send in its own.
3) The third test with a capacity of 1000 will run even faster,
4) The last test using the first unbounded database should run fastest of all. If running the test on a particularly slow computer, however, you may see a slowdown compared to the other two async tests if the computer's memory capacity is reached during the test.

A sample of the output at the end:

```text
Regular DB: 5.244320833s
Async with capacity 1: 1.523926416s
Async with capacity 1000: 1.441300833s
Async with unbounded capacity: 1.183820584s
```

## Using a channel instead of a JoinHandle

Besides the classic method of using a `JoinHandle` for each thread or task to wait until all have completed their operation, a [channel](https://docs.rs/tokio/latest/tokio/sync/mpsc/fn.channel.html) can also be used. A channel can be used in the following way:

* Create a channel with a buffer of 1 (the minimum size), as the channel will not be used to actually send data,
* Clone the sender and send it into each iteration of the `for` loop,
* Drop the cloned sender once the database query is done,
* Drop the original sender after the `for` loop,
* Call [`.recv()`](https://docs.rs/tokio/latest/tokio/sync/mpsc/struct.Receiver.html#method.recv) on the receiver at the very end.

As tokio's documentation shows, the `.recv()` method will cause the receiver to sleep as it waits for each task to complete, and at the end it will close once the last sender has been dropped.

```text
This method returns None if the channel has been closed and there are no remaining messages in the channel’s buffer. This indicates that no further values can ever be received from this Receiver. The channel is closed when all senders have been dropped, or when close is called.

If there are no messages in the channel’s buffer, but the channel has not yet been closed, this method will sleep until a message is sent or the channel is closed.
```

Here is what the `async_test()` function looks like using this method.

```rust
async fn async_test(db: Arc<Surreal<Client>>, test_num: &'static str) -> std::time::Duration {
    let (tx, mut rx) = mpsc::channel::<()>(1);
    let start = std::time::Instant::now();

    for idx in 0..=MAX {
        let sender = tx.clone();
        let db = db.clone();
        tokio::spawn(async move {
            select_index(&db, idx, test_num).await;
            drop(sender);
        });
    }
    drop(tx);

	rx.recv().await;

    Instant::now() - start
}
```

---

Source: https://surrealdb.com/docs/reference/rust/concepts/error-handling

# Error handling

Every fallible method in the Rust SDK returns a surrealdb::Error, which carries a kind you can match on along with structured details and a cause chain.

Every fallible method in the Rust SDK returns `surrealdb::Result<T>`, an alias for `Result<T, surrealdb::Error>`. That error type is the same one the server sends over the wire, so a failure raised inside the database and a failure raised by the SDK arrive in the same shape.

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.

Be sure to match on the error's kind rather than on its message. The kind and the wire code are a stable contract, while message text is free to change between releases.

## Two places an error can appear

A query travels through two layers, and each reports failure differently.

The `Result` covers the request as a whole: a malformed query, a connection that is unavailable, a rejected sign-in. The response covers the individual statements inside the query, which can fail while the request itself succeeds.

That second layer is easy to miss. A query whose statements fail still returns `Ok`, because the request reached the server and came back. The statement errors appear only when the response is inspected with [`.check()`](/docs/reference/rust/methods/query.md) or `.take_errors()`.

```rust
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("memory").await?;
    db.use_ns("test").use_db("test").await?;

    // Three statements in one call. Only the third breaks the assertion.
    let mut response = db
        .query(
            "DEFINE FIELD name ON user TYPE string ASSERT $value.len() <= 20;
             CREATE user:short SET name = 'Billy';
             CREATE user:long SET name = 'Mr. Muchtoolongname the Fourth';",
        )
        .await?;

    // The call succeeded, so `?` above did not fire. take_errors() reports which
    // statements failed and leaves the successful ones in place.
    for (index, e) in response.take_errors() {
        println!("statement {index}: {} - {}", e.kind_str(), e.message());
    }

    // The record created by the statement that worked is still there.
    let created: Vec<String> = db.query("SELECT VALUE name FROM user").await?.take(0)?;
    println!("records still created: {created:?}");

    // A malformed query fails the call itself, so this one does return Err.
    if let Err(e) = db.query("SELECT * FROM").await {
        println!("call error: {} (is_validation = {})", e.kind_str(), e.is_validation());
    }

    // Some kinds carry structured details.
    if let Err(e) = connect("ws://127.0.0.1:9/").await {
        println!("{}: {:?}", e.kind_str(), e.connection_details());
    }
    Ok(())
}
```

```text title="Output"
statement 2: Internal - Found 'Mr. Muchtoolongname the Fourth' for field `name`, with record `user:long`, but field must conform to: $value.len() <= 20
records still created: ["Billy"]
call error: Validation (is_validation = true)
Connection: Some(ConnectionFailed)
```

`take_errors()` gives the index of each failing statement, which is what makes it possible to tell which one of a multi-statement query went wrong. The same call wrapped in a transaction behaves differently: see [Errors](/docs/reference/rest-api/errors.md#inside-a-transaction).

## Error kinds

`.kind_str()` returns the kind as a string, and each kind has a matching predicate for use in a `match` guard. The meaning of each kind is described in [Errors](/docs/reference/rest-api/errors.md#error-kinds).

| Kind | Predicate |
| --- | --- |
| `Validation` | `.is_validation()` |
| `Configuration` | `.is_configuration()` |
| `Query` | `.is_query()` |
| `Serialization` | `.is_serialization()` |
| `NotAllowed` | `.is_not_allowed()` |
| `NotFound` | `.is_not_found()` |
| `AlreadyExists` | `.is_already_exists()` |
| `Connection` | `.is_connection()` |
| `Thrown` | `.is_thrown()` |
| `Internal` | `.is_internal()` |
| `Context` | `.is_context()` |

Because `Internal` absorbs unrecognised kinds, a `match` that handles the kinds it cares about and treats the rest as internal stays correct against a newer server.

```rust
match db.query("SELECT * FROM person").await {
    Ok(response) => { /* inspect statements with .check() */ }
    Err(e) if e.is_validation() => eprintln!("bad query: {}", e.message()),
    Err(e) if e.is_not_allowed() => eprintln!("not permitted: {}", e.message()),
    Err(e) if e.is_connection() => eprintln!("connection lost: {}", e.message()),
    Err(e) => eprintln!("{}: {}", e.kind_str(), e.message()),
}
```

## Structured details

Eight of the kinds carry a typed detail enum, reached through an accessor named after the kind: `.validation_details()`, `.configuration_details()`, `.query_details()`, `.serialization_details()`, `.not_allowed_details()`, `.not_found_details()`, `.already_exists_details()`, and `.connection_details()`. Each returns `Option`, since a kind does not always carry a detail.

The detail types live under `surrealdb::types`, so the `ConnectionFailed` printed by the example above is a `surrealdb::types::ConnectionError`. `Thrown`, `Internal` and `Context` have no detail type.

## Following the cause chain

`.cause()` returns the underlying error where one was attached, so a failure that passed through several layers can be unwound to its origin.

```rust
let mut current = Some(&error);
while let Some(e) = current {
    eprintln!("{}: {}", e.kind_str(), e.message());
    current = e.cause();
}
```

## Learn more

- [`.query()`](/docs/reference/rust/methods/query.md) - `.check()` and `.take_errors()` on a response
- [Working with types](/docs/reference/rust/concepts/working-with-types.md) - the `surrealdb::types` crate that defines `Error`

---

Source: https://surrealdb.com/docs/reference/rust/concepts/fetch

# Fetching linked records

All the fields of a SurrealDB linked record can be fetched and deserialised into a Rust type

**3.x**

Start a running database using the following command:

```bash
surreal start --user root --pass secret 
```

To follow along interactively, connect [using SurrealDB Studio](/docs/explore/studio.md) or the following command to open up the CLI:

```bash
surrealdb % surreal sql --user root --pass secret --pretty
```

Then use the `cargo add` command to add the crates `surrealdb` and `tokio`.

## Fetching all the fields of a record link

The following example shows a classroom joined to a few students by [record links](/docs/reference/query-language/language-primitives/record-links.md).

```surql
CREATE teacher:one;
CREATE student:one, student:two, student:three;

CREATE classroom SET
    location = (-16.7, 64.4),
    school_name = "Jöklaskóli",
    teacher = teacher:one,
    students = [student:one, student:two, student:three];
```

A query using `SELECT * FROM classroom:one` will show the teacher and all of the students, but only their record IDs.

```surql title="Query and response"
SELECT * FROM classroom:one;

[
	{
		id: classroom:one,
		location: (-16.7, 64.4),
		school_name: 'Jöklaskóli',
		students: [
			student:one,
			student:two,
			student:three
		],
		teacher: teacher:one
	}
]
```

[The `.*` operator](/docs/reference/query-language/language-primitives/idioms.md#all-elements) for the `teacher` and `student` fields can be used in this case.

```surql
SELECT *, teacher.*, students.* FROM classroom;
```

Here is the result:

```surql title="Output"
[
	{
		id: classroom:one,
		location: (-16.7, 64.4),
		school_name: 'Jöklaskóli',
		students: [
			{
				id: student:one,
				name: 'one'
			},
			{
				id: student:two,
				name: 'two'
			},
			{
				id: student:three,
				name: 'three'
			}
		],
		teacher: {
			id: teacher:one,
			name: 'one'
		}
	}
]
```

## The Rust code

The code below that shows an example of `FETCH` is another example related to classes and students. Note that in one part it passes a [`Resource`](https://docs.rs/surrealdb/latest/surrealdb/opt/enum.Resource.html) into the [`create`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.create) method in order to return a [`Value`](https://docs.rs/surrealdb/latest/surrealdb/types/enum.Value.html) and thus not have to specify a return type to deserialise into. For more information on this technique, see [the page on flexible typing](/docs/reference/rust/concepts/flexible-typing.md).

```rust
use surrealdb::{
    engine::remote::ws::Ws,
    opt::{auth::Root, Resource},
    Surreal,
};
use surrealdb::types::{Datetime, RecordId, SurrealValue};

// Dance classes table name
const DANCE: &str = "dance";
// Students table name
const STUDENT: &str = "student";

// Dance class table schema
#[derive(Debug, SurrealValue)]
struct DanceClass {
    id: RecordId,
    name: String,
    created_at: Datetime,
}

// Student table schema
#[derive(Debug, SurrealValue)]
struct Student {
    id: RecordId,
    name: String,
    classes: Vec<RecordId>,
    created_at: Datetime,
}

// Student model with full class details
#[derive(Debug, SurrealValue)]
#[allow(dead_code)]
struct StudentClasses {
    id: RecordId,
    name: String,
    classes: Vec<DanceClass>,
    created_at: Datetime,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("main").use_db("main").await?;

    // Create a dance class and store the result
    let classes: Option<DanceClass> = db
        .create(DANCE)
        .content(DanceClass {
            id: RecordId::new(DANCE, "dc101"),
            name: "Introduction to Dancing".to_owned(),
            created_at: Datetime::default(),
        })
        .await?;

    // Create a student and assign her to the previous dance class
    // We don't care about the result here so we don't need to
    // type-hint and store it. We use `Resource::from` to return
    // a `Value` instead and ignore it.
    db.create(Resource::from(STUDENT))
        .content(Student {
            id: RecordId::new(STUDENT, "jane"),
            name: "Jane Doe".to_owned(),
            classes: classes.into_iter().map(|class| class.id).collect(),
            created_at: Datetime::default(),
        })
        .await?;

    // Run a query to retrieve students and full class info
    let mut results = db.query(format!("SELECT * FROM {STUDENT} FETCH classes")).await?;

    // Extract the first query statement result and deserialise it as a vector of students
    let students: Vec<StudentClasses> = results.take(0)?;

    // Use the result as you see fit. In this case we are simply pretty printing it.
    println!("Students = {:?}", students);

    Ok(())
}
```

Here is the final output:

```text
Students = [StudentClasses { id: RecordId { table: "student", key: String("jane") }, name: "Jane Doe", classes: [DanceClass { id: RecordId { table: "dance", key: String("dc101") }, name: "Introduction to Dancing", created_at: Datetime(2025-11-06T02:15:05.116807Z) }], created_at: Datetime(2025-11-06T02:15:05.117644Z) }]
```

**2.x**

Start a running database using the following command:

```bash
surreal start --user root --pass secret 
```

To follow along interactively, connect [using SurrealDB Studio](/docs/explore/studio.md) or the following command to open up the CLI:

```bash
surrealdb % surreal sql --user root --pass secret --ns namespace --db database --pretty
```

Then use the `cargo add` command to add three crates: `surrealdb` and `tokio`, and with `serde` with the "serde_derive" feature (`cargo add serde --features serde_derive`). The dependencies inside `Cargo.toml` should look something like this:

cargo add serde --features serde_derive

```toml
[dependencies]
serde = { version = "1.0.228", features = ["serde_derive"] }
surrealdb = "2.4.1"
tokio = "1.49.0"
```

## When to use FETCH

The following example shows a classroom joined to a few students by [record links](/docs/reference/query-language/language-primitives/record-links.md).

```surql
CREATE teacher:one;
CREATE student:one, student:two, student:three;

CREATE classroom SET
    location = (-16.7, 64.4),
    school_name = "Jöklaskóli",
    teacher = teacher:one,
    students = [student:one, student:two, student:three];
```

A query using `SELECT * FROM classroom:one` will show the teacher and all of the students, but only their record IDs.

```surql title="Query and response"
SELECT * FROM classroom:one;

[
	{
		id: classroom:one,
		location: (-16.7, 64.4),
		school_name: 'Jöklaskóli',
		students: [
			student:one,
			student:two,
			student:three
		],
		teacher: teacher:one
	}
]
```

[The `.*` operator](/docs/reference/query-language/language-primitives/idioms.md#all-elements) for the `teacher` and `student` fields can be used in this case, but note that the `.*` must be used twice in the case of the students: once to access each member of the array, and once more to access all of its fields.

```surql
SELECT *, teacher.*, students.*.* FROM classroom;
```

Using `FETCH` may be a nicer option in this case. The syntax is a bit more readable, and there is no need to think about which fields are single records and which ones are arrays.

```surql
SELECT * FROM classroom FETCH teacher, students;
```

Here is the result:

```surql title="Output"
[
	{
		id: classroom:one,
		location: (-16.7, 64.4),
		school_name: 'Jöklaskóli',
		students: [
			{
				id: student:one,
				name: 'one'
			},
			{
				id: student:two,
				name: 'two'
			},
			{
				id: student:three,
				name: 'three'
			}
		],
		teacher: {
			id: teacher:one,
			name: 'one'
		}
	}
]
```

## The Rust code

The code below that shows an example of `FETCH` is another example related to classes and students. Note that in one part it passes a [`Resource`](https://docs.rs/surrealdb/latest/surrealdb/opt/enum.Resource.html) into the [`create`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.create) method in order to return a [`Value`](https://docs.rs/surrealdb/latest/surrealdb/types/enum.Value.html) and thus not have to specify a return type to deserialise into. For more information on this technique, see [the page on flexible typing](/docs/reference/rust/concepts/flexible-typing.md).

```rust
use serde::{Deserialize, Serialize};
use surrealdb::{
    engine::remote::ws::Ws,
    opt::{auth::Root, Resource},
    sql::Datetime,
    RecordId, Surreal,
};

// Dance classes table name
const DANCE: &str = "dance";
// Students table name
const STUDENT: &str = "student";

// Dance class table schema
#[derive(Debug, Serialize, Deserialize)]
struct DanceClass {
    id: RecordId,
    name: String,
    created_at: Datetime,
}

// Student table schema
#[derive(Debug, Serialize)]
struct Student {
    id: RecordId,
    name: String,
    classes: Vec<RecordId>,
    created_at: Datetime,
}

// Student model with full class details
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct StudentClasses {
    id: RecordId,
    name: String,
    classes: Vec<DanceClass>,
    created_at: Datetime,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("namespace").use_db("database").await?;

    // Create a dance class and store the result
    let classes: Option<DanceClass> = db
        .create(DANCE)
        .content(DanceClass {
            id: RecordId::from((DANCE, "dc101")),
            name: "Introduction to Dancing".to_owned(),
            created_at: Datetime::default(),
        })
        .await?;

    // Create a student and assign her to the previous dance class
    // We don't care about the result here so we don't need to
    // type-hint and store it. We use `Resource::from` to return
    // a `sql::Value` instead and ignore it.
    db.create(Resource::from(STUDENT))
        .content(Student {
            id: RecordId::from((STUDENT, "jane")),
            name: "Jane Doe".to_owned(),
            classes: classes.into_iter().map(|class| class.id).collect(),
            created_at: Datetime::default(),
        })
        .await?;

    // Run a query to retrieve students and full class info
    let mut results = db.query(format!("SELECT * FROM {STUDENT} FETCH classes")).await?;

    // Extract the first query statement result and deserialise it as a vector of students
    let students: Vec<StudentClasses> = results.take(0)?;

    // Use the result as you see fit. In this case we are simply pretty printing it.
    println!("Students = {:?}", students);

    Ok(())
}
```

Here is the final output:

```text
Students = [StudentClasses { id: RecordId { table: "student", key: String("jane") }, name: "Jane Doe", classes: [DanceClass { id: RecordId { table: "dance", key: String("dc101") }, name: "Introduction to Dancing", created_at: Datetime(2025-11-06T02:15:05.116807Z) }], created_at: Datetime(2025-11-06T02:15:05.117644Z) }]
```

---

Source: https://surrealdb.com/docs/reference/rust/concepts/flexible-typing

# Flexible typing

The Rust SDK for SurrealDB offers methods for working with types without deserialisation

**3.x**

Most examples in the Rust SDK feature strict types like the following that can be serialised and deserialised as needed.

```rust
#[derive(Debug, SurrealValue)]
struct Student {
    name: String,
    class_id: u32,
}
```

However, sometimes you will need to work with types that have a more dynamic structure. This page offers a few methods to use in such a case.

## Getting started

Start a running database using the following command:

```bash
surreal start --user root --pass secret 
```

To follow along interactively, connect [using SurrealDB Studio](/docs/explore/studio.md) or the following command to open up the CLI:

```bash
surrealdb % surreal sql --user root --pass secret --pretty
```

Then use the `cargo add` command to add the `surrealdb` and `tokio` crates.

## Strict vs. flexible typing

The following example is a typical one, featuring a `Student` struct that holds a `name` and a `class_id`, followed by the `.select()` function to display all the students.

```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::{Error, Surreal};
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Student {
    name: String,
    class_id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("main").use_db("main").await?;

    let all_students: Vec<Student> = db.select("student").await?;
    println!("All students: {all_students:?}");

    Ok(())
}
```

Before running this code, first use SurrealDB Studio or the CLI to populate the database with two students.

```surql
CREATE student SET name = "Student 1", class_id = 10; CREATE student SET name = "Another student", class_id = 20;
```

Once that is done, running `cargo run` will display the following output.

```text
All students: [Student { name: "Another student", class_id: 20 }, Student { name: "Student 1", class_id: 10 }]
```

So far so good, but what if we were still experimenting with the data and created a `student` that diverged from the `Student` struct?

```surql
CREATE student SET name = "Third student", class_id = 40, metadata = { teacher: teacher:mr_gundry_white, favourite_classes: ["Music", "Industrial arts"] };
```

In this case the output would still conform to the `Student` struct and the extra information in the third student would not show up.

```text
All students: [Student { name: "Another student", class_id: 20 }, Student { name: "Student 1", class_id: 10 }, Student { name: "Third student", class_id: 40 }]
```

One possibility here is to use the `.query()` method, which will always return the output as received from the database.

```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::{Error, Surreal};
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Student {
    name: String,
    class_id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("main").use_db("main").await?;

    let all_students = db.query("SELECT * FROM student").await?;
    println!("All students: {all_students:?}");

    Ok(())
}
```

However, the output is a bit noisy.

```text
All students: IndexedResults { results: {0: (DbResultStats { execution_time: Some(139.916µs), query_type: Some(Other) }, Ok(Array(Array([Object(Object({"class_id": Number(Int(10)), "id": RecordId(RecordId { table: Table("student"), key: String("9q65y3sujtd6y2oq9qou") }), "name": String("Student 1")})), Object(Object({"class_id": Number(Int(20)), "id": RecordId(RecordId { table: Table("student"), key: String("emm1fhw2bxdm7vkcchni") }), "name": String("Another student")})), Object(Object({"class_id": Number(Int(40)), "id": RecordId(RecordId { table: Table("student"), key: String("vcwgksxms0819ivwsm3q") }), "metadata": Object(Object({"favourite_classes": Array(Array([String("Music"), String("Industrial arts")])), "teacher": RecordId(RecordId { table: Table("teacher"), key: String("mr_gundry_white") })})), "name": String("Third student")}))]))))}, live_queries: {} }
```

A better solution when working with dynamic structures in a situation like this is to pass in a [`Resource`](https://docs.rs/surrealdb/latest/surrealdb/opt/enum.Resource.html) into the methods we use. Database methods that take a `Resource` will automatically return a [`Value`](https://docs.rs/surrealdb/latest/surrealdb/types/enum.Value.html) which contains an enum of all the possible data types in SurrealDB, and thus does not require deserialising. In addition, a `Value` has the methods `.to_sql()` and `.to_sql_pretty()`, making the output similar to that in the CLI.

```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;
use surrealdb::{Error, Surreal};
use surrealdb::types::{SurrealValue, ToSql};

#[derive(Debug, SurrealValue)]
struct Student {
    name: String,
    class_id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("main").use_db("main").await?;

    let all_students = db.select(Resource::from("student")).await?;
    println!("All students regular: {}\n", all_students.to_sql());
    println!("All students pretty: {}", all_students.to_sql_pretty());

    Ok(())
}
```

As the output shows, the first println! statement looks like the output in the CLI, while the second looks like the output in the CLI when the --pretty flag is passed in.

```text
All students regular: [{ class_id: 10, id: student:9q65y3sujtd6y2oq9qou, name: 'Student 1' }, { class_id: 20, id: student:emm1fhw2bxdm7vkcchni, name: 'Another student' }, { class_id: 40, id: student:vcwgksxms0819ivwsm3q, metadata: { favourite_classes: ['Music', 'Industrial arts'], teacher: teacher:mr_gundry_white }, name: 'Third student' }]

All students pretty: [
	{
		class_id: 10,
		id: student:9q65y3sujtd6y2oq9qou,
		name: 'Student 1'
	},
	{
		class_id: 20,
		id: student:emm1fhw2bxdm7vkcchni,
		name: 'Another student'
	},
	{
		class_id: 40,
		id: student:vcwgksxms0819ivwsm3q,
		metadata: {
			favourite_classes: [
				'Music',
				'Industrial arts'
			],
			teacher: teacher:mr_gundry_white
		},
		name: 'Third student'
	}
]
```

A `Value` from `serde_json` can be passed into functions like `.create()`, allowing the [`json!`](https://docs.rs/serde_json/latest/serde_json/macro.json.html) macro to be used.

```rust
use serde_json::json;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;
use surrealdb::{Error, Surreal};
use surrealdb::types::{SurrealValue, ToSql};

#[derive(Debug, SurrealValue)]
struct Student {
    name: String,
    class_id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("main").use_db("main").await?;

    let new_student = db.create(Resource::from("student")).content(json!({
        "age": 15,
        "weekly_allowance": 20.5
    })).await?;
    
    println!("{}", new_student.to_sql());

    Ok(())
}
```

Output:

```text
[{ age: 15, id: student:n89ugobw4iaw7gw3lh9b, weekly_allowance: 20.5f }]
```

**2.x**

Most examples in the Rust SDK feature strict types like the following that can be serialised and deserialised as needed.

```rust
#[derive(Debug, Serialize, Deserialize)]
struct Student {
    name: String,
    class_id: u32,
}
```

However, sometimes you will need to work with types that have a more dynamic structure. This page offers a few methods to use in such a case.

## Getting started

Start a running database using the following command:

```bash
surreal start --user root --pass secret 
```

To follow along interactively, connect [using SurrealDB Studio](/docs/explore/studio.md) or the following command to open up the CLI:

```bash
surrealdb % surreal sql --user root --pass secret --ns main --db main --pretty
```

Then use the `cargo add` command to add four crates: `surrealdb`, `serde`, `serde_json`, and `tokio`. Your `Cargo.toml` file should look something like this.

```toml
[dependencies]
serde = "1.0.214"
serde_json = "1.0.132"
surrealdb = "2.4.1"
tokio = "1.49.0"
```

## Strict vs. flexible typing

The following example is a typical one, featuring a `Student` struct that holds a `name` and a `class_id`, followed by the `.select()` function to display all the students.

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::{Error, Surreal};

#[derive(Debug, Serialize, Deserialize)]
struct Student {
    name: String,
    class_id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("main").use_db("main").await?;

    let all_students: Vec<Student> = db.select("student").await?;
    println!("All students: {all_students:?}");

    Ok(())
}
```

Before running this code, first use SurrealDB Studio or the CLI to populate the database with two students.

```surql
CREATE student SET name = "Student 1", class_id = 10; CREATE student SET name = "Another student", class_id = 20;
```

Once that is done, running `cargo run` will display the following output.

```text
All students: [Student { name: "Another student", class_id: 20 }, Student { name: "Student 1", class_id: 10 }]
```

So far so good, but what if we were still experimenting with the data and created a `student` that diverged from the `Student` struct?

```surql
CREATE student SET name = "Third student", class_id = 40, metadata = { teacher: teacher:mr_gundry_white, favourite_classes: ["Music", "Industrial arts"] };
```

In this case the output would still conform to the `Student` struct and the extra information in the third student would not show up.

```text
All students: [Student { name: "Another student", class_id: 20 }, Student { name: "Student 1", class_id: 10 }, Student { name: "Third student", class_id: 40 }]
```

One possibility here is to use the `.query()` method, which will always return the output as received from the database.

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::{Error, Surreal};

#[derive(Debug, Serialize, Deserialize)]
struct Student {
    name: String,
    class_id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("main").use_db("main").await?;

    let all_students = db.query("SELECT * FROM student").await?;
    println!("All students: {all_students:?}");

    Ok(())
}
```

However, the output is a bit noisy.

```text
All students: Response { client: Surreal { router: OnceLock(Router { sender: Sender { .. }, last_id: 4, features: {LiveQueries} }), engine: PhantomData<surrealdb::api::engine::any::Any> }, results: {0: (Stats { execution_time: Some(65.417µs) }, Ok(Array(Array([Object(Object({"class_id": Number(Int(20)), "id": RecordId(RecordId { table: "student", key: String("7bhlb23ti1vedykpsnzd") }), "name": Strand(Strand("Another student"))})), Object(Object({"class_id": Number(Int(10)), "id": RecordId(RecordId { table: "student", key: String("rpi0qmsqc7rwaxddfxpb") }), "name": Strand(Strand("Student 1"))})), Object(Object({"class_id": Number(Int(40)), "id": RecordId(RecordId { table: "student", key: String("xl5rzvlkghtn01nh5tw2") }), "metadata": Object(Object({"favourite_classes": Array(Array([Strand(Strand("Music")), Strand(Strand("Industrial arts"))])), "teacher": RecordId(RecordId { table: "teacher", key: String("mr_gundry_white") })})), "name": Strand(Strand("Third student"))}))]))))}, live_queries: {} }
```

A better solution when working with dynamic structures in a situation like this is to pass in a [`Resource`](https://docs.rs/surrealdb/latest/surrealdb/opt/enum.Resource.html) into the methods we use. Database methods that take a `Resource` will automatically return a [`Value`](https://docs.rs/surrealdb/latest/surrealdb/types/enum.Value.html) which contains an enum of all the possible data types in SurrealDB, and thus does not require deserialising. In addition, a `Value' implements `Display`, making the output similar to that in the CLI.

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;
use surrealdb::{Error, Surreal};

#[derive(Debug, Serialize, Deserialize)]
struct Student {
    name: String,
    class_id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("main").use_db("main").await?;

    let all_students = db.select(Resource::from("student")).await?;
    println!("All students debug: {all_students:?}\n");
    println!("All students display: {all_students}");

    Ok(())
}
```

As the output shows, `Debug` printing returns an output similar to the `.query()` example above, while using `Display` is much neater.

```text
All students debug: Array(Array([Object(Object({"class_id": Number(Int(20)), "id": RecordId(RecordId { table: "student", key: String("7bhlb23ti1vedykpsnzd") }), "name": Strand(Strand("Another student"))})), Object(Object({"class_id": Number(Int(10)), "id": RecordId(RecordId { table: "student", key: String("rpi0qmsqc7rwaxddfxpb") }), "name": Strand(Strand("Student 1"))})), Object(Object({"class_id": Number(Int(40)), "id": RecordId(RecordId { table: "student", key: String("xl5rzvlkghtn01nh5tw2") }), "metadata": Object(Object({"favourite_classes": Array(Array([Strand(Strand("Music")), Strand(Strand("Industrial arts"))])), "teacher": RecordId(RecordId { table: "teacher", key: String("mr_gundry_white") })})), "name": Strand(Strand("Third student"))}))]))

All students display: [{ class_id: 20, id: student:7bhlb23ti1vedykpsnzd, name: 'Another student' }, { class_id: 10, id: student:rpi0qmsqc7rwaxddfxpb, name: 'Student 1' }, { class_id: 40, id: student:xl5rzvlkghtn01nh5tw2, metadata: { favourite_classes: ['Music', 'Industrial arts'], teacher: teacher:mr_gundry_white }, name: 'Third student' }]
```

A `Value` from `serde_json` can be passed into functions like `.create()`, allowing the [`json!`](https://docs.rs/serde_json/latest/serde_json/macro.json.html) macro to also be used.

```rust
use serde::{Deserialize, Serialize};
use serde_json::json;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;
use surrealdb::{Error, Surreal};

#[derive(Debug, Serialize, Deserialize)]
struct Student {
    name: String,
    class_id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Connect to the database server
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in into the server
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    // Select the namespace and database to use
    db.use_ns("main").use_db("main").await?;

    let new_student = db.create(Resource::from("student")).content(json!({
        "age": 15,
        "weekly_allowance": 20.5
    })).await?;
    
    println!("{new_student}");

    Ok(())
}
```

Output:

```text
{ age: 15, id: student:gp6g0p7t23musi3ms77d, weekly_allowance: 20.5f }
```

---

Source: https://surrealdb.com/docs/reference/rust/concepts/live

# Live queries

The Rust SDK for SurrealDB allows changes to tables in real time to be observed

**3.x**

A [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md) statement creates a session that keeps track of changes to a table in real time. Inside the Rust SDK, this is accomplished by appending [`.live()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Select.html#method.live) to the end of a [`.select()`](/docs/reference/rust/methods/select.md) query.

## Getting started

Start a running database using the following command:

```bash
surreal start --user root --pass secret 
```

To follow along interactively, connect [using SurrealDB Studio](/docs/explore/studio.md) or the following command to open up the CLI:

```bash
surrealdb % surreal sql --user root --pass secret --pretty
```

Then use the `cargo add` command to add three crates: `surrealdb`, `tokio`, and the `futures` crate for the [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html) trait needed to use the async stream.

The example code below shows a live select that keeps track of changes made to a sample of records from the `account` table. By adding the [`.range()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Select.html#method.range-1) method, it can be restricted to certain record IDs, in this case any record IDs in between the letters a and g.

The stream from the `.live()` method returns a stream of [`Notification`](https://docs.rs/surrealdb/latest/surrealdb/struct.Notification.html)s. These contain an action (an `Action::Create`, `Action::Update`, or `Action::Delete`), and a field called `data` that contains anything that can be deserialised. In this case, the `data` field will deserialise into a struct that we create called `Account`. It also contains a `query_id` field that contains the ID of the live query itself, for record keeping or to cancel using the [`KILL`](/docs/reference/query-language/statements/kill.md) statement.

Once the following code is run, the Rust client will continue to listen for changes to the `account` table indefinitely.

```rust
use futures::StreamExt;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;
use surrealdb::types::{RecordId, SurrealValue};

const ACCOUNT: &str = "account";

#[derive(Debug, SurrealValue)]
struct Account {
    id: RecordId,
    balance: f64,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;
    db.query("DEFINE TABLE account").await?;

    let mut sample_accounts = db.select(ACCOUNT).range("a"..="g").live().await?;

    while let Some(result) = sample_accounts.next().await {
        match result {
            Ok(notification) => {
                let action = notification.action;
                let account: Account = notification.data;
                let id = notification.query_id;
                println!("{action:?} from live ID {id}:\n  {account:#?}\n");
            }
            Err(error) => eprintln!("{error}"),
        }
    }

    Ok(())
}
```

We can now move to SurrealDB Studio or the CLI to execute a few queries and see what happens. The following queries create, update, and delete ten `account` records.

```surql
FOR $_ IN 0..10 { CREATE account SET balance = 10.0 };
UPDATE account SET balance += 1000;
DELETE account;
```

The Rust client will keep track for any with a record ID in between a and g, returning an output similar to the following.

```bash
Create from live ID aad570d6-1d3f-4dd6-81f5-582b92a6d8a4:
  Account {
    id: RecordId {
        table: Table(
            "account",
        ),
        key: String(
            "cr4obgozhu4oe2fo3vra",
        ),
    },
    balance: 10.0,
}

Create from live ID aad570d6-1d3f-4dd6-81f5-582b92a6d8a4:
  Account {
    id: RecordId {
        table: Table(
            "account",
        ),
        key: String(
            "do3us1twpyaxm20mp2qt",
        ),
    },
    balance: 10.0,
}

Update from live ID aad570d6-1d3f-4dd6-81f5-582b92a6d8a4:
  Account {
    id: RecordId {
        table: Table(
            "account",
        ),
        key: String(
            "cr4obgozhu4oe2fo3vra",
        ),
    },
    balance: 1010.0,
}

Update from live ID aad570d6-1d3f-4dd6-81f5-582b92a6d8a4:
  Account {
    id: RecordId {
        table: Table(
            "account",
        ),
        key: String(
            "do3us1twpyaxm20mp2qt",
        ),
    },
    balance: 1010.0,
}

Delete from live ID aad570d6-1d3f-4dd6-81f5-582b92a6d8a4:
  Account {
    id: RecordId {
        table: Table(
            "account",
        ),
        key: String(
            "cr4obgozhu4oe2fo3vra",
        ),
    },
    balance: 1010.0,
}

Delete from live ID aad570d6-1d3f-4dd6-81f5-582b92a6d8a4:
  Account {
    id: RecordId {
        table: Table(
            "account",
        ),
        key: String(
            "do3us1twpyaxm20mp2qt",
        ),
    },
    balance: 1010.0,
}
```

**2.x**

A [`LIVE SELECT`](/docs/reference/query-language/statements/live-select.md) statement creates a session that keeps track of changes to a table in real time. Inside the Rust SDK, this is accomplished by appending [`.live()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Select.html#method.live) to the end of a [`.select()`](/docs/reference/rust/methods/select.md) query.

## Getting started

Start a running database using the following command:

```bash
surreal start --user root --pass secret 
```

To follow along interactively, connect [using SurrealDB Studio](/docs/explore/studio.md) or the following command to open up the CLI:

```bash
surrealdb % surreal sql --user root --pass secret --ns namespace --db database --pretty
```

Then use the `cargo add` command to add four crates: `surrealdb` and `tokio`, the `futures` crate for the [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html) trait needed to use the async stream, as well as with `serde` with the "serde_derive" feature (`cargo add serde --features serde_derive`). The dependencies inside `Cargo.toml` should look something like this:

cargo add serde --features serde_derive

```toml
[dependencies]
futures = "0.3.31"
serde = { version = "1.0.228", features = ["serde_derive"] }
surrealdb = "2.4.1"
tokio = "1.49.0"
```

The example code below shows a live select that keeps track of changes made to a sample of records from the `account` table. By adding the [`.range()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Select.html#method.range-1) method, it can be restricted to certain record IDs, in this case any record IDs in between the letters a and g.

The stream from the `.live()` method returns a stream of [`Notification`](https://docs.rs/surrealdb/latest/surrealdb/struct.Notification.html)s. These contain an action (an `Action::Create`, `Action::Update`, or `Action::Delete`), and a field called `data` that contains anything that can be deserialised. In this case, the `data` field will deserialise into a struct that we create called `Account`. It also contains a `query_id` field that contains the ID of the live query itself, for record keeping or to cancel using the [`KILL`](/docs/reference/query-language/statements/kill.md) statement.

Once the following code is run, the Rust client will continue to listen for changes to the `account` table indefinitely.

```rust
use futures::StreamExt;
use serde::Deserialize;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::{RecordId, Surreal};

const ACCOUNT: &str = "account";

#[derive(Debug, Deserialize)]
struct Account {
    id: RecordId,
    balance: f64,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("namespace").use_db("database").await?;

    let mut sample_accounts = db.select(ACCOUNT).range("a"..="g").live().await?;

    while let Some(result) = sample_accounts.next().await {
        match result {
            Ok(notification) => {
                let action = notification.action;
                let account: Account = notification.data;
                let id = notification.query_id;
                println!("{action:?} from live ID {id}:\n  {account:#?}\n");
            }
            Err(error) => eprintln!("{error}"),
        }
    }

    Ok(())
}
```

We can now move to SurrealDB Studio or the CLI to execute a few queries and see what happens. The following queries create, update, and delete ten `account` records.

```surql
FOR $_ IN 0..10 { CREATE account SET balance = 10 };
UPDATE account SET balance += 1000;
DELETE account;
```

The Rust client will keep track for any with a record ID in between a and g, returning an output similar to the following.

```bash
Create from live ID 63853ee1-aa9b-4e04-a54a-3b900a3cbaaa:
  Account {
    id: RecordId {
        table: "account",
        key: String(
            "cjk4pk2am5chjs4hxjpz",
        ),
    },
    balance: 10.0,
}

Create from live ID 63853ee1-aa9b-4e04-a54a-3b900a3cbaaa:
  Account {
    id: RecordId {
        table: "account",
        key: String(
            "dp98mide1w91fnjoezf0",
        ),
    },
    balance: 10.0,
}

Update from live ID 63853ee1-aa9b-4e04-a54a-3b900a3cbaaa:
  Account {
    id: RecordId {
        table: "account",
        key: String(
            "cjk4pk2am5chjs4hxjpz",
        ),
    },
    balance: 1010.0,
}

Update from live ID 63853ee1-aa9b-4e04-a54a-3b900a3cbaaa:
  Account {
    id: RecordId {
        table: "account",
        key: String(
            "dp98mide1w91fnjoezf0",
        ),
    },
    balance: 1010.0,
}

Delete from live ID 63853ee1-aa9b-4e04-a54a-3b900a3cbaaa:
  Account {
    id: RecordId {
        table: "account",
        key: String(
            "cjk4pk2am5chjs4hxjpz",
        ),
    },
    balance: 1010.0,
}

Delete from live ID 63853ee1-aa9b-4e04-a54a-3b900a3cbaaa:
  Account {
    id: RecordId {
        table: "account",
        key: String(
            "dp98mide1w91fnjoezf0",
        ),
    },
    balance: 1010.0,
}
```

---

Source: https://surrealdb.com/docs/reference/rust/concepts/multi-tenancy

# Multi-tenancy

Multi-tenancy in the Rust SDK allows multiple connections to be used simultaneously, each tenant operating inside its own isolated namespace and database.

_(since v3.0.0)_

Multi-tenancy allows each tenant to operate inside its own isolated namespace and database.

Multi-session inside the Rust SDK is implemented through a session cloning mechanism. When you clone a `Surreal<C>` client instance, it creates a new session with independent state while sharing the underlying database connection. Sessions share the same physical connection for efficiency, are thread-safe and can be used concurrently across Tokio tasks, and work with all connection types (embedded and remote).

Each cloned instance maintains its own:

* Namespace and database selection (use_ns/use_db)
* Authentication state (signin/signup/invalidate)
* Session variables (set/unset)
* Transactions (begin/commit/cancel)

Note that these will remain unchanged when cloning a connection.

```rust
use surrealdb::Surreal;
use surrealdb::engine::local::Mem;
use surrealdb::types::{ToSql, Value};

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Mem>(()).await?;
    db.use_ns("ns").use_db("db").await?;
    db.set("val", 1).await?;
    println!("$val is `{}`", db.query("$val").await?.take::<Value>(0).unwrap().to_sql());
    let new = db.clone();
    println!("$val is still `{}` in the new session", new.query("$val").await?.take::<Value>(0).unwrap().to_sql());
    
    // Set to a different value inside 'new'
    new.set("val", 100).await?;

    println!("$val is still `{}` in the original", db.query("$val").await?.take::<Value>(0).unwrap().to_sql());
    println!("But is now `{}` in the new session", new.query("$val").await?.take::<Value>(0).unwrap().to_sql());
    Ok(())
}
```

If a connection without any set values is desired, using a static singleton along with a convenience function is one way to achieve this.

```rust
use std::sync::OnceLock;
use surrealdb::engine::any::connect;
use surrealdb::{Surreal, engine::any::Any};

static DB: OnceLock<Surreal<Any>> = OnceLock::new();

async fn new_with(namespace: &str, database: &str) -> Surreal<Any> {
    let db = DB.get().unwrap().clone();
    db.use_ns(namespace).use_db(database).await.unwrap();
    db
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Set the overall connection
    DB.set(connect("memory").await?).unwrap();

    // NS and DB must now be specifically indicated to use
    let session1 = new_with("acme", "app").await;
    let session2 = new_with("user", "app").await;
    Ok(())
}
```

Before version 3.0, cloning a `Surreal<C>` would create a cheap clone of the same session. If you have been using `.clone()` to pass on a `Surreal<C>` without a need for multi-tenancy, it is now preferable to wrap the client inside a type like an `Arc` to ensure that only the wrapper is cloned. Doing so will be somewhat more performant, as this example shows.

```rust
use std::sync::Arc;
use std::time::Instant;

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

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Mem>(()).await?;
    db.use_ns("ns").use_db("db").await?;

    let now = Instant::now();
    for _ in 0..100 {
        let cloned = db.clone();
        cloned
            .query(
                "
            LET $one = CREATE ONLY person;
            LET $two = CREATE ONLY person;
            RELATE $one->likes->$two;
        ",
            )
            .await?;
    }
    println!("Elapsed: {:?}", now.elapsed());

    let arced = Arc::new(db);
    let now = Instant::now();
    for _ in 0..100 {
        let cloned_arc = Arc::clone(&arced);
        cloned_arc
            .query(
                "
            LET $one = CREATE ONLY person;
            LET $two = CREATE ONLY person;
            RELATE $one->likes->$two;
        ",
            )
            .await?;
    }
    println!("Elapsed: {:?}", now.elapsed());

    Ok(())
}
```

Let's now take a look at some usage examples that take advantage of the new multi-tenancy available in SurrealDB 3.0.

## Usage examples

This first example shows how to use multi-session support to implement multi-tenancy, where each tenant operates in their own isolated namespace:

```rust
use surrealdb::Surreal;
use surrealdb::engine::local::Mem;
use surrealdb::opt::Resource;
use surrealdb::types::{RecordId, SurrealValue, object};

#[derive(Debug, SurrealValue)]
struct User {
    id: RecordId,
    name: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Create the base database connection
    let db = Surreal::new::<Mem>(()).await?;

    // Tenant 1: ACME Corporation
    let acme_db = db.clone();
    acme_db.use_ns("acme").use_db("app").await?;
    acme_db
        .create(Resource::from(("user", "john")))
        .content(object! { name: "John from ACME" })
        .await?;

    // Tenant 2: Widget Inc
    let widget_db = db.clone();
    widget_db.use_ns("widget").use_db("app").await?;
    widget_db
        .create(Resource::from(("user", "john")))
        .content(object! { name: "John from Widget Inc" })
        .await?;

    // Tenant 3: Example LLC
    let example_db = db.clone();
    example_db.use_ns("example").use_db("app").await?;
    example_db
        .create(Resource::from(("user", "john")))
        .content(object! { name: "John from Example LLC" })
        .await?;

    // Each tenant sees only their own data
    let acme_users: Vec<User> = acme_db.select("user").await?;
    println!("ACME users: {acme_users:?}");
    // Output: [User { id: user:john, name: "John from ACME" }]

    let widget_users: Vec<User> = widget_db.select("user").await?;
    println!("Widget users: {widget_users:?}");
    // Output: [User { id: user:john, name: "John from Widget Inc" }]

    // Tenants can operate concurrently without interfering with each other
    let acme_alice = acme_db
        .create(Resource::from(("user", "alice")))
        .content(object! { name: "Alice from ACME" });
    let widget_alice = widget_db
        .create(Resource::from(("user", "alice")))
        .content(object! { name: "Alice from Widget" });
    tokio::try_join!(acme_alice, widget_alice)?;

    Ok(())
}
```

The next example demonstrates querying across multiple databases simultaneously to aggregate data:

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

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Create the base database connection
    let db = Surreal::new::<Mem>(()).await?;

    // Database 1: North America sales
    let na_db = db.clone();
    na_db.use_ns("company").use_db("sales_na").await?;
    na_db
        .query(r#"
            CREATE sale:1 SET amount = 1000.00dec, region = "North America";
            CREATE sale:2 SET amount = 1500.00dec, region = "North America";
            CREATE sale:3 SET amount = 2000.00dec, region = "North America";
        "#)
        .await?
        .check()?;

    // Database 2: Europe sales
    let eu_db = db.clone();
    eu_db.use_ns("company").use_db("sales_eu").await?;
    eu_db
        .query(r#"
            CREATE sale:1 SET amount = 1200.00dec, region = "Europe";
            CREATE sale:2 SET amount = 1800.00dec, region = "Europe";
        "#)
        .await?
        .check()?;

    // Database 3: Asia sales
    let asia_db = db.clone();
    asia_db.use_ns("company").use_db("sales_asia").await?;
    asia_db
        .query(r#"
            CREATE sale:1 SET amount = 3000.00dec, region = "Asia";
            CREATE sale:2 SET amount = 2500.00dec, region = "Asia";
            CREATE sale:3 SET amount = 1800.00dec, region = "Asia";
        "#)
        .await?
        .check()?;

    // Query all databases concurrently
    let (mut na_result, mut eu_result, mut asia_result) = tokio::try_join!(
        na_db.query("RETURN { total: math::sum((SELECT VALUE amount FROM sale)) }"),
        eu_db.query("RETURN { total: math::sum((SELECT VALUE amount FROM sale)) }"),
        asia_db.query("RETURN { total: math::sum((SELECT VALUE amount FROM sale)) }"),
    )?;

    let na_total = na_result
        .take::<Option<Decimal>>("total")?
        .unwrap_or_default();
    let eu_total = eu_result
        .take::<Option<Decimal>>("total")?
        .unwrap_or_default();
    let asia_total = asia_result
        .take::<Option<Decimal>>("total")?
        .unwrap_or_default();

    println!("North America total: ${na_total:.2}"); // $4500.00
    println!("Europe total: ${eu_total:.2}"); // $3000.00
    println!("Asia total: ${asia_total:.2}"); // $7300.00
    println!("Grand total: ${:.2}", na_total + eu_total + asia_total); // $14800.00

    // You can also perform complex operations on specific databases
    // while maintaining independent session variables
    na_db.set("discount_rate", 0.1).await?;
    eu_db.set("discount_rate", 0.15).await?;
    asia_db.set("discount_rate", 0.05).await?;

    let (mut na_result, mut eu_result, mut asia_result) = tokio::try_join!(
        na_db.query(
            "RETURN { total: math::sum((SELECT VALUE amount * (1 - $discount_rate) FROM sale)) }"
        ),
        eu_db.query(
            "RETURN { total: math::sum((SELECT VALUE amount * (1 - $discount_rate) FROM sale)) }"
        ),
        asia_db.query(
            "RETURN { total: math::sum((SELECT VALUE amount * (1 - $discount_rate) FROM sale)) }"
        ),
    )?;

    println!("\nWith regional discounts:");
    let na_disc = na_result
        .take::<Option<Decimal>>("total")?
        .unwrap_or_default();
    let eu_disc = eu_result
        .take::<Option<Decimal>>("total")?
        .unwrap_or_default();
    let asia_disc = asia_result
        .take::<Option<Decimal>>("total")?
        .unwrap_or_default();

    println!("North America (10% off): ${na_disc:.2}"); // $4050.00
    println!("Europe (15% off): ${eu_disc:.2}"); // $2550.00
    println!("Asia (5% off): ${asia_disc:.2}"); // $6935.00

    Ok(())
}
```

---

Source: https://surrealdb.com/docs/reference/rust/concepts/surrealvalue-attributes

# SurrealValue attributes

Reference for the #[surreal(...)] attributes used with the SurrealValue derive, covering renaming, defaults, flattening and enum tagging.

These attributes customise the `SurrealValue` derive, which is covered in [Working with types](/docs/reference/rust/concepts/working-with-types.md) along with the `kind!` macro and the `Value` helpers.

The `SurrealValue` derive uses its own `#[surreal(...)]` attribute that is inspired by [Serde](https://serde.rs/attributes.html) (familiar names, similar enum tagging ideas), but it is not Serde and not an inheritance of `#[serde(...)]` attributes.

Conversion runs through `SurrealValue` and its derive macro rather than Serde's `Serialize` and `Deserialize`. Where Serde would be reached for to customise field names or flattening, use the matching `#[surreal(...)]` form below.

## Relationship to Serde

| Idea | Serde | `SurrealValue` (`#[surreal(...)]`) |
| --- | --- | --- |
| Rename one field / variant | `rename = "..."` | `rename = "..."` |
| Rename all fields / variants | `rename_all = "..."` | `rename_all = "..."` (same case strings as Serde) |
| Flatten nested object | `flatten` | `flatten` |
| Enum tagging | `tag` / `content` / `untagged` | `tag` / `content` / `untagged` |
| Missing field default | `default` / `default = "path"` | `default` / `default = "path"` (also on the container) |
| Catch-all unit variant | `other` | `other` |
| Skip a field | `skip`, `skip_serializing`, `skip_serializing_if` | Not supported on ordinary fields |
| Conditionally omit enum payload | (use field `skip_serializing_if`) | `skip_content` / `skip_content_if = "..."` on tagged enums |
| Serde-only types | (n/a) | `wrap` for `Serialize + Deserialize` types that do not implement `SurrealValue` |
| Tuple struct as array | (n/a) | `tuple` |
| Literal substitute for a unit | (n/a) | `value = ...` |

Supported `rename_all` values match Serde’s usual set (`lowercase`, `UPPERCASE`, `PascalCase`, `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`, `kebab-case`, `SCREAMING-KEBAB-CASE`). An explicit `rename` on a field or variant wins over the container `rename_all`.

`#[surreal(uppercase)]` and `#[surreal(lowercase)]` on enums are legacy aliases for `rename_all = "UPPERCASE"` and `rename_all = "lowercase"`. Prefer `rename_all` in new code. Do not combine them with `rename_all` on the same enum.

The attributes below are the ones the derive currently recognises.

## `surreal(default)`

The `surreal(default)` attribute fills in values when fields are missing during deserialisation. On a struct container, missing fields come from the type’s `Default` implementation. On a single field, use `#[surreal(default)]` for `<T as Default>::default()`, or `#[surreal(default = "path")]` for a custom function path.

```rust
use surrealdb::engine::any::connect;
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
struct UserData {
    num: i32,
    other_num: i32,
}

#[derive(SurrealValue)]
#[surreal(default)]
struct UserDataDefault {
    num: i32,
    other_num: i32,
}

impl Default for UserDataDefault {
    fn default() -> Self {
        UserDataDefault {
            num: 10,
            other_num: 20,
        }
    }
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("ns").use_db("db").await.unwrap();

    let mut has_two_fields = db
        .query("CREATE user SET num = 10, other_num = 20")
        .await
        .unwrap();

    let mut has_one_field = db.query("CREATE user SET num = 5").await.unwrap();

    println!(
        "Regular deserialization from DB result: {}",
        has_two_fields
            .take::<Option<UserData>>(0)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql()
    );

    println!(
        "Deserialization using DB result plus default value: {}",
        has_one_field
            .take::<Option<UserDataDefault>>(0)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql()
    )
}
```

```title="Output"
Regular deserialization from DB result: { num: 10, other_num: 20 }
Deserialization using DB result plus default value: { num: 5, other_num: 20 }
```

## `surreal(rename)`

The `surreal(rename)` attribute is used to provide a different name for a field on the SurrealDB side than the one used in the Rust code.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
struct UserData {
    num: i32,
}

#[derive(SurrealValue)]
struct UserDataRename {
    #[surreal(rename = "user_num")]
    num: i32,
}

fn main() {
    let user_data = UserData { num: 555 };
    let user_data_rename = UserDataRename { num: 555 };

    println!("Before rename: {}", user_data.into_value().to_sql());
    println!("After rename: {}", user_data_rename.into_value().to_sql());
}
```

```title = "Output"
Before rename: { num: 555 }
After rename: { user_num: 555 }
```

## `surreal(rename_all)`

Apply a case transform to every field name on a struct, or every variant name on an enum, unless a field or variant sets its own `rename`.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
#[surreal(rename_all = "camelCase")]
struct UserProfile {
    full_name: String,
    years_old: i64,
}

fn main() {
    let profile = UserProfile {
        full_name: "Ada".into(),
        years_old: 36,
    };
    // { fullName: 'Ada', yearsOld: 36 }
    println!("{}", profile.into_value().to_sql());
}
```

## `surreal(flatten)`

Merge a nested object’s fields into the parent object instead of nesting them under one key. This is the Surreal equivalent of Serde’s `#[serde(flatten)]`.

```rust
use surrealdb_types::{SurrealValue, ToSql, Value};

#[derive(SurrealValue)]
struct Coords {
    x: i64,
    y: i64,
}

#[derive(SurrealValue)]
struct Point {
    name: String,
    #[surreal(flatten)]
    coords: Coords,
}

fn main() {
    let point = Point {
        name: "origin".into(),
        coords: Coords { x: 0, y: 0 },
    };
    // { name: 'origin', x: 0, y: 0 }
    println!("{}", point.into_value().to_sql());
}
```

`flatten` cannot be combined with `rename` on the same field: there is no single key left to rename.

> [!NOTE]
> A struct that flattens a field needs `Value` in scope, as in the import above. The code generated for `flatten` refers to `Value` without qualifying it, so leaving the import out fails to compile with `` cannot find type `Value` in this scope ``, reported against the `SurrealValue` derive rather than any line you wrote.

## `surreal(uppercase)` and `surreal(lowercase)`

These two attributes are legacy aliases for `rename_all = "UPPERCASE"` and `rename_all = "lowercase"` on enums. Prefer `rename_all` when writing new types.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
enum LogLevel {
    Debug(String),
    Info(String),
}

#[derive(SurrealValue)]
#[surreal(uppercase)]
enum LogLevelUpper {
    Debug(String),
    Info(String),
}

#[derive(SurrealValue)]
#[surreal(lowercase)]
enum LogLevelLower {
    Debug(String),
    Info(String),
}

fn main() {
    let log_level = LogLevel::Debug("User1".into());
    let log_level_upper = LogLevelUpper::Debug("User1".into());
    let log_level_lower = LogLevelLower::Debug("User1".into());

    println!("Before attribute: {}", log_level.into_value().to_sql());
    println!("After uppercase: {}", log_level_upper.into_value().to_sql());
    println!("After lowercase: {}", log_level_lower.into_value().to_sql());
}
```

```title = "Output"
Before attribute: { Debug: 'User1' }
After uppercase: { DEBUG: 'User1' }
After lowercase: { debug: 'User1' }
```

## `surreal(tuple)`

As SurrealQL does not have a tuple type, this attribute can be used to interface in which a Rust tuple struct is treated as an array (instead of a single value) and vice versa.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
struct UserData(i32);

#[derive(SurrealValue)]
#[surreal(tuple)]
struct UserDataTuple(i32);

fn main() {
    println!(
        "Without tuple attribute: {}",
        UserData(555).into_value().to_sql()
    );
    println!(
        "With tuple attribute: {}",
        UserDataTuple(555).into_value().to_sql()
    );
}
```

```title="Output"
Without tuple attribute: 555
With tuple attribute: [555]
```

## `surreal(untagged)`

The `surreal(untagged)` attribute removes the tag from the variant of an enum. This is similar to using `VALUE` in SurrealQL to show only the value and not the field name of a record.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
enum LogLevel {
    Debug(String),
    Info(String),
}

#[derive(SurrealValue)]
#[surreal(untagged)]
enum LogLevelUntagged {
    Debug(String),
    Info(String),
}

fn main() {
    let log_level = LogLevel::Debug("User1".into());
    let log_level_untagged = LogLevelUntagged::Debug("User1".into());

    println!("Before untagged: {}", log_level.into_value().to_sql());
    println!(
        "After untagged: {}",
        log_level_untagged.into_value().to_sql()
    );
}
```

```title="Output"
Before untagged: { Debug: 'User1' }
After untagged: 'User1'
```

## `surreal(tag)`

The `surreal(tag)` attribute can be used to give a tag to a variant. This will create a structure in which the new tag value is the field name, and the variant its value.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
enum LogLevel {
    Debug,
    Info,
}

#[derive(SurrealValue)]
#[surreal(tag = "log_level")]
enum LogLevelTag {
    Debug,
    Info,
}

fn main() {
    let log_level = LogLevel::Debug;
    let log_level_tag = LogLevelTag::Debug;
    println!("\n___surreal(tag)___");
    println!("Before tag: {}", log_level.into_value().to_sql());
    println!("After tag: {}", log_level_tag.into_value().to_sql());
}
```

```title="Output"
Before tag: { Debug: {  } }
After tag: { log_level: 'Debug' }
```

## `surreal(content)`

While the `surreal(tag)` attribute on its own can only be used on variants that do not hold data, the `surreal(content)` makes this possible.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
enum LogLevel {
    Debug(String),
    Info(String),
}

#[derive(SurrealValue)]
#[surreal(tag = "log_level", content = "user")]
enum LogLevelContent {
    Debug(String),
    Info(String),
}

fn main() {
    let log_level = LogLevel::Debug("User1".to_string());
    let log_level_tag = LogLevelContent::Debug("User1".to_string());

    println!("Before content: {}", log_level.into_value().to_sql());
    println!("After content: {}", log_level_tag.into_value().to_sql());
}
```

```title="Output"
Before content: { Debug: 'User1' }
After content: { log_level: 'Debug', user: 'User1' }
```

## `surreal(skip_content)` and `surreal(skip_content_if)`

On enums that use adjacent tagging (`tag` plus `content`), these control whether the content field is written (and whether it may be absent when reading).

They are the closest Surreal analogues to Serde’s `skip_serializing_if`, but they apply to the enum content field, not to arbitrary struct fields.

| Attribute | Effect |
| --- | --- |
| `skip_content` | Never emit the content field for that enum or variant |
| `skip_content_if = "path"` | Emit content only when the predicate returns false (for example `Value::is_empty`) |

```rust
use surrealdb_types::{SurrealValue, ToSql, Value};

#[derive(SurrealValue)]
#[surreal(tag = "kind", content = "details", skip_content_if = "Value::is_empty")]
enum ApiStatus {
    Ok,
    Error { message: String },
}

fn main() {
    // Unit variant: content omitted when empty
    // { kind: 'Ok' }
    println!("{}", ApiStatus::Ok.into_value().to_sql());

    // Named variant: content present when there is data
    // { kind: 'Error', details: { message: 'boom' } }
    println!(
        "{}",
        ApiStatus::Error {
            message: "boom".into()
        }
        .into_value()
        .to_sql()
    );
}
```

You can also put `skip_content` or `skip_content_if` on individual variants. They only apply to enums that already declare a `tag` (with or without `content`).

## `surreal(other)`

On a unit variant, `other` marks a deserialisation catch-all: if no other variant matches, that variant is chosen instead of returning an error. At most one variant per enum should use it. It cannot be combined with `rename` or `value` on the same variant.

```rust
use surrealdb_types::SurrealValue;

#[derive(Debug, PartialEq, SurrealValue)]
#[surreal(untagged)]
enum WireFlag {
    #[surreal(value = true)]
    On,
    #[surreal(value = false)]
    Off,
    #[surreal(other)]
    Unknown,
}
```

## `surreal(value)`

This attribute can be used on the fields of an enum marked with `surreal(untagged)` to give it a substitute value. The value that follows this attribute can be a NONE, NULL, bool, string, int, or float.

```rust
use surrealdb_types::{SurrealValue, ToSql};

fn main() {
    #[derive(Clone, Debug, SurrealValue)]
    #[surreal(untagged)]
    pub enum LogLevel {
        Regular,
        Verbose,
        Off,
    }

    #[derive(Clone, Debug, SurrealValue)]
    #[surreal(untagged)]
    pub enum LogLevelValue {
        #[surreal(value = "info")]
        Regular,
        #[surreal(value = "debug")]
        Verbose,
        #[surreal(value = NONE)]
        Off,
    }

    println!("Only untagged: {}", LogLevel::Off.into_value().to_sql());
    println!(
        "Untagged plus value: {}",
        LogLevelValue::Off.into_value().to_sql()
    );
}
```

```title="Output"
With only untagged: 'Off'
With untagged plus substitute value: NONE
```

## `surreal(wrap)`

This attribute can be used on fields with a type that implements serde's `Serialize` and `Deserialize` traits, but not `SurrealValue`.

This is meant for interoperability and should only be used if necessary.

```rust
use surrealdb_types::{SurrealValue, ToSql};
use serde::{Serialize, Deserialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExternStruct {
    foo: String,
    bar: String,
}

#[derive(Clone, Debug, SurrealValue)]
pub struct OurStruct {
    baz: String,
    #[surreal(wrap)]
    external: ExternStruct
}
```

## More examples

Here are some more examples from the SurrealDB source code showing how the `surreal` attribute can be used.

```rust
use surrealdb_types::{SurrealValue, Value};

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged)]
enum EnumMixedWithValue {
    #[surreal(value = false)]
    None,
    Some(Vec<String>),
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", content = "content")]
enum EnumTaggedWithTagAndContent {
    Foo,
    Bar { prop: String },
    Baz(String),
    Qux(String, i64),
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", content = "content", lowercase)]
enum EnumTaggedWithTagAndContentLowercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", content = "content", uppercase)]
enum EnumTaggedWithTagAndContentUppercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag")]
enum EnumTaggedWithTag {
    Foo,
    Bar { prop: String },
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", lowercase)]
enum EnumTaggedWithTagLowercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", uppercase)]
enum EnumTaggedWithTagUppercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
enum EnumTaggedVariant {
    Foo,
    Bar { prop: String },
    Baz(String),
    Qux(String, i64),
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(lowercase)]
enum EnumTaggedVariantLowercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(uppercase)]
enum EnumTaggedVariantUppercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged)]
enum EnumUnitValue {
    #[surreal(value = true)]
    True,
    #[surreal(value = false)]
    False,
    #[surreal(value = null)]
    Null,
    #[surreal(value = none)]
    None,
    #[surreal(value = "Hello")]
    String,
    #[surreal(value = 123)]
    Int,
    #[surreal(value = 123.45)]
    Float,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged)]
enum EnumUntagged {
    Foo,
    Bar,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged, lowercase)]
enum EnumUntaggedLowercase {
    Foo,
    Bar,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged, uppercase)]
enum EnumUntaggedUppercase {
    Foo,
    Bar,
}

#[derive(SurrealValue, Debug, PartialEq)]
struct PersonRenamed {
    #[surreal(rename = "full_name")]
    name: String,
    #[surreal(rename = "years_old")]
    age: i64,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tuple)]
struct StringWrapperTuple(String);

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(value = true)]
struct UnitStructWithValue;

#[derive(Clone, Debug, SurrealValue, PartialEq)]
#[surreal(default)]
struct TestDefault {
    str: String,
    boolean: bool,
    optional: Option<String>,
}

impl Default for TestDefault {
    fn default() -> Self {
        TestDefault {
            str: "default".to_string(),
            boolean: true,
            optional: None,
        }
    }
}

fn main() {}
```

---

Source: https://surrealdb.com/docs/reference/rust/concepts/transaction

# Manual transactions

Use SurrealQL BEGIN and COMMIT in queries, or the Rust SDK `begin` / `commit` / `cancel` transaction handle, and check per-statement results before committing

**3.x**

While every query in SurrealDB is run [inside its own transaction](/docs/reference/query-language/language-primitives/transactions.md), manual transactions made up of multiple statements can be used via the [BEGIN](/docs/reference/query-language/statements/begin.md) and [COMMIT](/docs/reference/query-language/statements/commit.md) keywords.

## Getting started

Start a running database using the following command:

```bash
surreal start --user root --pass secret 
```

To follow along interactively, connect [using SurrealDB Studio](/docs/explore/studio.md) or the following command to open a connection in the CLI:

```bash
surreal sql --user root --pass secret --pretty
```

Then use the `cargo add` command to add the `surrealdb` and `tokio` crates. The dependencies inside `Cargo.toml` should look something like this:

```toml
[dependencies]
surrealdb = "3.2.0"
tokio = "1.52.1"
```

### Using a client-side transaction

Once this is done, you can use [`.begin()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.begin) to get a client-side [`Transaction`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html). Run your statements with [`.query()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html#method.query) or other methods such as `.select()`,` `.create()` and so on, then end the transaction in one of two ways:

* [`commit()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html#method.commit) - apply the changes. The future resolves to a [`Surreal`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html) client again.
* [`cancel()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html#method.cancel) - roll back. This also returns the `Surreal` client when it completes.

Note that the outer `Result` from `await`ing a query only shows that the statements have succeeded, but a response can still include per-statement failures (the request succeeded, but one of the SQL statements did not). Collect those with [`take_errors()`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html#method.take_errors) on the query response, or use [`check()`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html#method.check) to fail on the first error. If the outer `Result` is `Err`, the transaction is not usable as intended.

The following example uses an in-memory database, runs every query in its own short transaction, and cancels (or would skip a commit) when a statement error shows up.

```rust
use surrealdb::Surreal;
use surrealdb::engine::any::{connect, Any};

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("memory").await?;
    db.use_ns("ns").use_db("db").await?;

    let db = run_in_transaction(db, "LET $x: int = 'not a number';").await?;
    let db = run_in_transaction(db, "SELECT SELECT SELECT").await?;
    run_in_transaction(db, "9").await?;

    Ok(())
}

// Runs a single query inside a new transaction.
// `commit` only runs when there
// are no per-statement errors;
// otherwise the transaction is cancelled.
async fn run_in_transaction(
    db: Surreal<Any>,
    surql: &str,
) -> surrealdb::Result<Surreal<Any>> {
    let tx = db.begin().await?;

    match tx.query(surql).await {
        Ok(mut response) => {
            let errors = response.take_errors();
            if !errors.is_empty() {
                eprintln!("Errors from statements: {errors:#?}\n");
                return tx.cancel().await;
            }
            println!("Ok: {response:#?}\n");
            return tx.commit().await;
        }
        Err(e) => {
            eprintln!("Error from query request: {e}\n");
            return tx.cancel().await;
        }
    }
}
```

### Using SurrealQL transaction statements

A manual transaction can also be performed by sending in a `BEGIN` and other statements into the `.query()` method manually. This will result in the same behaviour as the previous method, but will not return a `Transaction` on the SDK side.

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::types::{SurrealValue, ToSql, Value};

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("memory").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    let mut response = db
        .query(
            "
        // Start transaction
        BEGIN;
        // Setup accounts
        CREATE account:one SET balance = 135605.16;
        CREATE account:two SET balance = 91031.31;
        // Move money
        UPDATE account:one SET balance += 300.00;
        UPDATE account:two SET balance -= 300.00;
        // Finalise
        COMMIT;
        ",
        )
        .await?;

    for i in 0..response.num_statements() {
        println!(
            "{}",
            response
                .take::<Option<Value>>(i)
                .unwrap()
                .into_value()
                .to_sql()
        );
    }

    Ok(())
}
```

The output will look like this.

```text
NONE
[{ balance: 135605.16f, id: account:one }]
[{ balance: 91031.31f, id: account:two }]
[{ balance: 135905.16f, id: account:one }]
[{ balance: 90731.31f, id: account:two }]
NONE
```

To avoid the possibility of typos, the [`.set()`](/docs/reference/rust/methods/set.md) method can be used to set the amount to transfer.

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    // Set the parameter $amount for later use
    db.set("amount", 300).await?;

    let response = db
        .query(
            "
        // Start transaction
        BEGIN;
        // Setup accounts
        CREATE account:one SET balance = 135605.16;
        CREATE account:two SET balance = 91031.31;
        // Move money
        UPDATE account:one SET balance += $amount;
        UPDATE account:two SET balance -= $amount;
        // Finalise
        COMMIT;
        ",
        )
        .await?;

    println!("{response:#?}");

	// See if any errors were returned
	response.check()?;

	Ok(())
}
```

**2.x**

## Manual transactions

While every query in SurrealDB is run [inside its own transaction](/docs/reference/query-language/language-primitives/transactions.md), manual transactions made up of multiple statements can be used via the [BEGIN](/docs/reference/query-language/statements/begin.md) and [COMMIT](/docs/reference/query-language/statements/commit.md) keywords.

The [`.query()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.query) method can take any number of statements, returning a [`Response`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html) that contains the results of each of them. In addition, the same method before being called returns [a struct](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Query.html) that also allows [the same `.query()` method](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Query.html#method.query) to be called on it, chaining the new query onto the existing query. This can help greatly with readability, as the example code below shows.

## Getting started

Start a running database using the following command:

```bash
surreal start --user root --pass secret 
```

To follow along interactively, connect [using SurrealDB Studio](/docs/explore/studio.md) or the following command to open a connection in the CLI:

```bash
surreal sql --user root --pass secret --ns main --db main --pretty
```

Then use the `cargo add` command to add the `surrealdb` and `tokio` crates. The dependencies inside `Cargo.toml` should look something like this:

```toml
[dependencies]
surrealdb = "2.4.1"
tokio = "1.49.0"
```

Once this is done, copy and paste the following code to run a manual transaction that creates two `account` records and then transfers 300 units from one account to the other.

```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
	let db = Surreal::new::<Ws>("localhost:8000").await?;

	db.signin(Root {
		username: "root",
		password: "secret",
	})
	.await?;

	db.use_ns("main").use_db("main").await?;

    let response = db
        .query("
        // Start transaction
        BEGIN;
        // Setup accounts
        CREATE account:one SET balance = 135605.16;
        CREATE account:two SET balance = 91031.31;
        // Move money
        UPDATE account:one SET balance += 300.00;
        UPDATE account:two SET balance -= 300.00;
        // Finalise
        COMMIT;
        ")
        .await?;

    println!("{response:#?}");

	// See if any errors were returned
	response.check()?;

	Ok(())
}
```

To avoid the possibility of typos, the [`.set()`](/docs/reference/rust/methods/set.md) method can be used to set the amount to transfer.

```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
	let db = Surreal::new::<Ws>("localhost:8000").await?;

	db.signin(Root {
		username: "root",
		password: "secret",
	})
	.await?;

	db.use_ns("main").use_db("main").await?;

    // Set the parameter $amount for later use
    db.set("amount", 300).await?;

    let response = db
        .query("
        // Start transaction
        BEGIN;
        // Setup accounts
        CREATE account:one SET balance = 135605.16;
        CREATE account:two SET balance = 91031.31;
        // Move money
        UPDATE account:one SET balance += $amount;
        UPDATE account:two SET balance -= $amount;
        // Finalise
        COMMIT;
        ")
        .await?;

    println!("{response:#?}");

	// See if any errors were returned
	response.check()?;

	Ok(())
}
```

`Surreal::begin()` and the `Transaction` handle (with [`commit()`](https://docs.rs/surrealdb/3.0.5/surrealdb/method/struct.Transaction.html#method.commit) / [`cancel()`](https://docs.rs/surrealdb/3.0.5/surrealdb/method/struct.Transaction.html#method.cancel)) are only in the `surrealdb` **Rust crate 3.0.0+**. The first tab (**3.x**) shows the full example, including per-statement errors and `commit` or `cancel`. In this 2.x tab, use `BEGIN` / `COMMIT` in SurrealQL, or [upgrade the crate to 3.0 or newer](https://crates.io/crates/surrealdb) and add `surrealdb = "3.0.5"` (or similar) in `Cargo.toml` to use that API.

---

Source: https://surrealdb.com/docs/reference/rust/concepts/vector-embeddings

# Vector embeddings

Many crates are available to work with vector embeddings via the SurrealDB Rust SDK.

The quickest way to retrieve vector embeddings is to use the [fastembed crate](/docs/build/integrations/embeddings-providers/fastembed.md#language-specific-example), which does not require a user key or calling into an external service.

The following blog posts include runnable examples using vector embeddings via the Rust SDK.

* [Building an AI-native multi-model UI with SurrealDB
](/blog/building-an-ai-native-multi-model-ui-with-surrealdb): a UI built with Iced.rs with OpenAI and Mistral embeddings
* [Hybrid vector + text Search in the terminal with SurrealDB and Ratatui](/blog/hybrid-vector-text-search-in-the-terminal-with-surrealdb-and-ratatui): a UI built with Ratatui.rs with OpenAI and Mistral embeddings
* [Make a medical chatbot using GraphRAG with SurrealDB + LangChain](/blog/make-a-medical-chatbot-using-graphrag-with-surrealdb-langchain)
* [Semantic search in Rust with SurrealDB and Mistral AI
](/blog/semantic-search-in-rust-with-surrealdb-and-mistral-ai)
* [Semantic search with SurrealDB and OpenAI
](/blog/semantic-search-with-surrealdb-and-openai)
* [Building a smart knowledge agent with SurrealDB and Rig.rs](/blog/rag-can-be-rigged)

---

Source: https://surrealdb.com/docs/reference/rust/concepts/working-with-types

# Working with types

The surrealdb-types crate provides the SurrealValue trait, the kind! macro and the value constructors used to move data between Rust and SurrealDB.

The [surrealdb-types](https://crates.io/crates/surrealdb-types) crate holds the public value type system shared across SurrealDB. It is kept separate from the database core so that types and type conversions can be used on their own, without pulling in the whole database, but it is also available from the main `surrealdb` crate under the `surrealdb::types` path.

## The `SurrealValue` trait

`SurrealValue` is the trait that converts a Rust type to and from a SurrealDB value. Deriving it is all that is needed to use a Rust type for serialisation and deserialisation. To customise how a type is converted, such as renaming fields or tagging enum variants, see [SurrealValue attributes](/docs/reference/rust/concepts/surrealvalue-attributes.md).

```rust
use surrealdb::engine::any::connect;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Employee {
    name: String,
    active: bool,
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();

    db.use_ns("ns").use_db("db").await.unwrap();

    let mut res = db
        .query("CREATE employee:bobby SET name = 'Bobby', active = true")
        .await
        .unwrap();

    let bobby = res.take::<Option<Employee>>(0).unwrap().unwrap();

    // Employee { name: "Bobby", active: true }
    println!("{bobby:?}");
}
```

The `SurrealValue` trait can be implemented manually via three methods: one to indicate the matching SurrealDB type, a second to convert into a SurrealDB Value, and a third to convert out of a SurrealDB Value.

```rust
use surrealdb::engine::any::connect;
use surrealdb::types::{Datetime, Error, Kind, SurrealValue, Value};

#[derive(Debug)]
struct MyOwnDateTime(i64);

impl SurrealValue for MyOwnDateTime {
    fn kind_of() -> Kind {
        Kind::Datetime
    }

    fn into_value(self) -> Value {
        Value::Datetime(Datetime::from_timestamp(self.0, 0).unwrap())
    }

    fn from_value(value: Value) -> Result<Self, Error>
    where
        Self: Sized,
    {
        match value {
            Value::Datetime(n) => Ok(MyOwnDateTime(n.timestamp_millis())),
            _ => Err(Error::thrown("No good".to_string())),
        }
    }
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();

    db.use_ns("main").use_db("main").await.unwrap();

    println!(
        "{:?}",
        db.query("time::now()")
            .await
            .unwrap()
            .take::<Option<MyOwnDateTime>>(0)
    );
}
```

An example of successful and unsuccessful conversions into the user-created `MyOwnDateTime` struct:

```rust
#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();

    db.use_ns("main").use_db("main").await.unwrap();

    println!(
        "{:?}\n",
        db.query("time::now()")
            .await
            .unwrap()
            .take::<Option<MyOwnDateTime>>(0)
    );

    println!(
        "{:?}",
        db.query("CREATE person")
            .await
            .unwrap()
            .take::<Option<MyOwnDateTime>>(0)
    );
}
```

Output:

```text
Ok(Some(MyOwnDateTime(1760330504574)))

Err(InternalError("Couldn't convert Object(Object({\"id\": RecordId(RecordId { table: \"person\", key: String(\"tcblzaktx3ponin9dyci\") })})) to MyOwnDateTime"))
```

## The `kind!` macro

The crate includes a `kind!` macro which allows a SurrealQL type to be used directly instead of its Rust equivalent.

This macro is especially useful when working with types like [literals](/docs/reference/query-language/language-primitives/data-types/literals.md) which are similar to enums but can specify exact possible values in a way that Rust would require deriving `TryFrom` to work. In this case, the `SurrealValue` trait can be implemented manually and the `kind!` macro used for its `kind_of()` method.

```rust
fn kind_of() -> surrealdb_types::Kind {
    kind!({ status: "good" } | { status: "goodwithnotification", notification: string} | { status: "error", at: datetime, reason: string })
}
```

This is technically possible without the macro, but requires a lot more boilerplate. Here is the output when using `cargo expand` to show the generated code for the example above.

```rust
fn kind_of() -> surrealdb_types::Kind {
    surrealdb_types::Kind::Either(
        vec!([
            surrealdb_types::Kind::Literal(
                surrealdb_types::KindLiteral::Object(
                    std::collections::BTreeMap::from([
                        (
                            "status".to_string(),
                            surrealdb_types::Kind::Literal(
                                surrealdb_types::KindLiteral::String("good".to_string()),
                            ),
                        ),
                    ]),
                ),
            ),
            surrealdb_types::Kind::Literal(
                surrealdb_types::KindLiteral::Object(
                    std::collections::BTreeMap::from([
                        (
                            "status".to_string(),
                            surrealdb_types::Kind::Literal(
                                surrealdb_types::KindLiteral::String(
                                    "goodwithnotification".to_string(),
                                ),
                            ),
                        ),
                        ("notification".to_string(), surrealdb_types::Kind::String),
                    ]),
                ),
            ),
            surrealdb_types::Kind::Literal(
                surrealdb_types::KindLiteral::Object(
                    std::collections::BTreeMap::from([
                        (
                            "status".to_string(),
                            surrealdb_types::Kind::Literal(
                                surrealdb_types::KindLiteral::String("error".to_string()),
                            ),
                        ),
                        ("at".to_string(), surrealdb_types::Kind::Datetime),
                        ("reason".to_string(), surrealdb_types::Kind::String),
                    ]),
                ),
            ),
            ]),
        ),
}
```

The following example shows the `kind!` macro used for a Rust enum that manually implements `SurrealValue`, along with examples of its use from the Rust side to the SurrealDB side, and vice versa.

```rust
use surrealdb::engine::any::connect;
use surrealdb_types::{Datetime, Error, Object, SurrealValue, ToSql, Value, kind};

#[derive(SurrealValue)]
struct MyError {
    at: Datetime,
    reason: String,
}

enum Response {
    Good,
    GoodWithNotification(String),
    Error(MyError),
}

impl SurrealValue for Response {
    fn kind_of() -> surrealdb_types::Kind {
        kind!({ status: "good" } | { status: "goodwithnotification", notification: string} | { status: "error", at: datetime, reason: string })
    }

    fn into_value(self) -> Value {
        let mut obj = Object::new();
        match self {
            Response::Good => {
                obj.insert("status", "good");
            }
            Response::GoodWithNotification(n) => {
                obj.insert("status", "goodwithnotification");
                obj.insert("notification", n);
            }
            Response::Error(e) => {
                obj.insert("status", "error");
                obj.insert("at", e.at);
                obj.insert("reason", e.reason);
            }
        }
        Value::Object(obj)
    }

    fn from_value(value: Value) -> Result<Self, Error>
    where
        Self: Sized,
    {
        let Value::Object(o) = value else {
            return Err(Error::thrown("Should have been an object".to_string()));
        };
        let Some(Value::String(status)) = o.get("status") else {
            return Err(Error::thrown(
                "Error trying to get 'status' field".to_string(),
            ));
        };
        match status.as_str() {
            "Good" => Ok(Response::Good),
            status @ "GoodWithNotification" => {
                Ok(Response::GoodWithNotification(status.to_string()))
            }
            "Error" => {
                let Some(Value::Datetime(at)) = o.get("at") else {
                    return Err(Error::thrown("Error trying to get 'at' field".to_string()));
                };
                let Some(Value::String(reason)) = o.get("reason") else {
                    return Err(Error::thrown(
                        "Error trying to get 'reason' field".to_string(),
                    ));
                };
                Ok(Response::Error(MyError {
                    at: at.clone(),
                    reason: reason.clone(),
                }))
            }
            _ => Err(Error::thrown("No status field for some reason".to_string())),
        }
    }

    fn is_value(value: &Value) -> bool {
        value.is_kind(&Self::kind_of())
    }
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("main").use_db("main").await.unwrap();

    // Turning DB results into Rust enum
    let mut statuses = db.query("
        { status: 'Good' };
        { status: 'GoodWithNotification', notification: 'We need things to make us go. We need help.' };
        { status: 'Error', at: d'1914-07-28', reason: 'General conflagration'};
    ").await.unwrap();

    println!(
        "Good: {}",
        statuses
            .take::<Option<Response>>(0)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql_pretty()
    );
    println!(
        "Good with notification: {}",
        statuses
            .take::<Option<Response>>(1)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql_pretty()
    );
    println!(
        "Error: {}",
        statuses
            .take::<Option<Response>>(2)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql_pretty()
    );

    // Turn Rust enum into Values,
    // use them in the CONTENT clause
    // and then print the result
    let good = Response::Good;
    let good_but = Response::GoodWithNotification("Keep it up!".into());
    let error = Response::Error(MyError {
        at: Datetime::now(),
        reason: "Error: can't think of interesting error message".into(),
    });

    println!(
        "Good: {:?}",
        db.query("CREATE result CONTENT $content")
            .bind(("content", good))
            .await
            .unwrap()
            .take::<Option<Value>>(0)
            .unwrap()
            .unwrap()
            .to_sql()
    );
    println!(
        "Good but: {:?}",
        db.query("CREATE result CONTENT $content")
            .bind(("content", good_but))
            .await
            .unwrap()
            .take::<Option<Value>>(0)
            .unwrap()
            .unwrap()
            .to_sql()
    );
    println!(
        "Error: {:?}",
        db.query("CREATE result CONTENT $content")
            .bind(("content", error))
            .await
            .unwrap()
            .take::<Option<Value>>(0)
            .unwrap()
            .unwrap()
            .to_sql()
    );
}
```

## Value construction macros

Alongside `kind!`, the crate exports four macros for building values by hand: `object!`, `array!`, `set!` and `vars!`. Each is available from `surrealdb::types` as well as from `surrealdb_types` directly.

| Macro | Builds | Notes |
| ----- | ------ | ----- |
| `object!` | `Object` | Keys can be bare identifiers or quoted string literals |
| `array!` | `Array` | Uses square brackets, like `vec!` |
| `set!` | `Set` | Deduplicates its items; takes `Value`s, not raw Rust values |
| `vars!` | `Variables` | Same syntax as `object!`, used for `.bind()` after `.query()` |

Values passed to `object!`, `array!` and `vars!` only need to implement `SurrealValue`, so ordinary Rust types can be used directly.

```rust
use surrealdb::types::{Value, array, object, set};

fn main() {
    let obj = object! {
        name: "Aeon",
        age: 30,
        "home-town": "Bregna",
    };

    let arr = array![1, "two", true];

    let tags = set! {
        Value::from_t("rust"),
        Value::from_t("surrealdb"),
        Value::from_t("rust"),
    };

    println!("{obj:?}");
    println!("{arr:?}");
    println!("{tags:?}");
}
```

Output:

```text
Object({"age": Number(Int(30)), "home-town": String("Bregna"), "name": String("Aeon")})
Array([Number(Int(1)), String("two"), Bool(true)])
Set({String("rust"), String("surrealdb")})
```

Note the two differences between `array!` and `set!`. `set!` drops the duplicate `"rust"`, and it does not convert its items for you: each one must already be a `Value`. Passing `set! { 1, 2, 3 }` will not compile, while `array![1, 2, 3]` will.

### The `vars!` macro

`vars!` builds a `Variables` map, which is what the [`.bind()`](/docs/reference/rust/methods/query.md#binding-parameters) method on a query takes. It is the most direct way to pass more than one parameter into a query.

```rust
use surrealdb::engine::any::connect;
use surrealdb::types::{RecordId, SurrealValue, vars};

#[derive(Debug, SurrealValue)]
struct Person {
    id: RecordId,
    name: String,
    age: i64,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("mem://").await?;
    db.use_ns("main").use_db("main").await?;

    let sql = "
        CREATE type::table($table) SET name = $name, age = $age;
        SELECT * FROM type::table($table) WHERE age >= $min_age;
    ";

    let mut result = db
        .query(sql)
        .bind(vars! {
            table: "person",
            name: "Aeon",
            age: 30,
            min_age: 18,
        })
        .await?;

    let created: Option<Person> = result.take(0)?;
    dbg!(created);
    let adults: Vec<Person> = result.take(1)?;
    dbg!(adults);
    Ok(())
}
```

`Variables` is an ordinary struct as well as a macro target. `Variables::new()` followed by `.insert()` builds the same value at runtime, which is the better choice when the set of parameters is not known when the code is written.

## Convenience methods for the `Value` type

Importing the `SurrealValue` trait gives access to a lot of convenience methods.

One example is the `.into_value()` method which converts a large number of Rust standard library types into a SurrealQL `Value`.

```rust
use surrealdb_types::{SurrealValue, Value};

fn main() {
    let string_val = "string".into_value();
    assert!(string_val.is_string());
    assert_eq!(string_val, Value::String("string".into()));
}
```

One more example of `.into_value()` to convert a `HashMap<String, &'str>` into a `Value`:

```rust
use std::collections::HashMap;

use surrealdb::engine::any::connect;
use surrealdb_types::{SurrealValue, Value};

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("db").use_db("db").await.unwrap();

    let mut map = HashMap::new();
    map.insert("name".to_string(), "Billy");
    map.insert("id".to_string(), "person:one");

    // Turn HashMap into SurrealDB Value
    let as_person = map.into_value();

    // Object(Object({"id": String("person:one"), "name": String("Billy")}))
    println!("{as_person:?}");

    // Insert it into a query to create a record
    let res = db
        .query("CREATE ONLY person CONTENT $person")
        .bind(("person", as_person))
        .await
        .unwrap()
        .take::<Value>(0)
        .unwrap();

    // Object(Object({"id": RecordId(RecordId { table: "person", key: String("person:one") }), "name": String("Billy")}))
    println!("{res:?}");
}
```

A `Value` can be manually constructed using any of the various structs and enums contained within it. This is particularly useful when constructing a complex ID made up of a table name and an array for the key.

```rust
use std::str::FromStr;

use surrealdb::engine::any::connect;
use surrealdb_types::{Array, Datetime, RecordId, RecordIdKey, Value};

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("db").use_db("db").await.unwrap();

    let date = "2025-10-13T05:16:11.343Z";

    let complex_id = RecordId {
        table: "weather".into(),
        key: RecordIdKey::Array(Array::from(vec![
            Value::String("London".to_string()),
            Value::Datetime(Datetime::from_str(date).unwrap()),
        ])),
    };

    let mut res = db
        .query("CREATE ONLY weather SET id = $id")
        .bind(("id", complex_id))
        .await
        .unwrap();

    // Object(Object({"id": RecordId(RecordId { table: "weather", key: Array(Array([String("London"), Datetime(Datetime(2025-10-13T05:16:11.343Z))])) })}))
    println!("{:?}", res.take::<Value>(0).unwrap());
}
```

The `.is()` method for a `Value` returns `true` if the type(s) in question can be converted to the type indicated when the method is called.

```rust
use std::collections::HashMap;
use surrealdb_types::SurrealValue;

fn main() {
    // true
    println!("{}", "string".into_value().is::<String>());

    let mut map = HashMap::new();
    map.insert("name".to_string(), "Billy");
    map.insert("id".to_string(), "person:one");

    // true
    println!("{}", map.clone().into_value().is::<HashMap<String, &str>>());
    // Also true
    println!("{}", map.into_value().is::<HashMap<String, String>>());
}
```

A `Value` can be converted into a `serde_json::Value` using the `.into_json_value()` method, and vice versa using `.into_value()`.

```rust
use surrealdb::engine::any::connect;
use surrealdb_types::{SurrealValue, Value};

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("db").use_db("db").await.unwrap();

    let value = db
        .query("CREATE ONLY person:one SET age = 21")
        .await
        .unwrap()
        .take::<Value>(0)
        .unwrap();

    // Object(Object({"age": Number(Int(21)), "id": RecordId(RecordId { table: "person", key: String("one") })}))
    println!("{value:?}");
    // Object {"age": Number(21), "id": String("person:one")}
    println!("{:?}", value.clone().into_json_value());

    // Round trip
    value.into_json_value().into_value();
}
```

---

Source: https://surrealdb.com/docs/reference/rust/embedding

# Embedding

In Rust, SurrealDB can be run as an in-memory database, it can persist data using a file-based storage engine, or on a distributed cluster.

SurrealDB is designed to be run in many different ways and in many environments. Due to the [separation of the storage and API layers](/docs/learn/data-models/architecture.md), SurrealDB can be run in embedded mode, from within a number of different language environments. In Rust, SurrealDB can be run as an in-memory database, it can persist data using a file-based storage engine, or on a distributed cluster.

## Install the SDK

First, create a new project using `cargo new` and add the SurrealDB crate to your dependencies, enabling the key-value store you need:

```sh
# For an in memory database
cargo add surrealdb --features kv-mem

# For a RocksDB file
cargo add surrealdb --features kv-rocksdb
```

You will need to add the following additional dependencies:

```bash
cargo add serde --features derive
cargo add tokio --features macros,rt-multi-thread
```

<br />

## Connect to SurrealDB

Open `src/main.rs` and replace everything in there with the following code to try out some basic operations using the SurrealDB SDK with an embedded database. Look at [integrations to connect to a database](/docs/reference/rust.md).

```rust
use serde::{Deserialize, Serialize};
use surrealdb::types::RecordId;
use surrealdb::Surreal;

// For an in memory database
use surrealdb::engine::local::Mem;

// For a RocksDB file
// use surrealdb::engine::local::RocksDb;

#[derive(Debug, Serialize)]
struct Name<'a> {
    first: &'a str,
    last: &'a str,
}

#[derive(Debug, Serialize)]
struct Person<'a> {
    title: &'a str,
    name: Name<'a>,
    marketing: bool,
}

#[derive(Debug, Serialize)]
struct Responsibility {
    marketing: bool,
}

#[derive(Debug, Deserialize)]
struct Record {
    #[allow(dead_code)]
    id: RecordId,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Create database connection in memory
    let db = Surreal::new::<Mem>(()).await?;
    
    // Create database connection using RocksDB
    // let db = Surreal::new::<RocksDb>("path/to/database-folder").await?;

    // Select a specific namespace / database
    db.use_ns("main").use_db("main").await?;

    // Create a new person with a random id
    let created: Option<Record> = db
        .create("person")
        .content(Person {
            title: "Founder & CEO",
            name: Name {
                first: "Tobie",
                last: "Morgan Hitchcock",
            },
            marketing: true,
        })
        .await?;
    dbg!(created);

    // Update a person record with a specific id
    let updated: Option<Record> = db
        .update(("person", "jaime"))
        .merge(Responsibility { marketing: true })
        .await?;
    dbg!(updated);

    // Select all people records
    let people: Vec<Record> = db.select("person").await?;
    dbg!(people);

    // Perform a custom advanced query
    let groups = db
        .query("SELECT marketing, count() FROM type::table($table) GROUP BY marketing")
        .bind(("table", "person"))
        .await?;
    dbg!(groups);

    Ok(())
}
```

Run your program from the command line with:

```sh
cargo run
```

<br />

## SDK methods

The Rust SDK comes with a number of built-in 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="#init"><code>Surreal::init()</code></a></td>
            <td scope="row" data-label="Description">Initialises a static database engine</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#connect"><code>db.connect(endpoint)</code></a></td>
            <td scope="row" data-label="Description">Connects to a specific database endpoint, saving the connection on the static client</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#new"><code>{"Surreal::new::<T>(endpoint)"}</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="#use-ns-db"><code>db.use_ns(namespace).use_db(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="Function"><a href="#signup"><code>db.signup(credentials)</code></a></td>
            <td scope="row" data-label="Description">Signs up a user using a specific record access method</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#signin"><code>db.signin(credentials)</code></a></td>
            <td scope="row" data-label="Description">Signs this connection in using a specific access method or system user</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#invalidate"><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="#authenticate"><code>db.authenticate(token)</code></a></td>
            <td scope="row" data-label="Description">Authenticates the current connection with a JSON Web Token</td>
        </tr>
        <tr>
            <td scope="row" data-label="Function"><a href="#set"><code>db.set(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="#query"><code>db.query(sql)</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="Function"><a href="#select"><code>db.select(resource)</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="Function"><a href="#create"><code>db.create(resource).content(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="#update-content"><code>db.update(resource).content(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="#update-merge"><code>db.update(resource).merge(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="#update-patch"><code>db.update(resource).patch(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="#delete"><code>db.delete(resource)</code></a></td>
            <td scope="row" data-label="Description">Deletes all records, or a specific record</td>
        </tr>
    </tbody>
</table>

<br />

## `.init()` {#init}

The DB static singleton ensures that a single database instance is available across very large or complicated applications. With the singleton, only one connection to the database is instantiated, and the database connection does not have to be shared across components or controllers.

```rust title="Method Syntax"
Surreal::init()
```

### Example usage
```rust
static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Connect to the database
    DB.connect::<Wss>("cloud.surrealdb.com").await?;
    // Select a namespace + database
    DB.use_ns("main").use_db("main").await?;
    // Create or update a specific record
    let tobie: Option<Record> = DB
        .update(("person", "tobie"))
        .content(Person { name: "Tobie" })
        .await?;
    Ok(())
}
```

<br />

## `.connect()` {#connect}

Connects to a local or remote database endpoint.

```rust title="Method Syntax"
db.connect(endpoint)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Argument</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>endpoint</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The database endpoint to connect to.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
// Connect to a local endpoint
DB.connect::<Ws>("127.0.0.1:8000").await?;
// Connect to a remote endpoint
DB.connect::<Wss>("cloud.surrealdb.com").await?;
```

<br />

## `.new()` {#new}

Connects to a local or remote database endpoint.

```rust title="Method Syntax"
Surreal::new::<T>(endpoint)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>endpoint</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The database endpoint to connect to.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
```

<br />

## `.use_ns()` and `.use_db()` {#use-ns-db}

Switch to a specific namespace and database.

```rust title="Method Syntax"
db.use_ns(ns).use_db(db)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>ns</code>
                <label label="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>db</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Switches to a specific database.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
db.use_ns("main").use_db("main").await?;
```

<br />

## `.signup()` {#signup}

Signs up using a specific record access method.

```rust title="Method Syntax"
db.signup(credentials)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">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">
                Variables used in a signup query.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
use serde::Serialize;
use surrealdb::opt::auth::Scope;

#[derive(Serialize)]
struct Credentials<'a> {
    email: &'a str,
    pass: &'a str,
}

let jwt = db.signup(Scope {
    namespace: "main",
    database: "main",
    access: "user",
    params: Credentials {
        email: "info@surrealdb.com",
        pass: "123456",
    },
}).await?;

// ⚠️: It is important to note that the token should be handled securely and protected from unauthorized access.
let token = jwt.as_insecure_token();
```

<br />

## `.signin()` {#signin}

Signs in using a specific access method or system user.

```rust title="Method Syntax"
db.signin(credentials)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">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">
                Variables used in a signin query.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
use serde::Serialize;
use surrealdb::opt::auth::Scope;

#[derive(Serialize)]
struct Credentials<'a> {
    email: &'a str,
    pass: &'a str,
}

let jwt = db.signin(Scope {
    namespace: "main",
    database: "main",
    access: "user",
    params: Credentials {
        email: "info@surrealdb.com",
        pass: "123456",
    },
}).await?;

// ⚠️: It is important to note that the token should be handled securely and protected from unauthorized access.
let token = jwt.as_insecure_token();
```

<br />

## `.invalidate()` {#invalidate}

Invalidates the authentication for the current connection.

```rust title="Method Syntax"
db.invalidate(credentials)
```

### Example usage
```surql
db.invalidate().await?;
```

<br />

## `.authenticate()` {#authenticate}

Authenticates the current connection with a JWT token.

```rust title="Method Syntax"
db.authenticate(token)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">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="Description">
                The JWT authentication token.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
db.authenticate(jwt).await?;
```

<br />

## `.set()` {#set}

Assigns a value as a parameter for this connection.

```rust title="Method Syntax"
db.set(key, val)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">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="Description">
                Specifies the name of the variable.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>val</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns the value to the variable name.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
// Assign the variable on the connection
db.set("name", Name {
    first: "Tobie",
    last: "Morgan Hitchcock",
}).await?;
// Use the variable in a subsequent query
db.query("CREATE person SET name = $name").await?;
// Use the variable in a subsequent query
db.query("SELECT * FROM person WHERE name.first = $name.first").await?;
```

<br />

## `.query()` {#query}

Runs a set of SurrealQL statements against the database.

```rust title="Method Syntax"
db.query(sql).bind(vars)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">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)?;
```

<br />

## `.select()` {#select}

Selects all records in a table, or a specific record, from the database.

```rust title="Method Syntax"
db.select(resource)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>resource</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
// Select all records from a table
let people: Vec<Person> = db.select("person").await?;
// Select a specific record from a table
let person: Option<Person> = db.select(("person", "h5wxrf2ewk8xjxosxtyc")).await?;
```

### Translated query
This function will run the following query in the database:

```surql
SELECT * FROM $resource;
```

<br />

## `.create()` {#create}

Creates a record in the database.

```rust title="Method Syntax"
db.create(resource).content(data)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>resource</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID 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="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
// Create a record with a random ID
let person: Option<Person> = db.create("person").await?;
// Create a record with a specific ID
let record: Record = db
    .create(("person", "tobie"))
    .content(Person {
        name: "Tobie",
        settings: {
            active: true,
            marketing: true,
       },
    }).await?;
```

### Translated query
This function will run the following query in the database:

```surql
CREATE $resource CONTENT $data;
```

<br />

## `.update().content()` {#update-content}

Updates all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.update(resource).content(data)
```

> [!NOTE]
> This function replaces the current document / record data with the specified data.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>resource</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID 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="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
// Update all records in a table
let people: Vec<Person> = db.update("person").await?;
// Update a record with a specific ID
let person: Option<Person> = db
    .update(("person", "tobie"))
    .content(Person {
        name: "Tobie",
        settings: {
            active: true,
            marketing: true,
        },
    }).await?;
```

### Translated query
This function will run the following query in the database:

```surql
UPDATE $resource CONTENT $data;
```

<br />

## `.update().merge()` {#update-merge}

Modifies all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.update(resource).merge(data)
```

> [!NOTE]
> This function merges the current document / record data with the specified data.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>resource</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID 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="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
// Update all records in a table
let people: Vec<Person> = db.update("person")
    .merge(Document {
        updated_at: Datetime::default(),
    })
    .await?;
// Update a record with a specific ID
let person: Option<Person> = db.update(("person", "tobie"))
    .merge(Document {
        updated_at: Datetime::default(),
        settings: Settings {
            active: true,
        },
    })
    .await?;
```

### Translated query
This function will run the following query in the database:

```surql
UPDATE $resource MERGE $data;
```

<br />

## `.update().patch()` {#update-patch}

Applies JSON Patch changes to all records, or a specific record, in the database.

```rust title="Method Syntax"
db.update(resource).patch(data)
```

> [!NOTE]
> This function patches the current document / record data with the specified JSON Patch data.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>resource</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to modify.
            </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 JSON Patch data with which to modify the records.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
// Update all records in a table
let people: Vec<Person> = db.update("person")
    .patch(PatchOp::replace("/created_at", Datetime::default()))
    .await?;

// Update a record with a specific ID
let person: Option<Person> = db.update(("person", "tobie"))
    .patch(PatchOp::replace("/settings/active", false))
    .patch(PatchOp::add("/tags", &["developer", "engineer"]))
    .patch(PatchOp::remove("/temp"))
    .await?;
```

### Translated query
This function will run the following query in the database:

```surql
UPDATE $resource PATCH $data;
```

<br />

## `.delete()` {#delete}

Deletes all records in a table, or a specific record, from the database.

```rust title="Method Syntax"
db.delete(resource)
```

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2">Arguments</th>
            <th colspan="2">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>resource</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
// Delete all records from a table
let people: Vec<Person> = db.delete("person").await?;
// Delete a specific record from a table
let person: Option<Person> = db.delete(("person", "h5wxrf2ewk8xjxosxtyc")).await?;
```

### Translated query
This function will run the following query in the database:

```surql
DELETE FROM $resource RETURN BEFORE;
```

---

Source: https://surrealdb.com/docs/reference/rust/frameworks

# Crates

The following pages each contain a tutorial that walks through the setting up of a web server (for Actix, Axum, and Rocket) or a UI (for Egui) that uses SurrealDB as its storage backend.

The following pages each contain a tutorial that walks through the setting up of a web server (for Actix, Axum, and Rocket) or a UI (for Egui) that uses SurrealDB as its storage backend.

- [Actix](/docs/reference/rust/frameworks/actix.md)

- [Axum](/docs/reference/rust/frameworks/axum.md)

- [Egui](/docs/reference/rust/frameworks/egui.md)

- [Rocket](/docs/reference/rust/frameworks/rocket.md)

---

Source: https://surrealdb.com/docs/reference/rust/frameworks/actix

# Actix

The SDK for Rust allows SurrealDB to be used as the storage backend for an Actix web server

**3.x**

The following tutorial will set up a server with SurrealDB and [Actix Web](https://actix.rs/) that has a few endpoints:

* Some endpoints to demonstrate how the HTTP endpoints work to create, select, modify etc. a `person` table in a database,
* Other endpoints to display some helpful info for the user,
* Two endpoints to allow signing up and signing in as a [record user](/docs/reference/query-language/statements/define/access/record.md).

## Getting started

First, open up a terminal window and use the following command to start an empty database. This will also define a namespace and database by the name of "main" by default.

```bash
surreal start --user root --pass secret
```

You can also use the [Start serving](/docs/explore/studio.md) button on [SurrealDB Studio](/docs/explore/studio.md) to do the same if you have it installed locally.

The database initiated by the [surreal start](/docs/reference/cli/surrealdb-cli/commands/start.md) command stores data in memory by default, which then disappears every time the database is shut down. As such, you can simply use Ctrl+C every time you want to start the database anew with no existing definitions or data. To save data to disk which will persist after shutting down, add a [positional argument](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument) for one of the storage backends such as `rocksdb://mydatabase` or `surrealkv://mydatabase` (or `surrealkv+versioned//mydatabase` to include SurrealKV versioning).

With the database running, we will now connect to the database "main" located in the namespace "main". You can connect to it by [creating a connection](/docs/explore/studio.md) inside SurrealDB Studio, or by using the following command to start an interactive shell.

```bash
surreal sql --user root --pass secret --pretty
```

Next, create a new Rust project with the command `cargo new your_project_name`, go into the newly created directory, and use `cargo add` to add each of the following dependencies:

* `surrealdb` (of course),
* `actix-web`,
* `thiserror`, to make it easy to convert between SurrealDB's error type, other errors and Actix's response types,
* `fake`, to create random user names that can be used to sign in to the database as a record user.

## Starting the Rust code

The first thing to do is a bit of groundwork to convert database errors into an error type of our own. Implementing `From<surrealdb::Error>` for this type will let it be used with the `?` operator when handling results. Finally, it will also need to implement Actix's `ResponseError` trait so that it can be used as output for the server. All of this can be done manually if you prefer, but the `thiserror` crate saves a certain amount of typing.

```rust
mod error {
    use actix_web::{HttpResponse, ResponseError};
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db(String),
    }

    impl ResponseError for Error {
        fn error_response(&self) -> HttpResponse {
            match self {
                Error::Db(e) => HttpResponse::InternalServerError().body(e.to_string()),
            }
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db(error.to_string())
        }
    }
}
```

Next, we will put the database client together. Actix provides an [`.app_data()`](https://actix.rs/docs/application/#state) method when starting a router that would give us access to the database inside its functions. However, for simplicity we can instead wrap the client inside a `LazyLock` to make it into a global static.

```rust
use std::sync::LazyLock;
static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);
```

Inside `main()`, we will do the following:

* Connect to the database running at `localhost:8000`
* Sign in as the root user that was created through the `surreal start` command
* Use (move to) the namespace "main" and database "main"
* Use the `.query()` method to pass in a few definitions for the database.

```rust
DB.connect::<Ws>("localhost:8000").await?;

DB.signin(Root {
    username: "root",
    password: "secret",
})
.await?;

DB.use_ns("main").use_db("main").await?;

DB.query(
    "
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;

DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
SIGNUP ( CREATE user SET name = $name,
    pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
)
.await?;
```

## What the database definitions do

The first item that stands out with the definitions above is that they all contain a `IF NOT EXISTS` clause. As the `DEFINE` statements will be executed every time the app starts, it is possible that they might be executed on a database that already has the definitions in place. SurrealDB returns an error if a definition already exists, requiring the `OVERWRITE` clause if a definition needs to be redone.

Note that this would not affect our app, as this would still be a successful usage of the `.query()` method. Instead, its output would contain a number of error results that could be handled individually:

```text
IndexedResults { results: {0: (DbResultStats { execution_time: Some(44.75µs), query_type: Some(Other) }, Err(Thrown("The table 'person' already exists"))), 1: (DbResultStats { execution_time: Some(87.042µs), query_type: Some(Other) }, Err(Thrown("The field 'name' already exists"))), 2: (DbResultStats { execution_time: Some(21.125µs), query_type: Some(Other) }, Err(Thrown("The field 'created_by' already exists"))), 3: (DbResultStats { execution_time: Some(13.584µs), query_type: Some(Other) }, Err(Thrown("The index 'unique_name' already exists"))), 4: (DbResultStats { execution_time: Some(36.125µs), query_type: Some(Other) }, Err(Thrown("The access method 'account' already exists in the database 'main'")))}, live_queries: {} }
```

Now let's go over each of the definitions to see what they do.

The first three statements define a `person` table. This table is schemaless, but has one required field `name`, which must be present and must be a string. This table has defined permissions by which a record user is able to use `CREATE` and `SELECT` on the `person` table, but can only `UPDATE` and `DELETE` records that it has created. The root user, however, is not subject to permissions rules.

The way these permissions are set is by using the `$auth` parameter. This parameter has a value whenever a record user is set as the authorised used for the database. The `WHERE $auth` clause simply means "where a value exists for the parameter `$auth`" (`WHERE $auth IS NOT NONE` would also work in this case). But for `UPDATE` and `DELETE` queries, it is not enough for `$auth` to just be present, the `created_by` field of a `person` record must also match the ID of the currently authenticated user.

This `created_by` field is automatically generated from its definition in the `DEFINE FIELD` statement. It is given the value of `$auth`, and is `READONLY` and thus cannot be changed. When logged in as a system user (like a root user), its value will be `NONE`. But when logged in as a record user, its value will be something like `user:qx2apv5oc8mh03wtah0q`.

```surql
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by
  ON TABLE person VALUE $auth READONLY;
```

So where does an ID like `user:qx2apv5oc8mh03wtah0q` come from? This is thanks to the following definitions that set the signup and signin behaviour of the record users. A typical [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) statement will create some sort of record on signup (in this case, a `user`) record, and will compare it against a password during signin. Note that the access has a name that we gave it (`account`), so that it can be referenced elsewhere.

In addition, a `DEFINE INDEX` statement with a `UNIQUE` clause is used to ensure that no two users can have the same name.

```surql
    DEFINE INDEX IF NOT EXISTS unique_name
      ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name,
	  pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name
	  AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
```

For an actual user in production, you would probably want to require an email and some other fields. Functions like [`string::is_email`](/docs/reference/query-language/functions/database-functions/string.md#stringis_email) can be used to ensure that the value passed in is valid.

```surql
DEFINE FIELD email ON TABLE user TYPE string ASSERT $value.is_email();
```

However, for this simple example, each user will simply have a unique name and a password. The password will be stored in hashed and salted form on the database, making it unique and unreadable every time it is generated. The only way to check if it is correct is by using a [compare function](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) of the output with an attempted password. Here is a short SurrealQL sample to show how the process works.

```surql
LET $hash1 = crypto::argon2::generate("myPaSSWord");
LET $hash2 = crypto::argon2::generate("myPaSSWord");

RETURN [$hash1, $hash2];
-- First returns true, second returns false
RETURN [
    crypto::argon2::compare($hash1, "myPaSSWord")
    crypto::argon2::compare($hash1, "Wrongpassword")
];
```

## The rest of the code

The last step is where the majority of the work takes place: setting up the paths for Actix to handle, and writing the functions that handle the endpoints and (usually) access the database to handle the request. To start, we'll create a function for the `"/"` root path to display a helpful message to anybody giving the server a try via the browser or an app like curl or Postman. These paths and curl examples can all be seen on [the page for SurrealDB's HTTP endpoints](/docs/reference/rest-api/http-protocol.md).

```rust
    #[get("/")]
    pub async fn paths() -> &'static str {
        r#"

-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John Doe"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane Doe"}' http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                          http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }
```

Each of these functions will be put into a mod called `routes`, leading to the following code inside `main()`.

```rust
HttpServer::new(|| {
    App::new()
        .service(routes::create_person)
        .service(routes::read_person)
        .service(routes::update_person)
        .service(routes::delete_person)
        .service(routes::list_people)
        .service(routes::paths)
        .service(routes::session)
        .service(routes::make_new_user)
        .service(routes::get_new_token)
})
.bind(("localhost", 8080))?
.run()
.await?;
```

Many functions require some JSON data from the user, which will be deserialised into a `PersonData` struct. The database can then use it in methods like `.create().content()`. The output returned will now have a `name` and an `id`, which the `Person` struct holds.

These structs would normally only need to implement `SurrealValue`, but also implement `Serialize` and `Deserialize` because they are used in actix functions that require them.

```rust
#[derive(SurrealValue, Deserialize)]
pub struct PersonData {
    name: String,
}

#[derive(SurrealValue, Deserialize, Serialize)]
pub struct Person {
    name: String,
    id: RecordId,
}
```

Each of these functions are pretty straightforward: obtain some user input, initiate a query, feed the user input into it, and return it as JSON.

```rust
const PERSON: &str = "person";

#[post("/person/{id}")]
pub async fn create_person(
    id: Path<String>,
    person: Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB
        .create((PERSON, id.into_inner()))
        .content(person.into_inner())
        .await?;
    Ok(Json(person))
}

#[get("/person/{id}")]
pub async fn read_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
    let person = DB.select((PERSON, id.into_inner())).await?;
    Ok(Json(person))
}

#[put("/person/{id}")]
pub async fn update_person(
    id: Path<String>,
    person: Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB
        .update((PERSON, id.into_inner()))
        .content(person.into_inner())
        .await?;
    Ok(Json(person))
}

#[delete("/person/{id}")]
pub async fn delete_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
    let person = DB.delete((PERSON, id.into_inner())).await?;
    Ok(Json(person))
}

#[get("/people")]
pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
    let people = DB.select(PERSON).await?;
    Ok(Json(people))
}
```

The `session()` function is also quite small, and is just a convenience for a user curious about the current session data. As the `.query()` method can take more than one statement, it returns each of these responses in order with an index for each (starting at 0). The `.take()` method can then be used to access the response at that index, and turn it into anything that can be deserialised back into a Rust type. In our case, a `String` is all we need here as the output will only be used to show the user the current session info.

```rust
pub async fn session() -> Result<Json<String>, Error> {
    let res: Option<String> = DB.query("<string>$session").await?.take(0)?;

    Ok(Json(res.unwrap_or("No session data found!".into())))
}
```

The most interesting function is the one used to create a new record user. To make it really easy to try out the experience of logging in as a record user, this function will use create a random name and password each time it is accessed. It will then pass in a [`Record`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Record.html) struct which is used to sign up a new record user. Note the following:

* The access name is `"account"`, which is the name we chose in the `DEFINE ACCESS` statement above.
* The `params` field takes anything that implements `Serialize`, in this case a struct we put together called `Params`.
* The `.signup()` method returns a `Token` with a field called `access` which holds an `AccessToken`. To make the actual token visible, you can use the `.into_insecure_token()` method as we have done here. As a small guide to getting started, this example is not concerned about security. However, if you are looking to create something more production-worthy, do take a look at the [security](/docs/learn/security.md) section of the documentation and the [security best practices](/docs/learn/security/best-practices/security-best-practices.md) page.

The function will then end with an output showing the username, password, token, and instructions for how to log in using the CLI. This can be copied and pasted to begin making queries immediately.

```rust
#[derive(SurrealValue)]
struct Params {
    name: String,
    pass: String,
}

#[get("/new_user")]
pub async fn make_new_user() -> Result<String, Error> {
    let name: String = FirstName().fake();
    let pass: String = FirstName().fake();
    let jwt = DB
        .signup(Record {
            access: "account".to_string(),
            namespace: "main".to_string(),
            database: "main".to_string(),
            params: Params {
                name: name.clone(),
                pass: pass.clone(),
            },
        })
        .await?
        .access
        .into_insecure_token();
    Ok(format!(
        "New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --pretty --token \"{jwt}\""
    ))
}
```

A record user with an expired token can use the `/signin` endpoint to get a new token. Since this requires passing in a username and password, we'll just have this function return a `String` that contains a curl example to get a new token.

```rust
#[get("/new_token")]
pub async fn get_new_token() -> String {
    let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
    format!(
        "Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --pretty --token YOUR_TOKEN_HERE"
    )
}
```

## Experimenting with the app

The final code looks like this:

```rust
use actix_web::{App, HttpServer};
use std::sync::LazyLock;
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Client;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;

static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);

mod error {
    use actix_web::{HttpResponse, ResponseError};
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db(String),
    }

    impl ResponseError for Error {
        fn error_response(&self) -> HttpResponse {
            match self {
                Error::Db(e) => HttpResponse::InternalServerError().body(e.to_string()),
            }
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db(error.to_string())
        }
    }
}

mod routes {

    use fake::Fake;
    use fake::faker::name::en::FirstName;
    use surrealdb::opt::auth::Record;

    use crate::DB;
    use crate::error::Error;
    use actix_web::web::{Json, Path};
    use actix_web::{delete, get, post, put};
    use serde::{Deserialize, Serialize};
    use surrealdb::types::{RecordId, SurrealValue};
    const PERSON: &str = "person";

    #[derive(SurrealValue, Deserialize)]
    pub struct PersonData {
        name: String,
    }

    #[derive(SurrealValue, Deserialize, Serialize)]
    pub struct Person {
        name: String,
        id: RecordId,
    }

    #[get("/")]
    pub async fn paths() -> &'static str {
        r#"

-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John Doe"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane Doe"}' http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                          http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }

    #[get("/session")]
    pub async fn session() -> Result<Json<String>, Error> {
        let res: Option<String> = DB.query("<string>$session").await?.take(0)?;

        Ok(Json(res.unwrap_or("No session data found!".into())))
    }

    #[post("/person/{id}")]
    pub async fn create_person(
        id: Path<String>,
        person: Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB
            .create((PERSON, id.into_inner()))
            .content(person.into_inner())
            .await?;
        Ok(Json(person))
    }

    #[get("/person/{id}")]
    pub async fn read_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
        let person = DB.select((PERSON, id.into_inner())).await?;
        Ok(Json(person))
    }

    #[put("/person/{id}")]
    pub async fn update_person(
        id: Path<String>,
        person: Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB
            .update((PERSON, id.into_inner()))
            .content(person.into_inner())
            .await?;
        Ok(Json(person))
    }

    #[delete("/person/{id}")]
    pub async fn delete_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
        let person = DB.delete((PERSON, id.into_inner())).await?;
        Ok(Json(person))
    }

    #[get("/people")]
    pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
        let people = DB.select(PERSON).await?;
        Ok(Json(people))
    }

    #[derive(SurrealValue)]
    struct Params {
        name: String,
        pass: String,
    }

    #[get("/new_user")]
    pub async fn make_new_user() -> Result<String, Error> {
        let name: String = FirstName().fake();
        let pass: String = FirstName().fake();
        let jwt = DB
            .signup(Record {
                access: "account".to_string(),
                namespace: "main".to_string(),
                database: "main".to_string(),
                params: Params {
                    name: name.clone(),
                    pass: pass.clone(),
                },
            })
            .await?
            .access
            .into_insecure_token();
        Ok(format!(
            "New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --pretty --token \"{jwt}\""
        ))
    }

    #[get("/new_token")]
    pub async fn get_new_token() -> String {
        let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
        format!(
            "Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --pretty --token YOUR_TOKEN_HERE"
        )
    }
}

#[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    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?;

    DB.query(
        "DEFINE TABLE person SCHEMALESS
        PERMISSIONS FOR 
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD name ON TABLE person TYPE string;
    DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS account ON DATABASE TYPE RECORD
		SIGNUP ( CREATE user SET name = $name,
	    pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;

    HttpServer::new(|| {
        App::new()
            .service(routes::create_person)
            .service(routes::read_person)
            .service(routes::update_person)
            .service(routes::delete_person)
            .service(routes::list_people)
            .service(routes::paths)
            .service(routes::session)
            .service(routes::make_new_user)
            .service(routes::get_new_token)
    })
    .bind(("localhost", 8080))?
    .run()
    .await?;

    Ok(())
}
```

As the database client is logged in as a root user, the `/person/` routes can be used to perform any operation on the `person` records of the database.

You can also log in to the CLI or SurrealDB Studio as a root user and separately as a record user using the output of the `/new_user` endpoint to compare the experience between the two.

For example, the output when creating a `person` record as a root user will look like this:

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]
```

But as a record user, it will include a `created_by` field, set by the value found at the `$auth` paremeter.

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8syfiq2ovztn2tbr8mhb,
		name: 'Aeon'
	}
]
```

As a result, a `DELETE person RETURN BEFORE` statement (which deletes all `person` records and returns the records deleted) used by a record user will only delete the single record that it created earlier. The following `SELECT` statement shows that the `person` record created by the root user cannot be deleted or modified by the record user.

```bash
main/main> DELETE person RETURN BEFORE;
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8y06y06jmmb7e58trckz,
		name: 'Aeon'
	}
]

main/main> SELECT * FROM person;
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]

main/main> UPDATE person SET name = "Yogurt";
-- Query 1
[]
```

Also note that the root user is able to see the `user` tables and their information. A record user cannot, as a record user by default has no permissions except what it is given by the `PERMISSIONS` clause. If you create a record user using the `/new_user` endpoint, the root user will be able to view it. However, the password has been obscured by the `crypto::argon2::generate` function so that nobody else can use it.

```surql
[
	{
		id: user:qx2apv5oc8mh03wtah0q,
		name: 'Gerard',
		pass:
		  '$argon2id$v=19$m=19456,t=2,p=1$j0ktTqUxRjOWYnwS5LoMFQ$2NcGkf5+IuLml6NorPy/Le6T8RppYXTXakwY5cDiZPY'
	}
]
```

## Further steps

Now that you have a running Actix server with SurrealDB as the backend, here are some other ideas that you might want to explore.

* Using the [`AUTHENTICATE`](/docs/reference/query-language/statements/define/access/record.md#example-failing-authentication) clause inside the `DEFINE ACCESS` statement. This will result in increased performance thanks to only being executed once, compared to permissions checks which are executed for each query.
* Adding some interesting behaviour to the database such as [changefeeds](/docs/reference/query-language/statements/define/table.md#example-usage) or [events](/docs/reference/query-language/statements/define/event.md).

**2.x**

The following tutorial will set up a server with SurrealDB and [Actix Web](https://actix.rs/) that has a few endpoints:

* Some endpoints to demonstrate how the HTTP endpoints work to create, select, modify etc. a `person` table in a database,
* Other endpoints to display some helpful info for the user,
* Two endpoints to allow signing up and signing in as a [record user](/docs/reference/query-language/statements/define/access/record.md).

## Getting started

First, open up a terminal window and use the following command to start an empty database. This will also define a namespace and database by the name of "main" by default.

```bash
surreal start --user root --pass secret
```

You can also use the [Start serving](/docs/explore/studio.md) button on [SurrealDB Studio](/docs/explore/studio.md) to do the same if you have it installed locally.

The database initiated by the [surreal start](/docs/reference/cli/surrealdb-cli/commands/start.md) command stores data in memory by default, which then disappears every time the database is shut down. As such, you can simply use Ctrl+C every time you want to start the database anew with no existing definitions or data. To save data to disk which will persist after shutting down, add a [positional argument](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument) for one of the storage backends such as `rocksdb://mydatabase` or `surrealkv://mydatabase` (or `surrealkv+versioned//mydatabase` to include SurrealKV versioning).

With the database running, we will now connect to the database "main" located in the namespace "main". You can connect to it by [creating a connection](/docs/explore/studio.md) inside SurrealDB Studio, or by using the following command to start an interactive shell.

```bash
surreal sql --user root --pass secret --pretty
```

Next, create a new Rust project with the command `cargo new your_project_name`, go into the newly created directory, and use `cargo add` to add each of the following dependencies:

* `surrealdb` (of course),
* `actix-web`,
* `serde`, for serialising and deserialising Rust structs passed to and from the database and Actix,
* `thiserror`, to make it easy to convert between SurrealDB's error type, other errors and Actix's response types,
* `rand` and `faker_rand`, to create random user names that can be used to sign in to the database as a record user.

The `serde` crate will need the `derive` flag enabled as well. Your `cargo.toml` dependencies should look like this:

```text
actix-web = "4.9.0"
faker_rand = "0.1.1"
rand = "0.8.5"
serde = { version = "1.0.228", features = ["derive"] }
surrealdb = "2.4.1"
thiserror = "2.0.18"
```

## Starting the Rust code

The first thing to do is a bit of groundwork to convert database errors into an error type of our own. Implementing `From<surrealdb::Error>` for this type will let it be used with the `?` operator when handling results. Finally, it will also need to implement Actix's `ResponseError` trait so that it can be used as output for the server. All of this can be done manually if you prefer, but the `thiserror` crate saves a certain amount of typing.

```rust
mod error {
    use actix_web::{HttpResponse, ResponseError};
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db(String),
    }

    impl ResponseError for Error {
        fn error_response(&self) -> HttpResponse {
            match self {
                Error::Db(e) => HttpResponse::InternalServerError().body(e.to_string()),
            }
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db(error.to_string())
        }
    }
}
```

Next, we will put the database client together. Actix provides an [`.app_data()`](https://actix.rs/docs/application/#state) method when starting a router that would give us access to the database inside its functions. However, for simplicity we can instead wrap the client inside a `LazyLock` to make it into a global static.

```rust
use std::sync::LazyLock;
static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);
```

Inside `main()`, we will do the following:

* Connect to the database running at `localhost:8000`
* Sign in as the root user that was created through the `surreal start` command
* Use (move to) the namespace "main" and database "main"
* Use the `.query()` method to pass in a few definitions for the database.

```rust
DB.connect::<Ws>("localhost:8000").await?;

DB.signin(Root {
    username: "root",
    password: "secret",
})
.await?;

DB.use_ns("main").use_db("main").await?;

DB.query(
    "
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;

DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
SIGNUP ( CREATE user SET name = $name,
    pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
)
.await?;
```

## What the database definitions do

The first item that stands out with the definitions above is that they all contain a `IF NOT EXISTS` clause. As the `DEFINE` statements will be executed every time the app starts, it is possible that they might be executed on a database that already has the definitions in place. SurrealDB returns an error if a definition already exists, requiring the `OVERWRITE` clause if a definition needs to be redone.

Note that this would not affect our app, as this would still be a successful usage of the `.query()` method. Instead, its output would contain a number of error results that could be handled individually:

```text
Response { client: Surreal { router: OnceLock(Router { sender: Sender { .. }, last_id: 4, features: {LiveQueries} }), engine: PhantomData<surrealdb::api::engine::any::Any> }, results: {0: (Stats { execution_time: Some(252.625µs) }, Err(Api(Query("The table 'person' already exists")))), 1: (Stats { execution_time: Some(79.167µs) }, Err(Api(Query("The field 'name' already exists")))), 2: (Stats { execution_time: Some(69.5µs) }, Err(Api(Query("The field 'created_by' already exists")))), 3: (Stats { execution_time: Some(73.625µs) }, Err(Api(Query("The index 'unique_name' already exists")))), 4: (Stats { execution_time: Some(73.583µs) }, Err(Api(Query("The access method 'account' already exists in the database 'main'"))))}, live_queries: {} }
```

However, adding `IF NOT EXISTS` is a nice way to change the results from errors into successful results, and to avoid the rare case in which they end up applied to some other version 1.x database that would rewrite its definitions if `IF NOT EXISTS` is present. So while not necessary in our case, it is a good practice to follow and makes for cleaner output.

Now let's go over each of the definitions to see what they do.

The first three statements define a `person` table. This table is schemaless, but has one required field `name`, which must be present and must be a string. This table has defined permissions by which a record user is able to use `CREATE` and `SELECT` on the `person` table, but can only `UPDATE` and `DELETE` records that it has created. The root user, however, is not subject to permissions rules.

The way these permissions are set is by using the `$auth` parameter. This parameter has a value whenever a record user is set as the authorised used for the database. The `WHERE $auth` clause simply means "where a value exists for the parameter `$auth`" (`WHERE $auth IS NOT NONE` would also work in this case). But for `UPDATE` and `DELETE` queries, it is not enough for `$auth` to just be present, the `created_by` field of a `person` record must also match the ID of the currently authenticated user.

This `created_by` field is automatically generated from its definition in the `DEFINE FIELD` statement. It is given the value of `$auth`, and is `READONLY` and thus cannot be changed. When logged in as a system user (like a root user), its value will be `NONE`. But when logged in as a record user, its value will be something like `user:qx2apv5oc8mh03wtah0q`.

```surql
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by
  ON TABLE person VALUE $auth READONLY;
```

So where does an ID like `user:qx2apv5oc8mh03wtah0q` come from? This is thanks to the following definitions that set the signup and signin behaviour of the record users. A typical [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) statement will create some sort of record on signup (in this case, a `user`) record, and will compare it against a password during signin. Note that the access has a name that we gave it (`account`), so that it can be referenced elsewhere.

In addition, a `DEFINE INDEX` statement with a `UNIQUE` clause is used to ensure that no two users can have the same name.

```surql
    DEFINE INDEX IF NOT EXISTS unique_name
      ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name,
	  pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name
	  AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
```

For an actual user in production, you would probably want to require an email and some other fields. Functions like [`string::is_email`](/docs/reference/query-language/functions/database-functions/string.md#stringis_email) can be used to ensure that the value passed in is valid.

```surql
DEFINE FIELD email ON TABLE user TYPE string ASSERT $value.is_email();
```

However, for this simple example, each user will simply have a unique name and a password. The password will be stored in hashed and salted form on the database, making it unique and unreadable every time it is generated. The only way to check if it is correct is by using a [compare function](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) of the output with an attempted password. Here is a short SurrealQL sample to show how the process works.

```surql
LET $hash1 = crypto::argon2::generate("myPaSSWord");
LET $hash2 = crypto::argon2::generate("myPaSSWord");

RETURN [$hash1, $hash2];
-- First returns true, second returns false
RETURN [
    crypto::argon2::compare($hash1, "myPaSSWord"),
    crypto::argon2::compare($hash1, "Wrongpassword")
];
```

## The rest of the code

The last step is where the majority of the work takes place: setting up the paths for Actix to handle, and writing the functions that handle the endpoints and (usually) access the database to handle the request. To start, we'll create a function for the `"/"` root path to display a helpful message to anybody giving the server a try via the browser or an app like curl or Postman. These paths and curl examples can all be seen on [the page for SurrealDB's HTTP endpoints](/docs/reference/rest-api/http-protocol.md).

```rust
    #[get("/")]
    pub async fn paths() -> &'static str {
        r#"

-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John Doe"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane Doe"}' http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                          http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }
```

Each of these functions will be put into a mod called `routes`, leading to the following code inside `main()`.

```rust
HttpServer::new(|| {
    App::new()
        .service(routes::create_person)
        .service(routes::read_person)
        .service(routes::update_person)
        .service(routes::delete_person)
        .service(routes::list_people)
        .service(routes::paths)
        .service(routes::session)
        .service(routes::make_new_user)
        .service(routes::get_new_token)
})
.bind(("localhost", 8080))?
.run()
.await?;
```

Many functions require some JSON data from the user, which will be deserialised into a `PersonData` struct. The database can then use it in methods like `.create().content()`. The output returned will now have a `name` and an `id`, which the `Person` struct holds.

```rust
#[derive(Serialize, Deserialize, Clone)]
pub struct PersonData {
    name: String,
}

#[derive(Serialize, Deserialize)]
pub struct Person {
    name: String,
    id: RecordId,
}
```

Each of these functions are pretty straightforward: obtain some user input, initiate a query, feed the user input into it, and return it as JSON.

```rust
const PERSON: &str = "person";

#[post("/person/{id}")]
pub async fn create_person(
    id: Path<String>,
    person: Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB.create((PERSON, &*id)).content(person).await?;
    Ok(Json(person))
}

#[get("/person/{id}")]
pub async fn read_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
    let person = DB.select((PERSON, &*id)).await?;
    Ok(Json(person))
}

#[put("/person/{id}")]
pub async fn update_person(
    id: Path<String>,
    person: Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB.update((PERSON, &*id)).content(person).await?;
    Ok(Json(person))
}

#[delete("/person/{id}")]
pub async fn delete_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
    let person = DB.delete((PERSON, &*id)).await?;
    Ok(Json(person))
}

#[get("/people")]
pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
    let people = DB.select(PERSON).await?;
    Ok(Json(people))
}
```

The `session()` function is also quite small, and is just a convenience for a user curious about the current session data. As the `.query()` method can take more than one statement, it returns each of these responses in order with an index for each (starting at 0). The `.take()` method can then be used to access the response at that index, and turn it into anything that can be deserialised back into a Rust type. In our case, a `String` is all we need here as the output will only be used to show the user the current session info.

```rust
pub async fn session() -> Result<Json<String>, Error> {
    let res: Option<String> = DB.query("RETURN <string>$session").await?.take(0)?;

    Ok(Json(res.unwrap_or("No session data found!".into())))
}
```

The most interesting function is the one used to create a new record user. To make it really easy to try out the experience of logging in as a record user, this function will use create a random name and password each time it is accessed. It will then pass in a [`Record`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Record.html) struct which is used to sign up a new record user. Note the following:

* The access name is `"account"`, which is the name we chose in the `DEFINE ACCESS` statement above.
* The `params` field takes anything that implements `Serialize`, in this case a struct we put together called `Params`.
* The `.signup()` method returns a redacted `Jwt` by default. To make the token visible, you can use the `.into_insecure_token()` method as we have done here. As a small guide to getting started, this example is not concerned about security. However, if you are looking to create something more production-worthy, do take a look at the [security](/docs/learn/security.md) section of the documentation and the [security best practices](/docs/learn/security/best-practices/security-best-practices.md) page.

The function will then end with an output showing the username, password, token, and instructions for how to log in using the CLI. This can be copied and pasted to begin making queries immediately.

```rust
#[derive(Serialize, Deserialize)]
struct Params<'a> {
    name: &'a str,
    pass: &'a str,
}

#[get("/new_user")]
pub async fn make_new_user() -> Result<String, Error> {
    let name = rand::random::<FirstName>().to_string();
    let pass = rand::random::<FirstName>().to_string();
    let jwt = DB
        .signup(Record {
            access: "account",
            namespace: "namespace",
            database: "database",
            params: Params {
                name: &name,
                pass: &pass,
            },
        })
        .await?
        .into_insecure_token();
    Ok(format!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --namespace namespace --database database --pretty --token \"{jwt}\""))
}
```

A record user with an expired token can use the `/signin` endpoint to get a new token. Since this requires passing in a username and password, we'll just have this function return a `String` that contains a curl example to get a new token.

```rust
#[get("/new_token")]
pub async fn get_new_token() -> String {
    let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"namespace","db":"database","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
    format!("Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --namespace namespace --database database --pretty --token YOUR_TOKEN_HERE")
}
```

## Experimenting with the app

The final code looks like this:

```rust
use actix_web::{App, HttpServer};
use std::sync::LazyLock;
use surrealdb::engine::remote::ws::Client;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);

mod error {
    use actix_web::{HttpResponse, ResponseError};
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db(String),
    }

    impl ResponseError for Error {
        fn error_response(&self) -> HttpResponse {
            match self {
                Error::Db(e) => HttpResponse::InternalServerError().body(e.to_string()),
            }
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db(error.to_string())
        }
    }
}

mod routes {

    use faker_rand::en_us::names::FirstName;
    use surrealdb::opt::auth::Record;

    use crate::error::Error;
    use crate::DB;
    use actix_web::web::{Json, Path};
    use actix_web::{delete, get, post, put};
    use serde::{Deserialize, Serialize};
    use surrealdb::RecordId;
    const PERSON: &str = "person";

    #[derive(Serialize, Deserialize)]
    pub struct PersonData {
        name: String,
    }

    #[derive(Serialize, Deserialize)]
    pub struct Person {
        name: String,
        id: RecordId,
    }

    #[get("/")]
    pub async fn paths() -> &'static str {
        r#"

-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John Doe"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane Doe"}' http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                          http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }

    #[get("/session")]
    pub async fn session() -> Result<Json<String>, Error> {
        let res: Option<String> = DB.query("RETURN <string>$session").await?.take(0)?;

        Ok(Json(res.unwrap_or("No session data found!".into())))
    }

    #[post("/person/{id}")]
    pub async fn create_person(
        id: Path<String>,
        person: Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB.create((PERSON, &*id)).content(person).await?;
        Ok(Json(person))
    }

    #[get("/person/{id}")]
    pub async fn read_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
        let person = DB.select((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    #[put("/person/{id}")]
    pub async fn update_person(
        id: Path<String>,
        person: Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB.update((PERSON, &*id)).content(person).await?;
        Ok(Json(person))
    }

    #[delete("/person/{id}")]
    pub async fn delete_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
        let person = DB.delete((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    #[get("/people")]
    pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
        let people = DB.select(PERSON).await?;
        Ok(Json(people))
    }

    #[derive(Serialize, Deserialize)]
    struct Params<'a> {
        name: &'a str,
        pass: &'a str,
    }

    #[get("/new_user")]
    pub async fn make_new_user() -> Result<String, Error> {
        let name = rand::random::<FirstName>().to_string();
        let pass = rand::random::<FirstName>().to_string();
        let jwt = DB
            .signup(Record {
                access: "account",
                namespace: "namespace",
                database: "database",
                params: Params {
                    name: &name,
                    pass: &pass,
                },
            })
            .await?
            .into_insecure_token();
        Ok(format!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --namespace namespace --database database --pretty --token \"{jwt}\""))
    }

    #[get("/new_token")]
    pub async fn get_new_token() -> String {
        let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"namespace","db":"database","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
        format!("Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --namespace namespace --database database --pretty --token YOUR_TOKEN_HERE")
    }
}

#[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    DB.connect::<Ws>("localhost:8000").await?;

    DB.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    DB.use_ns("namespace").use_db("database").await?;

    DB.query(
        "DEFINE TABLE person SCHEMALESS
        PERMISSIONS FOR 
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD name ON TABLE person TYPE string;
    DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS account ON DATABASE TYPE RECORD
		SIGNUP ( CREATE user SET name = $name,
	    pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;

    HttpServer::new(|| {
        App::new()
            .service(routes::create_person)
            .service(routes::read_person)
            .service(routes::update_person)
            .service(routes::delete_person)
            .service(routes::list_people)
            .service(routes::paths)
            .service(routes::session)
            .service(routes::make_new_user)
            .service(routes::get_new_token)
    })
    .bind(("localhost", 8080))?
    .run()
    .await?;

    Ok(())
}
```

As the database client is logged in as a root user, the `/person/` routes can be used to perform any operation on the `person` records of the database.

You can also log in to the CLI or SurrealDB Studio as a root user and separately as a record user using the output of the `/new_user` endpoint to compare the experience between the two.

For example, the output when creating a `person` record as a root user will look like this:

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]
```

But as a record user, it will include a `created_by` field, set by the value found at the `$auth` paremeter.

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8syfiq2ovztn2tbr8mhb,
		name: 'Aeon'
	}
]
```

As a result, a `DELETE person RETURN BEFORE` statement (which deletes all `person` records and returns the records deleted) used by a record user will only delete the single record that it created earlier. The following `SELECT` statement shows that the `person` record created by the root user cannot be deleted or modified by the record user.

```bash
main/main> DELETE person RETURN BEFORE;
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8y06y06jmmb7e58trckz,
		name: 'Aeon'
	}
]

main/main> SELECT * FROM person;
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]

main/main> UPDATE person SET name = "Yogurt";
-- Query 1
[]
```

Also note that the root user is able to see the `user` tables and their information. A record user cannot, as a record user by default has no permissions except what it is given by the `PERMISSIONS` clause. If you create a record user using the `/new_user` endpoint, the root user will be able to view it. However, the password has been obscured by the `crypto::argon2::generate` function so that nobody else can use it.

```surql
[
	{
		id: user:qx2apv5oc8mh03wtah0q,
		name: 'Gerard',
		pass:
		  '$argon2id$v=19$m=19456,t=2,p=1$j0ktTqUxRjOWYnwS5LoMFQ$2NcGkf5+IuLml6NorPy/Le6T8RppYXTXakwY5cDiZPY'
	}
]
```

## Further steps

Now that you have a running Actix server with SurrealDB as the backend, here are some other ideas that you might want to explore.

* Using the [`AUTHENTICATE`](/docs/reference/query-language/statements/define/access/record.md#example-failing-authentication) clause inside the `DEFINE ACCESS` statement. This will result in increased performance thanks to only being executed once, compared to permissions checks which are executed for each query.
* Adding some interesting behaviour to the database such as [changefeeds](/docs/reference/query-language/statements/define/table.md#example-usage) or [events](/docs/reference/query-language/statements/define/event.md).

---

Source: https://surrealdb.com/docs/reference/rust/frameworks/axum

# Axum

The SDK for Rust allows SurrealDB to be used as the storage backend for an Axum web server

**3.x**

The following tutorial will set up a server with SurrealDB and [Axum](https://docs.rs/axum/latest/axum/) that has a few endpoints:

* Some endpoints to demonstrate how the HTTP endpoints work to create, select, modify etc. a `person` table in a database,
* Other endpoints to display some helpful info for the user,
* Two endpoints to allow signing up and signing in as a [record user](/docs/reference/query-language/statements/define/access/record.md).

## Getting started

First, open up a terminal window and use the following command to start an empty database. This will also define a namespace and database by the name of "main" by default.

```bash
surreal start --user root --pass secret
```

You can also use the [Start serving](/docs/explore/studio.md) button on [SurrealDB Studio](/docs/explore/studio.md) to do the same if you have it installed locally.

The database initiated by the [surreal start](/docs/reference/cli/surrealdb-cli/commands/start.md) command stores data in memory by default, which then disappears every time the database is shut down. As such, you can simply use Ctrl+C every time you want to start the database anew with no existing definitions or data. To save data to disk which will persist after shutting down, add a [positional argument](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument) for one of the storage backends such as `rocksdb://mydatabase` or `surrealkv://mydatabase`.

With the database running, we will now connect to the database "main" located in the namespace "main". You can connect to it by [creating a connection](/docs/explore/studio.md) inside SurrealDB Studio, or by using the following command to start an interactive shell.

```bash
surreal sql --user root --pass secret --pretty
```

Next, create a new Rust project with the command `cargo new your_project_name`, go into the newly created directory, and use `cargo add` to add each of the following dependencies:

* `surrealdb` (of course),
* `axum`,
* `serde`, for serialising and deserialising Rust structs passed to and from the database and Axum,
* `tokio`, for the async runtime used by both Axum and SurrealDB,
* `thiserror`, to make it easy to convert between SurrealDB's error type, other errors and Axum's response types,
* `rand` and `faker_rand`, to create random user names that can be used to sign in to the database as a record user.

The `serde` crate will also need a feature flag for its `Serialize` and `Deserialize` macros. Your `cargo.toml` dependencies should look like this:

```text
axum = "0.8.8"
fake = "4.4.0"
serde = { version = "1.0.228", features = ["derive"] }
surrealdb = "3.2.0"
thiserror = "2.0.18"
tokio = "1.49.0"
```

## Starting the Rust code

The first thing to do is a bit of groundwork to convert database errors into an error type of our own. Implementing `From<surrealdb::Error>` for this type will let it be used with the `?` operator when handling results. Finally, it will also need to implement Axum's `IntoResponse` trait so that it can be used as output for the server. All of this can be done manually if you prefer, but the `thiserror` crate saves a certain amount of typing.

```rust
mod error {
    use axum::http::StatusCode;
    use axum::response::IntoResponse;
    use axum::response::Response;
    use axum::Json;
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db,
    }

    impl IntoResponse for Error {
        fn into_response(self) -> Response {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(self.to_string())).into_response()
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db
        }
    }
}
```

Next, we will put the database client together. Axum provides a [`.with_state()`](https://docs.rs/axum/latest/axum/routing/struct.Router.html#method.with_state) method when starting a router that would give us access to the database inside its functions. However, for simplicity we can instead wrap the client inside a `LazyLock` to make it into a global static.

```rust
use std::sync::LazyLock;
static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);
```

Inside `main()`, we will do the following:

* Connect to the database running at `localhost:8000`
* Sign in as the root user that was created through the `surreal start` command
* Use (move to) the namespace "main" and database "main"
* Use the `.query()` method to pass in a few definitions for the database.

```rust
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?;

DB.query(
    "
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;

DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
)
.await?;
```

## What the database definitions do

The first item that stands out with the definitions above is that they all contain a `IF NOT EXISTS` clause. As the `DEFINE` statements will be executed every time the app starts, it is possible that they might be executed on a database that already has the definitions in place. In that case, SurrealDB simply returns an error if a definition already exists, requiring the `OVERWRITE` clause if a definition needs to be redone. Without `IF NOT EXISTS`, the message "The table 'person' already exists" will be returned.

Note that this would not affect our app, as this would still be a successful usage of the `.query()` method. Instead, its output would contain a number of error results that could be handled individually:

```text
IndexedResults { results: {0: (DbResultStats { execution_time: Some(44.75µs), query_type: Some(Other) }, Err(Thrown("The table 'person' already exists"))), 1: (DbResultStats { execution_time: Some(87.042µs), query_type: Some(Other) }, Err(Thrown("The field 'name' already exists"))), 2: (DbResultStats { execution_time: Some(21.125µs), query_type: Some(Other) }, Err(Thrown("The field 'created_by' already exists"))), 3: (DbResultStats { execution_time: Some(13.584µs), query_type: Some(Other) }, Err(Thrown("The index 'unique_name' already exists"))), 4: (DbResultStats { execution_time: Some(36.125µs), query_type: Some(Other) }, Err(Thrown("The access method 'account' already exists in the database 'main'")))}, live_queries: {} }
```

However, adding `IF NOT EXISTS` is a nice way to change the results from errors into successful results, and to avoid the rare case in which they end up applied to some other version 1.x database that would rewrite its definitions if `IF NOT EXISTS` is present. So while not necessary in our case, it is a good practice to follow and makes for cleaner output.

Now let's go over each of the definitions to see what they do.

The first three statements define a `person` table. This table is schemaless, but has one required field `name`, which must be present and must be a string. This table has defined permissions by which a record user is able to use `CREATE` and `SELECT` on the `person` table, but can only `UPDATE` and `DELETE` records that it has created. The root user, however, is not subject to permissions rules.

The way these permissions are set is by using the `$auth` parameter. This parameter has a value whenever a record user is set as the authorised used for the database. The `WHERE $auth` clause simply means "where a value exists for the parameter `$auth`" (`WHERE $auth IS NOT NONE` would also work in this case). But for `UPDATE` and `DELETE` queries, it is not enough for `$auth` to just be present, the `created_by` field of a `person` record must also match the ID of the currently authenticated user.

This `created_by` field is automatically generated from its definition in the `DEFINE FIELD` statement. It is given the value of `$auth`, and is `READONLY` and thus cannot be changed. When logged in as a system user (like a root user), its value will be `NONE`. But when logged in as a record user, its value will be something like `user:qx2apv5oc8mh03wtah0q`.

```surql
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;
```

So where does an ID like `user:qx2apv5oc8mh03wtah0q` come from? This is thanks to the following definitions that set the signup and signin behaviour of the record users. A typical [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) statement will create some sort of record on signup (in this case, a `user`) record, and will compare it against a password during signin. Note that the access has a name that we gave it (`account`), so that it can be referenced elsewhere.

In addition, a `DEFINE INDEX` statement with a `UNIQUE` clause is used to ensure that no two users can have the same name.

```surql
    DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
```

For an actual user in production, you would probably want to require an email and some other fields. Functions like [`string::is_email`](/docs/reference/query-language/functions/database-functions/string.md#stringis_email) can be used to ensure that the value passed in is valid.

```surql
DEFINE FIELD email ON TABLE user TYPE string ASSERT $value.is_email();
```

However, for this simple example, each user will simply have a unique name and a password. The password will be stored in hashed and salted form on the database, making it unique and unreadable every time it is generated. The only way to check if it is correct is by using a [compare function](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) of the output with an attempted password. Here is a short SurrealQL sample to show how the process works.

```surql
LET $hash1 = crypto::argon2::generate("myPaSSWord");
LET $hash2 = crypto::argon2::generate("myPaSSWord");

RETURN [$hash1, $hash2];
-- First returns true, second returns false
RETURN [
    crypto::argon2::compare($hash1, "myPaSSWord"),
    crypto::argon2::compare($hash1, "Wrongpassword")
];
```

## The rest of the code

The last step is where the majority of the work takes place: setting up the paths for Axum to handle, and writing the functions that handle the endpoints and (usually) access the database to handle the request. To start, we'll create a function for the `"/"` root path to display a helpful message to anybody giving the server a try via the browser or an app like curl or Postman. These paths and curl examples can all be seen on [the page for SurrealDB's HTTP endpoints](/docs/reference/rest-api/http-protocol.md).

```rust
    pub async fn paths() -> &'static str {
        r#"
-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John"}' http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                      http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }
```

Each of these functions will be put into a mod called `routes`, leading to the following code inside `main()`.

```rust
let listener = TcpListener::bind("localhost:8080").await?;
let router = Router::new()
    .route("/", get(routes::paths))
    .route(
        "/person/{*id}",
        post(routes::create_person)
            .get(routes::read_person)
            .put(routes::update_person)
            .delete(routes::delete_person),
    )
    .route("/people", get(routes::list_people))
    .route("/session", get(routes::session))
    .route("/new_user", get(routes::make_new_user))
    .route("/new_token", get(routes::get_new_token));
axum::serve(listener, router).await?;
```

Many functions require some JSON data from the user, which will be deserialised into a `PersonData` struct. The database can then use it in methods like `.create().content()`. The output returned will now have a `name` and an `id`, which the `Person` struct holds.

```rust
#[derive(Serialize, Deserialize, Clone)]
pub struct PersonData {
    name: String,
}

#[derive(Serialize, Deserialize)]
pub struct Person {
    name: String,
    id: RecordId,
}
```

Each of these functions are pretty straightforward: obtain some user input, initiate a query, feed the user input into it, and return it as JSON.

```rust
pub async fn create_person(
    id: Path<String>,
    Json(person): Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB.create((PERSON, id.0)).content(person).await?;
    Ok(Json(person))
}

pub async fn read_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
    let person = DB.select((PERSON, id.0)).await?;
    Ok(Json(person))
}

pub async fn update_person(
    id: Path<String>,
    Json(person): Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB.update((PERSON, id.0)).content(person).await?;
    Ok(Json(person))
}

pub async fn delete_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
    let person = DB.delete((PERSON, id.0)).await?;
    Ok(Json(person))
}

pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
    let people = DB.select(PERSON).await?;
    Ok(Json(people))
}
```

The `session()` function is also quite small, and is just a convenience for a user curious about the current session data. As the `.query()` method can take more than one statement, it returns each of these responses in order with an index for each (starting at 0). The `.take()` method can then be used to access the response at that index, and turn it into anything that can be deserialised back into a Rust type. In our case, a `String` is all we need here as the output will only be used to show the user the current session info.

```rust
pub async fn session() -> Result<Json<String>, Error> {
    let res: Option<String> = DB.query("<string>$session").await?.take(0)?;

    Ok(Json(res.unwrap_or("No session data found!".into())))
}
```

The most interesting function is the one used to create a new record user. To make it really easy to try out the experience of logging in as a record user, this function will use create a random name and password each time it is accessed. It will then pass in a [`Record`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Record.html) struct which is used to sign up a new record user. Note the following:

* The access name is `"account"`, which is the name we chose in the `DEFINE ACCESS` statement above.
* The `params` field takes anything that implements `SurrealValue`, in this case a struct we put together called `Params`.
* The `.signup()` method returns a `Token` which holds an `AccessToken` inside it, which holds a redacted `Jwt`. To make the token visible, you can use the `.into_insecure_token()` method as we have done here. As a small guide to getting started, this example is not concerned about security. However, if you are looking to create something more production-worthy, do take a look at the [security](/docs/learn/security.md) section of the documentation and the [security best practices](/docs/learn/security/best-practices/security-best-practices.md) page.

The function will then end with an output showing the username, password, token, and instructions for how to log in using the CLI. This can be copied and pasted to begin making queries immediately.

```rust
#[derive(SurrealValue)]
struct Params {
    name: String,
    pass: String,
}

pub async fn make_new_user() -> Result<String, Error> {
    let name: String = FirstName().fake();
    let pass: String = FirstName().fake();
    let jwt = DB
        .signup(Record {
            access: "account".to_string(),
            namespace: "main".to_string(),
            database: "main".to_string(),
            params: Params {
                name: name.clone(),
                pass: pass.clone(),
            },
        })
        .await?
        .access
        .into_insecure_token();
    Ok(format!(
        "New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --pretty --token \"{jwt}\""
    ))
}
```

A record user with an expired token can use the `/signin` endpoint to get a new token. Since this requires passing in a username and password, we'll just have this function return a `String` that contains a curl example to get a new token.

```rust
pub async fn get_new_token() -> String {
    let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
    format!("Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --pretty --token YOUR_TOKEN_HERE")
}
```

## Experimenting with the app

The final code looks like this:

```rust
use axum::{
    Router,
    routing::{get, post},
};
use std::sync::LazyLock;
use surrealdb::{
    Surreal,
    engine::remote::ws::{Client, Ws},
    opt::auth::Root,
};
use tokio::net::TcpListener;

static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);

mod error {
    use axum::Json;
    use axum::http::StatusCode;
    use axum::response::IntoResponse;
    use axum::response::Response;
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db,
    }

    impl IntoResponse for Error {
        fn into_response(self) -> Response {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(self.to_string())).into_response()
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db
        }
    }
}

mod routes {
    use crate::DB;
    use crate::error::Error;
    use fake::faker::name::en::FirstName;

    use axum::{Json, extract::Path};
    use fake::Fake;
    use serde::{Deserialize, Serialize};
    use surrealdb::opt::auth::Record;
    use surrealdb::types::{RecordId, SurrealValue};

    const PERSON: &str = "person";

    #[derive(SurrealValue, Deserialize, Clone)]
    pub struct PersonData {
        name: String,
    }

    #[derive(SurrealValue, Serialize, Deserialize)]
    pub struct Person {
        name: String,
        id: RecordId,
    }

    pub async fn paths() -> &'static str {
        r#"
-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John"}' http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                      http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }

    pub async fn session() -> Result<Json<String>, Error> {
        let res: Option<String> = DB.query("<string>$session").await?.take(0)?;

        Ok(Json(res.unwrap_or("No session data found!".into())))
    }

    pub async fn create_person(
        id: Path<String>,
        Json(person): Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB.create((PERSON, id.0)).content(person).await?;
        Ok(Json(person))
    }

    pub async fn read_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
        let person = DB.select((PERSON, id.0)).await?;
        Ok(Json(person))
    }

    pub async fn update_person(
        id: Path<String>,
        Json(person): Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB.update((PERSON, id.0)).content(person).await?;
        Ok(Json(person))
    }

    pub async fn delete_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
        let person = DB.delete((PERSON, id.0)).await?;
        Ok(Json(person))
    }

    pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
        let people = DB.select(PERSON).await?;
        Ok(Json(people))
    }

    #[derive(SurrealValue)]
    struct Params {
        name: String,
        pass: String,
    }

    pub async fn make_new_user() -> Result<String, Error> {
        let name: String = FirstName().fake();
        let pass: String = FirstName().fake();
        let jwt = DB
            .signup(Record {
                access: "account".to_string(),
                namespace: "main".to_string(),
                database: "main".to_string(),
                params: Params {
                    name: name.clone(),
                    pass: pass.clone(),
                },
            })
            .await?
            .access
            .into_insecure_token();
        Ok(format!(
            "New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --pretty --token \"{jwt}\""
        ))
    }

    pub async fn get_new_token() -> String {
        let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
        format!(
            "Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --pretty --token YOUR_TOKEN_HERE"
        )
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    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?;

    DB.query(
        "
    DEFINE TABLE IF NOT EXISTS person SCHEMALESS
        PERMISSIONS FOR 
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
    DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;

    let listener = TcpListener::bind("localhost:8080").await?;
    let router = Router::new()
        .route("/", get(routes::paths))
        .route(
            "/person/{*id}",
            post(routes::create_person)
                .get(routes::read_person)
                .put(routes::update_person)
                .delete(routes::delete_person),
        )
        .route("/people", get(routes::list_people))
        .route("/session", get(routes::session))
        .route("/new_user", get(routes::make_new_user))
        .route("/new_token", get(routes::get_new_token));
    axum::serve(listener, router).await?;
    Ok(())
}
```

As the database client is logged in as a root user, the `/person/` routes can be used to perform any operation on the `person` records of the database.

You can also log in to the CLI or SurrealDB Studio as a root user and separately as a record user using the output of the `/new_user` endpoint to compare the experience between the two.

For example, the output when creating a `person` record as a root user will look like this:

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]
```

But as a record user, it will include a `created_by` field, set by the value found at the `$auth` paremeter.

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8syfiq2ovztn2tbr8mhb,
		name: 'Aeon'
	}
]
```

As a result, a `DELETE person RETURN BEFORE` statement (which deletes all `person` records and returns the records deleted) used by a record user will only delete the single record that it created earlier. The following `SELECT` statement shows that the `person` record created by the root user cannot be deleted or modified by the record user.

```bash
main/main> DELETE person RETURN BEFORE;
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8y06y06jmmb7e58trckz,
		name: 'Aeon'
	}
]

main/main> SELECT * FROM person;
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]

main/main> UPDATE person SET name = "Yogurt";
-- Query 1
[]
```

Also note that the root user is able to see the `user` tables and their information. A record user cannot, as a record user by default has no permissions except what it is given by the `PERMISSIONS` clause. If you create a record user using the `/new_user` endpoint, the root user will be able to view it. However, the password has been obscured by the `crypto::argon2::generate` function so that nobody else can use it.

```surql
[
	{
		id: user:qx2apv5oc8mh03wtah0q,
		name: 'Gerard',
		pass: '$argon2id$v=19$m=19456,t=2,p=1$j0ktTqUxRjOWYnwS5LoMFQ$2NcGkf5+IuLml6NorPy/Le6T8RppYXTXakwY5cDiZPY'
	}
]
```

## Further steps

Now that you have a running Axum server with SurrealDB as the backend, here are some other ideas that you might want to explore.

* Using the [`AUTHENTICATE`](/docs/reference/query-language/statements/define/access/record.md#example-failing-authentication) clause inside the `DEFINE ACCESS` statement. This will result in increased performance thanks to only being executed once, compared to permissions checks which are executed for each query.
* Adding some interesting behaviour to the database such as [changefeeds](/docs/reference/query-language/statements/define/table.md#example-usage) or [events](/docs/reference/query-language/statements/define/event.md).

**2.x**

The following tutorial will set up a server with SurrealDB and [Axum](https://docs.rs/axum/latest/axum/) that has a few endpoints:

* Some endpoints to demonstrate how the HTTP endpoints work to create, select, modify etc. a `person` table in a database,
* Other endpoints to display some helpful info for the user,
* Two endpoints to allow signing up and signing in as a [record user](/docs/reference/query-language/statements/define/access/record.md).

## Getting started

First, open up a terminal window and use the following command to start an empty database. This will also define a namespace and database by the name of "main" by default.

```bash
surreal start --user root --pass secret
```

You can also use the [Start serving](/docs/explore/studio.md) button on [SurrealDB Studio](/docs/explore/studio.md) to do the same if you have it installed locally.

The database initiated by the [surreal start](/docs/reference/cli/surrealdb-cli/commands/start.md) command stores data in memory by default, which then disappears every time the database is shut down. As such, you can simply use Ctrl+C every time you want to start the database anew with no existing definitions or data. To save data to disk which will persist after shutting down, add a [positional argument](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument) for one of the storage backends such as `rocksdb://mydatabase` or `surrealkv://mydatabase` (or `surrealkv+versioned//mydatabase` to include SurrealKV versioning).

With the database running, we will now connect to the database "main" located in the namespace "main". You can connect to it by [creating a connection](/docs/explore/studio.md) inside SurrealDB Studio, or by using the following command to start an interactive shell.

```bash
surreal sql --user root --pass secret --pretty
```

Next, create a new Rust project with the command `cargo new your_project_name`, go into the newly created directory, and use `cargo add` to add each of the following dependencies:

* `surrealdb` (of course),
* `axum`,
* `serde`, for serialising and deserialising Rust structs passed to and from the database and Axum,
* `tokio`, for the async runtime used by both Axum and SurrealDB,
* `thiserror`, to make it easy to convert between SurrealDB's error type, other errors and Axum's response types,
* `rand` and `faker_rand`, to create random user names that can be used to sign in to the database as a record user.

The `serde` crate will also need a feature flag for its `Serialize` and `Deserialize` macros. Your `cargo.toml` dependencies should look like this:

```text
axum = "0.8.8"
faker_rand = "0.1.1"
rand = "0.8.5"
serde = { version = "1.0.228", features = ["derive"] }
surrealdb = "2.4.1"
thiserror = "2.0.18"
tokio = "1.49.0"
```

## Starting the Rust code

The first thing to do is a bit of groundwork to convert database errors into an error type of our own. Implementing `From<surrealdb::Error>` for this type will let it be used with the `?` operator when handling results. Finally, it will also need to implement Axum's `IntoResponse` trait so that it can be used as output for the server. All of this can be done manually if you prefer, but the `thiserror` crate saves a certain amount of typing.

```rust
mod error {
    use axum::http::StatusCode;
    use axum::response::IntoResponse;
    use axum::response::Response;
    use axum::Json;
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db,
    }

    impl IntoResponse for Error {
        fn into_response(self) -> Response {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(self.to_string())).into_response()
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db
        }
    }
}
```

Next, we will put the database client together. Axum provides a [`.with_state()`](https://docs.rs/axum/latest/axum/routing/struct.Router.html#method.with_state) method when starting a router that would give us access to the database inside its functions. However, for simplicity we can instead wrap the client inside a `LazyLock` to make it into a global static.

```rust
use std::sync::LazyLock;
static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);
```

Inside `main()`, we will do the following:

* Connect to the database running at `localhost:8000`
* Sign in as the root user that was created through the `surreal start` command
* Use (move to) the namespace "main" and database "main"
* Use the `.query()` method to pass in a few definitions for the database.

```rust
DB.connect::<Ws>("localhost:8000").await?;

DB.signin(Root {
    username: "root",
    password: "secret",
})
.await?;

DB.use_ns("main").use_db("main").await?;

DB.query(
    "
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;

DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
)
.await?;
```

## What the database definitions do

The first item that stands out with the definitions above is that they all contain a `IF NOT EXISTS` clause. As the `DEFINE` statements will be executed every time the app starts, it is possible that they might be executed on a database that already has the definitions in place. SurrealDB returns an error if a definition already exists, requiring the `OVERWRITE` clause if a definition needs to be redone.

Note that this would not affect our app, as this would still be a successful usage of the `.query()` method. Instead, its output would contain a number of error results that could be handled individually:

```text
Response { client: Surreal { router: OnceLock(Router { sender: Sender { .. }, last_id: 4, features: {LiveQueries} }), engine: PhantomData<surrealdb::api::engine::any::Any> }, results: {0: (Stats { execution_time: Some(252.625µs) }, Err(Api(Query("The table 'person' already exists")))), 1: (Stats { execution_time: Some(79.167µs) }, Err(Api(Query("The field 'name' already exists")))), 2: (Stats { execution_time: Some(69.5µs) }, Err(Api(Query("The field 'created_by' already exists")))), 3: (Stats { execution_time: Some(73.625µs) }, Err(Api(Query("The index 'unique_name' already exists")))), 4: (Stats { execution_time: Some(73.583µs) }, Err(Api(Query("The access method 'account' already exists in the database 'main'"))))}, live_queries: {} }
```

However, adding `IF NOT EXISTS` is a nice way to change the results from errors into successful results, and to avoid the rare case in which they end up applied to some other version 1.x database that would rewrite its definitions if `IF NOT EXISTS` is present. So while not necessary in our case, it is a good practice to follow and makes for cleaner output.

Now let's go over each of the definitions to see what they do.

The first three statements define a `person` table. This table is schemaless, but has one required field `name`, which must be present and must be a string. This table has defined permissions by which a record user is able to use `CREATE` and `SELECT` on the `person` table, but can only `UPDATE` and `DELETE` records that it has created. The root user, however, is not subject to permissions rules.

The way these permissions are set is by using the `$auth` parameter. This parameter has a value whenever a record user is set as the authorised used for the database. The `WHERE $auth` clause simply means "where a value exists for the parameter `$auth`" (`WHERE $auth IS NOT NONE` would also work in this case). But for `UPDATE` and `DELETE` queries, it is not enough for `$auth` to just be present, the `created_by` field of a `person` record must also match the ID of the currently authenticated user.

This `created_by` field is automatically generated from its definition in the `DEFINE FIELD` statement. It is given the value of `$auth`, and is `READONLY` and thus cannot be changed. When logged in as a system user (like a root user), its value will be `NONE`. But when logged in as a record user, its value will be something like `user:qx2apv5oc8mh03wtah0q`.

```surql
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;
```

So where does an ID like `user:qx2apv5oc8mh03wtah0q` come from? This is thanks to the following definitions that set the signup and signin behaviour of the record users. A typical [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) statement will create some sort of record on signup (in this case, a `user`) record, and will compare it against a password during signin. Note that the access has a name that we gave it (`account`), so that it can be referenced elsewhere.

In addition, a `DEFINE INDEX` statement with a `UNIQUE` clause is used to ensure that no two users can have the same name.

```surql
    DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
```

For an actual user in production, you would probably want to require an email and some other fields. Functions like [`string::is_email`](/docs/reference/query-language/functions/database-functions/string.md#stringis_email) can be used to ensure that the value passed in is valid.

```surql
DEFINE FIELD email ON TABLE user TYPE string ASSERT $value.is_email();
```

However, for this simple example, each user will simply have a unique name and a password. The password will be stored in hashed and salted form on the database, making it unique and unreadable every time it is generated. The only way to check if it is correct is by using a [compare function](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) of the output with an attempted password. Here is a short SurrealQL sample to show how the process works.

```surql
LET $hash1 = crypto::argon2::generate("myPaSSWord");
LET $hash2 = crypto::argon2::generate("myPaSSWord");

RETURN [$hash1, $hash2];
-- First returns true, second returns false
RETURN [
    crypto::argon2::compare($hash1, "myPaSSWord")
    crypto::argon2::compare($hash1, "Wrongpassword")
];
```

## The rest of the code

The last step is where the majority of the work takes place: setting up the paths for Axum to handle, and writing the functions that handle the endpoints and (usually) access the database to handle the request. To start, we'll create a function for the `"/"` root path to display a helpful message to anybody giving the server a try via the browser or an app like curl or Postman. These paths and curl examples can all be seen on [the page for SurrealDB's HTTP endpoints](/docs/reference/rest-api/http-protocol.md).

```rust
    pub async fn paths() -> &'static str {
        r#"
-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John"}' http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                      http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }
```

Each of these functions will be put into a mod called `routes`, leading to the following code inside `main()`.

```rust
let listener = TcpListener::bind("localhost:8080").await?;
let router = Router::new()
    .route("/", get(routes::paths))
    .route("/person/:id", post(routes::create_person))
    .route("/person/:id", get(routes::read_person))
    .route("/person/:id", put(routes::update_person))
    .route("/person/:id", delete(routes::delete_person))
    .route("/people", get(routes::list_people))
    .route("/session", get(routes::session))
    .route("/new_user", get(routes::make_new_user))
    .route("/new_token", get(routes::get_new_token));
axum::serve(listener, router).await?;
```

Many functions require some JSON data from the user, which will be deserialised into a `PersonData` struct. The database can then use it in methods like `.create().content()`. The output returned will now have a `name` and an `id`, which the `Person` struct holds.

```rust
#[derive(Serialize, Deserialize, Clone)]
pub struct PersonData {
    name: String,
}

#[derive(Serialize, Deserialize)]
pub struct Person {
    name: String,
    id: RecordId,
}
```

Each of these functions are pretty straightforward: obtain some user input, initiate a query, feed the user input into it, and return it as JSON.

```rust
    const PERSON: &str = "person";

    pub async fn create_person(
        id: Path<String>,
        Json(person): Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB.create((PERSON, &*id)).content(person).await?;
        Ok(Json(person))
    }

    pub async fn read_person(id: Json<String>) -> Result<Json<Option<Person>>, Error> {
        let person = DB.select((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    pub async fn update_person(
        id: Path<String>,
        Json(person): Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB.update((PERSON, &*id)).content(person).await?;
        Ok(Json(person))
    }

    pub async fn delete_person(id: String) -> Result<Json<Option<Person>>, Error> {
        let person = DB.delete((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
        let people = DB.select(PERSON).await?;
        Ok(Json(people))
    }
```

The `session()` function is also quite small, and is just a convenience for a user curious about the current session data. As the `.query()` method can take more than one statement, it returns each of these responses in order with an index for each (starting at 0). The `.take()` method can then be used to access the response at that index, and turn it into anything that can be deserialised back into a Rust type. In our case, a `String` is all we need here as the output will only be used to show the user the current session info.

```rust
pub async fn session() -> Result<Json<String>, Error> {
    let res: Option<String> = DB.query("RETURN <string>$session").await?.take(0)?;

    Ok(Json(res.unwrap_or("No session data found!".into())))
}
```

The most interesting function is the one used to create a new record user. To make it really easy to try out the experience of logging in as a record user, this function will use create a random name and password each time it is accessed. It will then pass in a [`Record`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Record.html) struct which is used to sign up a new record user. Note the following:

* The access name is `"account"`, which is the name we chose in the `DEFINE ACCESS` statement above.
* The `params` field takes anything that implements `Serialize`, in this case a struct we put together called `Params`.
* The `.signup()` method returns a redacted `Jwt` by default. To make the token visible, you can use the `.into_insecure_token()` method as we have done here. As a small guide to getting started, this example is not concerned about security. However, if you are looking to create something more production-worthy, do take a look at the [security](/docs/learn/security.md) section of the documentation and the [security best practices](/docs/learn/security/best-practices/security-best-practices.md) page.

The function will then end with an output showing the username, password, token, and instructions for how to log in using the CLI. This can be copied and pasted to begin making queries immediately.

```rust
#[derive(Serialize, Deserialize)]
struct Params<'a> {
    name: &'a str,
    pass: &'a str,
}

pub async fn make_new_user() -> Result<String, Error> {
    let name = rand::random::<FirstName>().to_string();
    let pass = rand::random::<FirstName>().to_string();
    let jwt = DB
        .signup(Record {
            access: "account",
            namespace: "main",
            database: "main",
            params: Params {
                name: &name,
                pass: &pass,
            },
        })
        .await?
        .into_insecure_token();
    Ok(format!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --pretty --token \"{jwt}\""))
}
```

A record user with an expired token can use the `/signin` endpoint to get a new token. Since this requires passing in a username and password, we'll just have this function return a `String` that contains a curl example to get a new token.

```rust
    pub async fn get_new_token() -> String {
        let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
        format!("Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --pretty --token YOUR_TOKEN_HERE")
    }
```

## Experimenting with the app

The final code looks like this:

```rust
use std::sync::LazyLock;
use axum::{Router, routing::{delete, get, post, put}};
use surrealdb::{Surreal, engine::remote::ws::{Client, Ws}, opt::auth::Root};
use tokio::net::TcpListener;

static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);

mod error {
    use axum::http::StatusCode;
    use axum::response::IntoResponse;
    use axum::response::Response;
    use axum::Json;
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db,
    }

    impl IntoResponse for Error {
        fn into_response(self) -> Response {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(self.to_string())).into_response()
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db
        }
    }
}

mod routes {
    use crate::error::Error;
    use crate::DB;

    use axum::{extract::Path, Json};
    use faker_rand::en_us::names::FirstName;
    use surrealdb::{RecordId, opt::auth::Record};
    use serde::{Deserialize, Serialize};

    const PERSON: &str = "person";

    #[derive(Serialize, Deserialize, Clone)]
    pub struct PersonData {
        name: String,
    }

    #[derive(Serialize, Deserialize)]
    pub struct Person {
        name: String,
        id: RecordId,
    }

    pub async fn paths() -> &'static str {
        r#"
-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John"}' http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                      http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                      http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }

    pub async fn session() -> Result<Json<String>, Error> {
        let res: Option<String> = DB.query("RETURN <string>$session").await?.take(0)?;

        Ok(Json(res.unwrap_or("No session data found!".into())))
    }

    pub async fn create_person(
        id: Path<String>,
        Json(person): Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB.create((PERSON, &*id)).content(person).await?;
        Ok(Json(person))
    }

    pub async fn read_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
        let person = DB.select((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    pub async fn update_person(
        id: Path<String>,
        Json(person): Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB.update((PERSON, &*id)).content(person).await?;
        Ok(Json(person))
    }

    pub async fn delete_person(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
        let person = DB.delete((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
        let people = DB.select(PERSON).await?;
        Ok(Json(people))
    }

    #[derive(Serialize, Deserialize)]
    struct Params<'a> {
        name: &'a str,
        pass: &'a str,
    }

    pub async fn make_new_user() -> Result<String, Error> {
        let name = rand::random::<FirstName>().to_string();
        let pass = rand::random::<FirstName>().to_string();
        let jwt = DB
            .signup(Record {
                access: "account",
                namespace: "main",
                database: "main",
                params: Params {
                    name: &name,
                    pass: &pass,
                },
            })
            .await?
            .into_insecure_token();
        Ok(format!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --pretty --token \"{jwt}\""))
    }

    pub async fn get_new_token() -> String {
        let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
        format!("Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --pretty --token YOUR_TOKEN_HERE")
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    DB.connect::<Ws>("localhost:8000").await?;

    DB.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    DB.use_ns("main").use_db("main").await?;

    DB.query(
        "
    DEFINE TABLE IF NOT EXISTS person SCHEMALESS
        PERMISSIONS FOR 
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
    DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;

    let listener = TcpListener::bind("localhost:8080").await?;
    let router = Router::new()
        .route("/", get(routes::paths))
        .route("/person/:id", post(routes::create_person))
        .route("/person/:id", get(routes::read_person))
        .route("/person/:id", put(routes::update_person))
        .route("/person/:id", delete(routes::delete_person))
        .route("/people", get(routes::list_people))
        .route("/session", get(routes::session))
        .route("/new_user", get(routes::make_new_user))
        .route("/new_token", get(routes::get_new_token));
    axum::serve(listener, router).await?;
    Ok(())
}
```

As the database client is logged in as a root user, the `/person/` routes can be used to perform any operation on the `person` records of the database.

You can also log in to the CLI or SurrealDB Studio as a root user and separately as a record user using the output of the `/new_user` endpoint to compare the experience between the two.

For example, the output when creating a `person` record as a root user will look like this:

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]
```

But as a record user, it will include a `created_by` field, set by the value found at the `$auth` paremeter.

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8syfiq2ovztn2tbr8mhb,
		name: 'Aeon'
	}
]
```

As a result, a `DELETE person RETURN BEFORE` statement (which deletes all `person` records and returns the records deleted) used by a record user will only delete the single record that it created earlier. The following `SELECT` statement shows that the `person` record created by the root user cannot be deleted or modified by the record user.

```bash
main/main> DELETE person RETURN BEFORE;
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8y06y06jmmb7e58trckz,
		name: 'Aeon'
	}
]

main/main> SELECT * FROM person;
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]

main/main> UPDATE person SET name = "Yogurt";
-- Query 1
[]
```

Also note that the root user is able to see the `user` tables and their information. A record user cannot, as a record user by default has no permissions except what it is given by the `PERMISSIONS` clause. If you create a record user using the `/new_user` endpoint, the root user will be able to view it. However, the password has been obscured by the `crypto::argon2::generate` function so that nobody else can use it.

```surql
[
	{
		id: user:qx2apv5oc8mh03wtah0q,
		name: 'Gerard',
		pass: '$argon2id$v=19$m=19456,t=2,p=1$j0ktTqUxRjOWYnwS5LoMFQ$2NcGkf5+IuLml6NorPy/Le6T8RppYXTXakwY5cDiZPY'
	}
]
```

## Further steps

Now that you have a running Axum server with SurrealDB as the backend, here are some other ideas that you might want to explore.

* Using the [`AUTHENTICATE`](/docs/reference/query-language/statements/define/access/record.md#example-failing-authentication) clause inside the `DEFINE ACCESS` statement. This will result in increased performance thanks to only being executed once, compared to permissions checks which are executed for each query.
* Adding some interesting behaviour to the database such as [changefeeds](/docs/reference/query-language/statements/define/table.md#example-usage) or [events](/docs/reference/query-language/statements/define/event.md).

---

Source: https://surrealdb.com/docs/reference/rust/frameworks/egui

# Egui

The SDK for Rust allows SurrealDB to be used as the storage backend for an Egui visual app

**3.x**

The following tutorial will set up a small app with Egui that uses SurrealDB as its database.

## Getting started

First, open up a terminal window and use the following command to start an empty database. This will also define a namespace and database by the name of "main" by default.

```bash
surreal start --user root --pass secret
```

You can also use the [Start serving](/docs/explore/studio.md) button on [SurrealDB Studio](/docs/explore/studio.md) to do the same if you have it installed locally.

The database initiated by the [surreal start](/docs/reference/cli/surrealdb-cli/commands/start.md) command stores data in memory by default, which then disappears every time the database is shut down. As such, you can simply use Ctrl+C every time you want to start the database anew with no existing definitions or data. To save data to disk which will persist after shutting down, add a [positional argument](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument) for one of the storage backends such as `rocksdb://mydatabase` or `surrealkv://mydatabase` (or `surrealkv+versioned//mydatabase` to include SurrealKV versioning).

With the database running, it's time to start setting up the Rust code.

## Starting the Rust code

First create a new Rust project with the command `cargo new your_project_name`, go into the newly created directory, and use `cargo add` to add each of the following dependencies:

* `anyhow`, to allow us to not worry about how to handle different error types,
* `fake`, to create random user names that can be used to sign in to the database as a record user,
* `egui` and its framework `eframe`,
* `serde` and `serde_json`, for deserialising into Rust structs from the JSON format submitted from Egui,
* `tokio`, for the async runtime that SurrealDB uses.

The `serde` crate will need the `derive` flag enabled, and `tokio` will need the `rt` flag enabled as well. Your `cargo.toml` dependencies should look like this:

```text
anyhow = "1.0.91"
eframe = "0.33.3"
egui = "0.33.3"
fake = "4.4.0"
rand = "0.8.5"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.132"
surrealdb = "3.2.0"
tokio = { version = "1.49.0", features = ["rt"] }
```

Before we get around to the Egui frontend, let's set up the database.

SurrealDB's Rust crate uses async code, and while usually you will see an `async fn main()` with a `#[tokio::main]` attribute on top in SurrealDB examples, Egui does not use async. To isolate one from the other, we can create the tokio runtime manually and call `.block_on()` to isolate the database in its own space. Later one, we will create two [channels](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html) to communicate between the database and the Egui app.

Inside `main()`, we will do the following:

* Connect to the database running at `localhost:8000`
* Sign in as the root user that was created through the `surreal start` command
* Use (move to) the namespace "main" and database "main"
* Use the `.query()` method to pass in a few definitions for the database.

```rust
fn main() -> Result<(), Error> {
    let rt = tokio::runtime::Runtime::new()?;

    let _: Result<(), Error> = rt.block_on(async {

            let db = Surreal::new::<Ws>("localhost:8000").await?;

            db.signin(Root {
                username: "root",
                password: "secret",
            })
            .await?;

            db.use_ns("main").use_db("main").await?;

            db.query(
                "    DEFINE TABLE person SCHEMALESS
                PERMISSIONS FOR
                    CREATE, SELECT WHERE $auth,
                    FOR UPDATE, DELETE WHERE created_by = $auth;
            DEFINE FIELD name ON TABLE person TYPE string;
            DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

            DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
            DEFINE ACCESS account ON DATABASE TYPE RECORD
            SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
            SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
            DURATION FOR TOKEN 15m, FOR SESSION 12h
        ;",
            )
            .await?;
        Ok(())
        });
    Ok(())
}
```

## What the database definitions do

The first item that stands out with the definitions above is that they all contain a `IF NOT EXISTS` clause. As the `DEFINE` statements will be executed every time the app starts, it is possible that they might be executed on a database that already has the definitions in place. SurrealDB returns an error if a definition already exists, requiring the `OVERWRITE` clause if a definition needs to be redone.

Note that this would not affect our app, as this would still be a successful usage of the `.query()` method. Instead, its output would contain a number of error results that could be handled individually:

```text
IndexedResults { results: {0: (DbResultStats { execution_time: Some(44.75µs), query_type: Some(Other) }, Err(Thrown("The table 'person' already exists"))), 1: (DbResultStats { execution_time: Some(87.042µs), query_type: Some(Other) }, Err(Thrown("The field 'name' already exists"))), 2: (DbResultStats { execution_time: Some(21.125µs), query_type: Some(Other) }, Err(Thrown("The field 'created_by' already exists"))), 3: (DbResultStats { execution_time: Some(13.584µs), query_type: Some(Other) }, Err(Thrown("The index 'unique_name' already exists"))), 4: (DbResultStats { execution_time: Some(36.125µs), query_type: Some(Other) }, Err(Thrown("The access method 'account' already exists in the database 'main'")))}, live_queries: {} }
```

However, adding `IF NOT EXISTS` is a nice way to change the results from errors into successful results, and to avoid the rare case in which they end up applied to some other version 1.x database that would rewrite its definitions if `IF NOT EXISTS` is present. So while not necessary in our case, it is a good practice to follow and makes for cleaner output.

Now let's go over each of the definitions to see what they do.

The first three statements define a `person` table. This table is schemaless, but has one required field `name`, which must be present and must be a string. This table has defined permissions by which a record user is able to use `CREATE` and `SELECT` on the `person` table, but can only `UPDATE` and `DELETE` records that it has created. The root user, however, is not subject to permissions rules.

The way these permissions are set is by using the `$auth` parameter. This parameter has a value whenever a record user is set as the authorised used for the database. The `WHERE $auth` clause simply means "where a value exists for the parameter `$auth`" (`WHERE $auth IS NOT NONE` would also work in this case). But for `UPDATE` and `DELETE` queries, it is not enough for `$auth` to just be present, the `created_by` field of a `person` record must also match the ID of the currently authenticated user.

This `created_by` field is automatically generated from its definition in the `DEFINE FIELD` statement. It is given the value of `$auth`, and is `READONLY` and thus cannot be changed. When logged in as a system user (like a root user), its value will be `NONE`. But when logged in as a record user, its value will be something like `user:qx2apv5oc8mh03wtah0q`.

```surql
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;
```

So where does an ID like `user:qx2apv5oc8mh03wtah0q` come from? This is thanks to the following definitions that set the signup and signin behaviour of the record users. A typical [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) statement will create some sort of record on signup (in this case, a `user`) record, and will compare it against a password during signin. Note that the access has a name that we gave it (`account`), so that it can be referenced elsewhere.

In addition, a `DEFINE INDEX` statement with a `UNIQUE` clause is used to ensure that no two users can have the same name.

```surql
    DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
```

For an actual user in production, you would probably want to require an email and some other fields. Functions like [`string::is_email`](/docs/reference/query-language/functions/database-functions/string.md#stringis_email) can be used to ensure that the value passed in is valid.

```surql
DEFINE FIELD email ON TABLE user TYPE string ASSERT $value.is_email();
```

However, for this simple example, each user will simply have a unique name and a password. The password will be stored in hashed and salted form on the database, making it unique and unreadable every time it is generated. The only way to check if it is correct is by using a [compare function](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) of the output with an attempted password. Here is a short SurrealQL sample to show how the process works.

```surql
LET $hash1 = crypto::argon2::generate("myPaSSWord");
LET $hash2 = crypto::argon2::generate("myPaSSWord");

RETURN [$hash1, $hash2];
-- First returns true, second returns false
RETURN [
    crypto::argon2::compare($hash1, "myPaSSWord"),
    crypto::argon2::compare($hash1, "Wrongpassword")
];
```

## The Egui code

### The Database struct

We are now at the point where the majority of the work takes place: creating the actual app and a way for it to interact with the database.

To keep the code to a minimum, our app will be as simple as possible. It will have a few buttons, a panel to take user input, and a second panel to display results. The buttons will be as follows:

* `Create person`: Instructs the database to try to create a `person` record based on the input provided by the user.
* `Delete person`: Deletes all the `person` records if the user input is left blank, otherwise will take a single id.
* `List people`: Shows all the `person` records in the database.
* `Session data`: Shows the current session data.
* `Raw query`: Allows the user to execute a raw query.
* `New user`: Creates a new record user with a random name and password, displayed as an object.
* `Sign in as record user`: Signs in using an object with a name and a password. The `New user` output can be pasted in to sign in here.
* `Sign in as root`: Signs back in as the database root user.

The behaviour of an Egui app takes place inside a single [.update()](https://docs.rs/eframe/latest/eframe/trait.App.html#tymethod.update), which takes a mutable reference to the app (usually a struct).

Because this function can be called up to several times per second, waiting for even short database queries might have noticeable effects on the repainting of the app. To ensure that this won't happen, we will create two channels between the Egui app and the database. The first channel will send commands from the app to the database, while the second channel will send each response back as a simple `String`. The database will loop continuously as it checks for commands, while the app will check for responses during every iteration of the `.update()` function.

Here are the two apps and the types used to communicate with each other.

```rust
struct SurrealDbApp {
    input: String,
    results: String,
    command_sender: Sender<Command>,
    response_receiver: Receiver<String>,
}

struct Database {
    client: Surreal<Client>,
    command_receiver: Receiver<Command>,
    response_sender: Sender<String>,
}

#[derive(Debug, Clone)]
enum Command {
    CreatePerson(String),
    DeletePerson(String),
    ListPeople,
    RawQuery(String),
    SignUp,
    SignIn(String),
    SignInRoot,
    Session,
}
```

Egui has [sample code for running a native app](https://docs.rs/eframe/latest/eframe/#usage-native) that shows how to start one that we can copy and paste, only changing the name. Here is what the `main()` portion of the final code will look like.

```rust
fn main() -> Result<(), Error> {
    let (command_sender, command_receiver) = channel();
    let (response_sender, response_receiver) = channel();

    std::thread::spawn(|| -> Result<(), Error> {
        let rt = tokio::runtime::Runtime::new()?;

        rt.block_on(async {

            let client = Surreal::new::<Ws>("localhost:8000").await?;

            let db = Database {
                client,
                command_receiver,
                response_sender
            };

            db.signin(Root {
                username: "root",
                password: "secret",
            })
            .await?;

            db.use_ns("main").use_db("main").await?;

            db.query(
                "    DEFINE TABLE person SCHEMALESS
                PERMISSIONS FOR
                    CREATE, SELECT WHERE $auth,
                    FOR UPDATE, DELETE WHERE created_by = $auth;
            DEFINE FIELD name ON TABLE person TYPE string;
            DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

            DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
            DEFINE ACCESS account ON DATABASE TYPE RECORD
            SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
            SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
            DURATION FOR TOKEN 15m, FOR SESSION 12h
        ;",
            )
            .await?;

        loop {
            if let Ok(command) = db.command_receiver.try_recv() {
                match db.handle_command(command).await {
                    Ok(s) => db.response_sender.send(s)?,
                    Err(e) => db.response_sender.send(e.to_string())?
                }
            }
        }
        })
    });

    let app = SurrealDbApp {
        input: String::new(),
        results: String::new(),
        command_sender,
        response_receiver,
    };

    let native_options = eframe::NativeOptions::default();
    let _ = eframe::run_native(
        "SurrealDB App",
        native_options,
        Box::new(|_cc| Ok(Box::new(app))),
    );
    Ok(())
}
```

The database calls a function called `.handle_command()` every time it receives a command, so let's take a look at that one. It uses a simple `match` statement and executes database queries depending on what it is asked to do.

We'll start with some notable parts of the `.handle_command()` function and related code.

First we have two pieces of code added for convenience. One implements `Deref` so that the `Database` struct can call methods like `.create()` instead of `.client.create()`. The other is a helper trait so that we can call `.string()` after each method that returns an `Option<Person>` instead of having to use a `match` statement every time. There is also a `const` declared with `const PERSON: &str = "person"` that removes the possibility of typos inside the various query methods.

```rust
impl Deref for Database {
    type Target = Surreal<Client>;

    fn deref(&self) -> &Self::Target {
        &self.client
    }
}

trait StringIt {
    fn string(self) -> Result<String, Error>;
}

impl StringIt for Option<Person> {
    fn string(self) -> Result<String, Error> {
        match self {
            Some(t) => Ok(format!("{t:?}")),
            None => Ok("[]".into()),
        }
    }
}

const PERSON: &str = "person";
```

When the `.handle_command()` method comes across a `Command::CreatePerson`, which contains a `String`, it will attempt to turn it into this `PersonData` struct.

```rust
#[derive(SurrealValue, Serialize, Deserialize, Clone, Default)]
pub struct PersonData {
    name: String,
    id: Option<String>,
}
```

This can be done using the `serde_json::from_str()` function. If the input is properly formatted, such as `{ "name": "Billy" }`, it will deserialise into a `PersonData` that can then be passed into the `.create()` function. The helper function `.string()` will then pass it back as an `Ok` with the `String` data inside if successful.

For simplicity, data passed in will only ever deserialise into a `Person` app with three fields: a name, a record Id, and a possible `created_by` field which will have a value if the `person` record is created by a record user.

```rust
#[derive(SurrealValue, Serialize, Deserialize, Debug)]
pub struct Person {
    name: String,
    id: RecordId,
    created_by: Option<RecordId>,
}

Command::CreatePerson(s) => {
    let person_data: PersonData = serde_json::from_str(&s)?;
    self.create::<Option<Person>>(PERSON)
        .content(person_data)
        .await?
        .string()
}
```

For the `Command::DeletePerson` variant, a check is made to see whether the user input is empty, in which case it will delete every `person` record. Otherwise, it will assume that the input is the key of a record ID (like the `one` in `person:one`) and delete that record if it exists.

```rust
Command::DeletePerson(s) => {
    if s.is_empty() {
        let res: Vec<Person> = self.delete(PERSON).await?;
        Ok(format!("{res:?}"))
    } else {
        let key = RecordIdKey::from(s);
        self.delete::<Option<Person>>((PERSON, key)).await?.string()
    }
}
```

The other three query methods are pretty simple. `Command::ListPeople` returns all `person` records, `Command::RawQuery` takes a direct SurrealQL input and returns the result, and `Command::Session` just accesses the `$session` parameter cast into a string.

```rust
Command::ListPeople => {
    let person: Vec<Person> = self.select(PERSON).await?;
    Ok(format!("{person:?}"))
}
Command::RawQuery(q) => match self.query(q).await {
    Ok(ok) => Ok(format!("{ok:?}")),
    Err(e) => Ok(e.to_string()),
},
Command::Session => Ok(self
    .query("<string>$session")
    .await?
    .take::<Option<String>>(0)?
    .unwrap_or("No session data found!".into()))
```

The `Command::SignUp` and `Command::SignIn` variants are a bit more interesting.

A user is allowed to choose a name and a password when signing up as a record user, but to make the process as quick as possible we will use the `faker_rand` crate to generate two names: one for the username and one for the password.

The [`.signup()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signup) method actually returns a struct that holds an access token which can be displayed using the `.into_insecure_token()` method. However, tokens are mostly useful when signing in via the [surreal sql](/docs/reference/cli/surrealdb-cli/commands/sql.md) command or through SurrealDB Studio. In our case, we can simply use the `.signin()` method along with a name and password and so we don't need to display the token.

The `Params` struct is our own struct, which holds a `name` and a `pass` field because those are the two fields that we specified in the `DEFINE ACCESS` statement which creates a new `user` record every time a record user is signed up. Similarly, the `access` field inside `.signup()` takes the input "account" because that is the name that we have to the `DEFINE ACCESS` statement.

```rust
// DEFINE ACCESS account ON DATABASE TYPE RECORD
// SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
// SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
// DURATION FOR TOKEN 15m, FOR SESSION 12h

#[derive(SurrealValue, Deserialize)]
struct Params {
    name: String,
    pass: String,
}

Command::SignUp => {
    let name = rand::random::<FirstName>().to_string();
    let pass = rand::random::<FirstName>().to_string();
    self.signup(Record {
        access: "account",
        namespace: "main",
        database: "main",
        params: Params {
            name: &name,
            pass: &pass,
        },
    })
    .await?;
    Ok(format!(
        "New user created!\n\n{{ \"name\": \"{name}\", \n \"pass\": \"{pass}\" }}"
    ))
}
```

The output when the button is clicked to sign up a new user is in `JSON` format so that the user can copy and paste it to sign in.

```text
New user created!

{ "name": "Rebecca", 
 "pass": "Neha" }
```

Signing in is pretty similar, except that it begins by trying to deserialise the user input into a `Params` struct. The `.signin()` method also returns a `Jwt` that we don't need, so the output will just let the user know that it has signed in under a certain name.

```rust
Command::SignIn(s) => {
    let Ok(Params { name, pass }) = serde_json::from_str::<Params>(&s) else {
        return Ok("Params don't work!".to_string());
    };
    self.signin(Record {
        access: "account",
        namespace: "main",
        database: "main",
        params: Params { name, pass },
    })
    .await?;
    Ok(format!("Signed in as {name}!"))
}
```

The last command will allow the user to sign back in as the root user. To make it easy to experiment with the database as a record user vs. a root user, we won't make the user type in the root user's name and password each time.

```rust
Command::SignInRoot => {
    self.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    Ok("Back to root!".to_string())
}
```

### The Egui app struct

The struct for the Egui app is quite simple, with only four fields. Two of them hold the user input and database results, which will be displayed on the screen at all times. The other two fields are for the sending and receiving end of the two channels.

```rust
struct SurrealDbApp {
    input: String,
    results: String,
    command_sender: Sender<Command>,
    response_receiver: Receiver<String>,
}
```

Since the [`.send()`](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html#method.send) function for channels in Rust returns a `Result` but Egui's `.update()` function doesn't, we'll save ourselves a lot of typing by putting together a quick method for this struct called `.send()` that does the error handling. All it will do is turn any errors into a `String` which it will then give to the `results` field.

```rust
impl SurrealDbApp {
    fn send(&mut self, command: Command) {
        if let Err(e) = self.command_sender.send(command) {
            self.results = e.to_string()
        }
    }
}
```

After that, the final task left to us is to create the layout and buttons for the Egui app inside the `update()` function that all Egui app structs are required to implement. The first line will be the one where the app checks to see if the database has sent it a message. All of these messages are simple Strings, so it will just set the `results` field with them so the user can see what was sent. Note that the `Ok` here just means that `.try_recv()` has successfully received a message, not that the database has succeeded at what it was instructed to do. The `String` might contain successful data, or an error message.

```rust
if let Ok(response) = self.response_receiver.try_recv() {
    self.results = response;
}
```

After this come the buttons. The [`SidePanel::left()`](https://docs.rs/egui/latest/egui/containers/panel/struct.SidePanel.html) will create a panel on the left side of the screen inside which we can add the buttons. If the button is clicked, the app will send off a command that may or might not include the data from the `input` field.

```rust
egui::SidePanel::left("left").show(ctx, |ui| {
    if let Ok(response) = self.response_receiver.try_recv() {
        self.results = response;
    }
    if ui.button("Create person").clicked() {
        self.send(Command::CreatePerson(self.input.clone()))
    };
    if ui.button("Delete person").clicked() {
        self.send(Command::DeletePerson(self.input.clone()))
    }
    if ui.button("List people").clicked() {
        self.send(Command::ListPeople)
    }
    if ui.button("Session data").clicked() {
        self.send(Command::Session)
    }
    if ui.button("New user").clicked() {
        self.send(Command::SignUp)
    }
    if ui.button("Sign in as record user").clicked() {
        self.send(Command::SignIn(self.input.clone()));
    }
    if ui.button("Sign in as root").clicked() {
        self.send(Command::SignInRoot)
    }
    if ui.button("Raw query").clicked() {
        self.send(Command::RawQuery(self.input.clone()))
    }
});
```

The final bit of code just involves creating two more panels, one in the centre and one on the right. The one on the right will add a [`ScrollArea`](https://docs.rs/egui/latest/egui/containers/scroll_area/struct.ScrollArea.html) so that the user can scroll through any results that are larger than the space in the right panel.

```rust
egui::CentralPanel::default().show(ctx, |ui| {
    ui.label(RichText::new("Input:").heading());
    ui.text_edit_multiline(&mut self.input);
});
egui::SidePanel::right("right").show(ctx, |ui| {
    egui::ScrollArea::vertical().show(ui, |ui| {
        ui.label(RichText::new("Results:").heading());
        ui.text_edit_multiline(&mut self.results);
    });
});
```

And that's all the code!

## Experimenting with the app

The final code looks like this:

```rust
use std::{
    ops::Deref,
    sync::mpsc::{Receiver, Sender, channel},
};

use egui::RichText;
use fake::{Fake, faker::name::en::FirstName};
use surrealdb::{
    Surreal,
    engine::remote::ws::{Client, Ws},
    opt::auth::{Record, Root},
};

use anyhow::Error;
use serde::{Deserialize, Serialize};
use surrealdb::types::{RecordId, RecordIdKey, SurrealValue};

const PERSON: &str = "person";

#[derive(SurrealValue, Deserialize)]
struct Params {
    name: String,
    pass: String,
}

#[derive(SurrealValue, Serialize, Deserialize, Clone, Default)]
pub struct PersonData {
    name: String,
    id: Option<String>,
}

#[derive(SurrealValue, Serialize, Deserialize, Debug)]
pub struct Person {
    name: String,
    id: RecordId,
    created_by: Option<RecordId>,
}

#[derive(Debug, Clone)]
enum Command {
    CreatePerson(String),
    DeletePerson(String),
    ListPeople,
    RawQuery(String),
    SignUp,
    SignIn(String),
    SignInRoot,
    Session,
}

struct Database {
    client: Surreal<Client>,
    command_receiver: Receiver<Command>,
    response_sender: Sender<String>,
}

impl Deref for Database {
    type Target = Surreal<Client>;

    fn deref(&self) -> &Self::Target {
        &self.client
    }
}

trait StringIt {
    fn string(self) -> Result<String, Error>;
}

impl StringIt for Option<Person> {
    fn string(self) -> Result<String, Error> {
        match self {
            Some(t) => Ok(format!("{t:?}")),
            None => Ok("[]".into()),
        }
    }
}

impl Database {
    async fn handle_command(&self, command: Command) -> Result<String, Error> {
        match command {
            Command::CreatePerson(s) => {
                let person_data: PersonData = serde_json::from_str(&s)?;
                self.create::<Option<Person>>(PERSON)
                    .content(person_data)
                    .await?
                    .string()
            }
            Command::DeletePerson(s) => {
                if s.is_empty() {
                    let res: Vec<Person> = self.delete(PERSON).await?;
                    Ok(format!("{res:?}"))
                } else {
                    let key = RecordIdKey::from(s);
                    self.delete::<Option<Person>>((PERSON, key)).await?.string()
                }
            }
            Command::ListPeople => {
                let person: Vec<Person> = self.select(PERSON).await?;
                Ok(format!("{person:?}"))
            }
            Command::SignUp => {
                let name: String = FirstName().fake();
                let pass: String = FirstName().fake();
                self.signup(Record {
                    access: "account".to_string(),
                    namespace: "main".to_string(),
                    database: "main".to_string(),
                    params: Params {
                        name: name.clone(),
                        pass: pass.clone(),
                    },
                })
                .await?;
                Ok(format!(
                    "New user created!\n\n{{ \"name\": \"{name}\", \n \"pass\": \"{pass}\" }}"
                ))
            }
            Command::RawQuery(q) => match self.query(q).await {
                Ok(ok) => Ok(format!("{ok:?}")),
                Err(e) => Ok(e.to_string()),
            },
            Command::SignIn(s) => {
                let Ok(Params { name, pass }) = serde_json::from_str::<Params>(&s) else {
                    return Ok("Params don't work!".to_string());
                };
                self.signin(Record {
                    access: "account".to_string(),
                    namespace: "main".to_string(),
                    database: "main".to_string(),
                    params: Params {
                        name: name.clone(),
                        pass,
                    },
                })
                .await?;
                Ok(format!("Signed in as {name}!"))
            }
            Command::SignInRoot => {
                self.signin(Root {
                    username: "root".to_string(),
                    password: "secret".to_string(),
                })
                .await?;
                Ok("Back to root!".to_string())
            }
            Command::Session => Ok(self
                .query("RETURN <string>$session")
                .await?
                .take::<Option<String>>(0)?
                .unwrap_or("No session data found!".into())),
        }
    }
}

struct SurrealDbApp {
    input: String,
    results: String,
    command_sender: Sender<Command>,
    response_receiver: Receiver<String>,
}

impl SurrealDbApp {
    fn send(&mut self, command: Command) {
        if let Err(e) = self.command_sender.send(command) {
            self.results = e.to_string()
        }
    }
}

impl eframe::App for SurrealDbApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::SidePanel::left("left").show(ctx, |ui| {
            if let Ok(response) = self.response_receiver.try_recv() {
                self.results = response;
            }
            if ui.button("Create person").clicked() {
                self.send(Command::CreatePerson(self.input.clone()))
            };
            if ui.button("Delete person").clicked() {
                self.send(Command::DeletePerson(self.input.clone()))
            }
            if ui.button("List people").clicked() {
                self.send(Command::ListPeople)
            }
            if ui.button("Session data").clicked() {
                self.send(Command::Session)
            }
            if ui.button("New user").clicked() {
                self.send(Command::SignUp)
            }
            if ui.button("Sign in as record user").clicked() {
                self.send(Command::SignIn(self.input.clone()));
            }
            if ui.button("Sign in as root").clicked() {
                self.send(Command::SignInRoot)
            }
            if ui.button("Raw query").clicked() {
                self.send(Command::RawQuery(self.input.clone()))
            }
        });
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.label(RichText::new("Input:").heading());
            ui.text_edit_multiline(&mut self.input);
        });
        egui::SidePanel::right("right").show(ctx, |ui| {
            egui::ScrollArea::vertical().show(ui, |ui| {
                ui.label(RichText::new("Results:").heading());
                ui.text_edit_multiline(&mut self.results);
            });
        });
    }
}

fn main() -> Result<(), Error> {
    let (command_sender, command_receiver) = channel();
    let (response_sender, response_receiver) = channel();

    std::thread::spawn(|| -> Result<(), Error> {
        let rt = tokio::runtime::Runtime::new()?;

        rt.block_on(async {

            let client = Surreal::new::<Ws>("localhost:8000").await?;
      
            let db = Database {
                client,
                command_receiver,
                response_sender
            };

            db.signin(Root {
                username: "root".to_string(),
                password: "secret".to_string(),
            })
            .await?;

            db.use_ns("main").use_db("main").await?;

            db.query(
                "    DEFINE TABLE person SCHEMALESS
                PERMISSIONS FOR
                    CREATE, SELECT WHERE $auth,
                    FOR UPDATE, DELETE WHERE created_by = $auth;
            DEFINE FIELD name ON TABLE person TYPE string;
            DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

            DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
            DEFINE ACCESS account ON DATABASE TYPE RECORD
            SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
            SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
            DURATION FOR TOKEN 15m, FOR SESSION 12h
        ;",
            )
            .await?;

        loop {
            if let Ok(command) = db.command_receiver.try_recv() {
                match db.handle_command(command).await {
                    Ok(s) => db.response_sender.send(s)?,
                    Err(e) => db.response_sender.send(e.to_string())?
                }
            }
        }
        })
    });

    let app = SurrealDbApp {
        input: String::new(),
        results: String::new(),
        command_sender,
        response_receiver,
    };

    let native_options = eframe::NativeOptions::default();
    let _ = eframe::run_native(
        "SurrealDB App",
        native_options,
        Box::new(|_cc| Ok(Box::new(app))),
    );
    Ok(())
}
```

Here are some experiments you can do now that the app is up and running.

* `Create person` button: pass in `{ "name": "Billy", "id": "billy" }` and see the return value `Person { name: "Billy", id: RecordId { table: "person", key: String("billy") }, created_by: None }`.
* `Delete person` button: pass in `billy` to delete `person:billy`, or leave it blank to delete all the `person` records. As `.delete()` in the Rust SDK returns the records that are deleted, you will see `Person { name: "Billy", id: RecordId { table: "person", key: String("billy") }, created_by: None }` here as well.
* `List people`, `Session data`, and `Sign in as root` buttons, which only require a single click.
* `New user` button: will return an output like `New user created! { "name": "Estrella", "pass": "Jeromy" }`.
* `Sign in as record user` button: if you paste in the output from the `New user` button, you will see an output like `Signed in as Lonnie!`. Note that this query will take a fraction of a second to compute. This is because the algorithms behind cryptographic functions like [`crypto::argon2::compare()`](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) are [meant to be computationally expensive](/docs/learn/security/best-practices/security-best-practices.md#passwords) so that comparing real passwords to hashed and salted passwords takes as long as possible - but just quick enough that a single comparison is barely noticed by a legitimate user.
* `Raw query` button: runs [a raw query](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.query) and returns a [Response](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html). The output is not particularly pretty, but being able to run any query is convenient in a pinch.

Since the app lets you sign in as both a record user and a root user, let's use this to compare the permissions between the two.

First, paste in `{ "name": "Billy", "id": "billy" }` as a root user and click `Create person`, then again with `{ "name": "Billina", "id": "billina" }`.

Next, click on `New user`, copy the `JSON` output, paste it into the input in the central panel, and click on `Sign in as record user`.

Now try creating a `person` by entering `{ "name": "recorduserperson" }` into the input box and clicking on `Create person`. You should see a different output this time, as now the `created_by` field has been filled in because the `$auth` parameter currently holds the record user's ID. It should look something like this. `Person { name: "recorduserperson", id: RecordId { table: "person", key: String("70fpp3dd72hriekgclfb") }, created_by: Some(RecordId { table: "user", key: String("141datbkzq5tum9h5xvk") }) }`

We'll now imagine that the record user wants to delete all of the `person` records in the database. Delete everything in the Input box and click on `Delete person`. You should see the same output as before: just the `person` record that the record user created.

Finally, click on `List people`. The results will show that the Billy and Billina `person` records are safe and sound, because record users can only delete `person` records that they have created.

## Further steps

Now that you have a running Egui app with SurrealDB as the backend, here are some other ideas that you might want to explore.

* Compiling to WASM (and even deploying to a website). Egui can be compiled to WASM, and SurrealDB can use IndexedDB as [one of its backends](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument).

**2.x**

The following tutorial will set up a small app with Egui that uses SurrealDB as its database.

## Getting started

First, open up a terminal window and use the following command to start an empty database. This will also define a namespace and database by the name of "main" by default.

```bash
surreal start --user root --pass secret
```

You can also use the [Start serving](/docs/explore/studio.md) button on [SurrealDB Studio](/docs/explore/studio.md) to do the same if you have it installed locally.

The database initiated by the [surreal start](/docs/reference/cli/surrealdb-cli/commands/start.md) command stores data in memory by default, which then disappears every time the database is shut down. As such, you can simply use Ctrl+C every time you want to start the database anew with no existing definitions or data. To save data to disk which will persist after shutting down, add a [positional argument](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument) for one of the storage backends such as `rocksdb://mydatabase` or `surrealkv://mydatabase`.

With the database running, it's time to start setting up the Rust code.

## Starting the Rust code

First create a new Rust project with the command `cargo new your_project_name`, go into the newly created directory, and use `cargo add` to add each of the following dependencies:

* `anyhow`, to allow us to not worry about how to handle different error types,
* `egui` and its framework `eframe`,
* `rand` and `faker_rand`, to create random user names that can be used to sign in to the database as a record user,
* `serde` and `serde_json`, for serialising and deserialising Rust structs passed to and from the database and Egui,
* `tokio`, for the async runtime that SurrealDB uses.

The `serde` crate will need the `derive` flag enabled, and `tokio` will need the `rt` flag enabled as well. Your `cargo.toml` dependencies should look like this:

```text
anyhow = "1.0.91"
eframe = "0.29.1"
egui = "0.29.1"
faker_rand = "0.1.1"
rand = "0.8.5"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.132"
surrealdb = "2.4.1"
tokio = { version = "1.49.0", features = ["rt"] }
```

Before we get around to the Egui frontend, let's set up the database.

SurrealDB's Rust crate uses async code, and while usually you will see an `async fn main()` with a `#[tokio::main]` attribute on top in SurrealDB examples, Egui does not use async. To isolate one from the other, we can create the tokio runtime manually and call `.block_on()` to isolate the database in its own space. Later one, we will create two [channels](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html) to communicate between the database and the Egui app.

Inside `main()`, we will do the following:

* Connect to the database running at `localhost:8000`
* Sign in as the root user that was created through the `surreal start` command
* Use (move to) the namespace "main" and database "main"
* Use the `.query()` method to pass in a few definitions for the database.

```rust
fn main() -> Result<(), Error> {
    let rt = tokio::runtime::Runtime::new()?;

    let _: Result<(), Error> = rt.block_on(async {

            let db = Surreal::new::<Ws>("localhost:8000").await?;

            db.signin(Root {
                username: "root",
                password: "secret",
            })
            .await?;

            db.use_ns("main").use_db("main").await?;

            db.query(
                "    DEFINE TABLE person SCHEMALESS
                PERMISSIONS FOR
                    CREATE, SELECT WHERE $auth,
                    FOR UPDATE, DELETE WHERE created_by = $auth;
            DEFINE FIELD name ON TABLE person TYPE string;
            DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

            DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
            DEFINE ACCESS account ON DATABASE TYPE RECORD
            SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
            SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
            DURATION FOR TOKEN 15m, FOR SESSION 12h
        ;",
            )
            .await?;
        Ok(())
        });
    Ok(())
}
```

## What the database definitions do

The first item that stands out with the definitions above is that they all contain a `IF NOT EXISTS` clause. As the `DEFINE` statements will be executed every time the app starts, it is possible that they might be executed on a database that already has the definitions in place. SurrealDB returns an error if a definition already exists, requiring the `OVERWRITE` clause if a definition needs to be redone.

Note that this would not affect our app, as this would still be a successful usage of the `.query()` method. Instead, its output would contain a number of error results that could be handled individually:

```text
Response { client: Surreal { router: OnceLock(Router { sender: Sender { .. }, last_id: 4, features: {LiveQueries} }), engine: PhantomData<surrealdb::api::engine::any::Any> }, results: {0: (Stats { execution_time: Some(252.625µs) }, Err(Api(Query("The table 'person' already exists")))), 1: (Stats { execution_time: Some(79.167µs) }, Err(Api(Query("The field 'name' already exists")))), 2: (Stats { execution_time: Some(69.5µs) }, Err(Api(Query("The field 'created_by' already exists")))), 3: (Stats { execution_time: Some(73.625µs) }, Err(Api(Query("The index 'unique_name' already exists")))), 4: (Stats { execution_time: Some(73.583µs) }, Err(Api(Query("The access method 'account' already exists in the database 'main'"))))}, live_queries: {} }
```

However, adding `IF NOT EXISTS` is a nice way to change the results from errors into successful results, and to avoid the rare case in which they end up applied to some other version 1.x database that would rewrite its definitions if `IF NOT EXISTS` is present. So while not necessary in our case, it is a good practice to follow and makes for cleaner output.

Now let's go over each of the definitions to see what they do.

The first three statements define a `person` table. This table is schemaless, but has one required field `name`, which must be present and must be a string. This table has defined permissions by which a record user is able to use `CREATE` and `SELECT` on the `person` table, but can only `UPDATE` and `DELETE` records that it has created. The root user, however, is not subject to permissions rules.

The way these permissions are set is by using the `$auth` parameter. This parameter has a value whenever a record user is set as the authorised used for the database. The `WHERE $auth` clause simply means "where a value exists for the parameter `$auth`" (`WHERE $auth IS NOT NONE` would also work in this case). But for `UPDATE` and `DELETE` queries, it is not enough for `$auth` to just be present, the `created_by` field of a `person` record must also match the ID of the currently authenticated user.

This `created_by` field is automatically generated from its definition in the `DEFINE FIELD` statement. It is given the value of `$auth`, and is `READONLY` and thus cannot be changed. When logged in as a system user (like a root user), its value will be `NONE`. But when logged in as a record user, its value will be something like `user:qx2apv5oc8mh03wtah0q`.

```surql
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;
```

So where does an ID like `user:qx2apv5oc8mh03wtah0q` come from? This is thanks to the following definitions that set the signup and signin behaviour of the record users. A typical [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) statement will create some sort of record on signup (in this case, a `user`) record, and will compare it against a password during signin. Note that the access has a name that we gave it (`account`), so that it can be referenced elsewhere.

In addition, a `DEFINE INDEX` statement with a `UNIQUE` clause is used to ensure that no two users can have the same name.

```surql
    DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
```

For an actual user in production, you would probably want to require an email and some other fields. Functions like [`string::is_email`](/docs/reference/query-language/functions/database-functions/string.md#stringis_email) can be used to ensure that the value passed in is valid.

```surql
DEFINE FIELD email ON TABLE user TYPE string ASSERT $value.is_email();
```

However, for this simple example, each user will simply have a unique name and a password. The password will be stored in hashed and salted form on the database, making it unique and unreadable every time it is generated. The only way to check if it is correct is by using a [compare function](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) of the output with an attempted password. Here is a short SurrealQL sample to show how the process works.

```surql
LET $hash1 = crypto::argon2::generate("myPaSSWord");
LET $hash2 = crypto::argon2::generate("myPaSSWord");

RETURN [$hash1, $hash2];
-- First returns true, second returns false
RETURN [
    crypto::argon2::compare($hash1, "myPaSSWord")
    crypto::argon2::compare($hash1, "Wrongpassword")
];
```

## The Egui code

### The Database struct

We are now at the point where the majority of the work takes place: creating the actual app and a way for it to interact with the database.

To keep the code to a minimum, our app will be as simple as possible. It will have a few buttons, a panel to take user input, and a second panel to display results. The buttons will be as follows:

* `Create person`: Instructs the database to try to create a `person` record based on the input provided by the user.
* `Delete person`: Deletes all the `person` records if the user input is left blank, otherwise will take a single id.
* `List people`: Shows all the `person` records in the database.
* `Session data`: Shows the current session data.
* `Raw query`: Allows the user to execute a raw query.
* `New user`: Creates a new record user with a random name and password, displayed as an object.
* `Sign in as record user`: Signs in using an object with a name and a password. The `New user` output can be pasted in to sign in here.
* `Sign in as root`: Signs back in as the database root user.

The behaviour of an Egui app takes place inside a single [.update()](https://docs.rs/eframe/latest/eframe/trait.App.html#tymethod.update), which takes a mutable reference to the app (usually a struct).

Because this function can be called up to several times per second, waiting for even short database queries might have noticeable effects on the repainting of the app. To ensure that this won't happen, we will create two channels between the Egui app and the database. The first channel will send commands from the app to the database, while the second channel will send each response back as a simple `String`. The database will loop continuously as it checks for commands, while the app will check for responses during every iteration of the `.update()` function.

Here are the two apps and the types used to communicate with each other.

```rust
struct SurrealDbApp {
    input: String,
    results: String,
    command_sender: Sender<Command>,
    response_receiver: Receiver<String>,
}

struct Database {
    client: Surreal<Client>,
    command_receiver: Receiver<Command>,
    response_sender: Sender<String>,
}

#[derive(Debug, Clone)]
enum Command {
    CreatePerson(String),
    DeletePerson(String),
    ListPeople,
    RawQuery(String),
    SignUp,
    SignIn(String),
    SignInRoot,
    Session,
}
```

Egui has [sample code for running a native app](https://docs.rs/eframe/latest/eframe/#usage-native) that shows how to start one that we can copy and paste, only changing the name. Here is what the `main()` portion of the final code will look like.

```rust
fn main() -> Result<(), Error> {
    let (command_sender, command_receiver) = channel();
    let (response_sender, response_receiver) = channel();

    std::thread::spawn(|| -> Result<(), Error> {
        let rt = tokio::runtime::Runtime::new()?;

        rt.block_on(async {

            let client = Surreal::new::<Ws>("localhost:8000").await?;

            let db = Database {
                client,
                command_receiver,
                response_sender
            };

            db.signin(Root {
                username: "root",
                password: "secret",
            })
            .await?;

            db.use_ns("main").use_db("main").await?;

            db.query(
                "    DEFINE TABLE person SCHEMALESS
                PERMISSIONS FOR
                    CREATE, SELECT WHERE $auth,
                    FOR UPDATE, DELETE WHERE created_by = $auth;
            DEFINE FIELD name ON TABLE person TYPE string;
            DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

            DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
            DEFINE ACCESS account ON DATABASE TYPE RECORD
            SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
            SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
            DURATION FOR TOKEN 15m, FOR SESSION 12h
        ;",
            )
            .await?;

        loop {
            if let Ok(command) = db.command_receiver.try_recv() {
                match db.handle_command(command).await {
                    Ok(s) => db.response_sender.send(s)?,
                    Err(e) => db.response_sender.send(e.to_string())?
                }
            }
        }
        })
    });

    let app = SurrealDbApp {
        input: String::new(),
        results: String::new(),
        command_sender,
        response_receiver,
    };

    let native_options = eframe::NativeOptions::default();
    let _ = eframe::run_native(
        "SurrealDB App",
        native_options,
        Box::new(|_cc| Ok(Box::new(app))),
    );
    Ok(())
}
```

The database calls a function called `.handle_command()` every time it receives a command, so let's take a look at that one. It uses a simple `match` statement and executes database queries depending on what it is asked to do.

We'll start with some notable parts of the `.handle_command()` function and related code.

First we have two pieces of code added for convenience. One implements `Deref` so that the `Database` struct can call methods like `.create()` instead of `.client.create()`. The other is a helper trait so that we can call `.string()` after each method that returns an `Option<Person>` instead of having to use a `match` statement every time. There is also a `const` declared with `const PERSON: &str = "person"` that removes the possibility of typos inside the various query methods.

```rust
impl Deref for Database {
    type Target = Surreal<Client>;

    fn deref(&self) -> &Self::Target {
        &self.client
    }
}

trait StringIt {
    fn string(self) -> Result<String, Error>;
}

impl StringIt for Option<Person> {
    fn string(self) -> Result<String, Error> {
        match self {
            Some(t) => Ok(format!("{t:?}")),
            None => Ok("[]".into()),
        }
    }
}

const PERSON: &str = "person";
```

When the `.handle_command()` method comes across a `Command::CreatePerson`, which contains a `String`, it will attempt to turn it into this `PersonData` struct.

```rust
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct PersonData {
    name: String,
    id: Option<String>,
}
```

This can be done using the `serde_json::from_str()` function. If the input is properly formatted, such as `{ "name": "Billy" }`, it will deserialise into a `PersonData` that can then be passed into the `.create()` function. The helper function `.string()` will then pass it back as an `Ok` with the `String` data inside if successful.

For simplicity, data passed in will only ever deserialise into a `Person` app with three fields: a name, a record Id, and a possible `created_by` field which will have a value if the `person` record is created by a record user.

```rust
#[derive(Serialize, Deserialize, Debug)]
pub struct Person {
    name: String,
    id: RecordId,
    created_by: Option<RecordId>,
}

Command::CreatePerson(s) => {
    let person_data: PersonData = serde_json::from_str(&s)?;
    self.create::<Option<Person>>(PERSON)
        .content(person_data)
        .await?
        .string()
}
```

For the `Command::DeletePerson` variant, a check is made to see whether the user input is empty, in which case it will delete every `person` record. Otherwise, it will assume that the input is the key of a record ID (like the `one` in `person:one`) and delete that record if it exists.

```rust
Command::DeletePerson(s) => {
    if s.is_empty() {
        let res: Vec<Person> = self.delete(PERSON).await?;
        Ok(format!("{res:?}"))
    } else {
        let key = RecordIdKey::from(s);
        self.delete::<Option<Person>>((PERSON, key)).await?.string()
    }
}
```

The other three query methods are pretty simple. `Command::ListPeople` returns all `person` records, `Command::RawQuery` takes a direct SurrealQL input and returns the result, and `Command::Session` just accesses the `$session` parameter cast into a string.

```rust
Command::ListPeople => {
    let person: Vec<Person> = self.select(PERSON).await?;
    Ok(format!("{person:?}"))
}
Command::RawQuery(q) => match self.query(q).await {
    Ok(ok) => Ok(format!("{ok:?}")),
    Err(e) => Ok(e.to_string()),
},
Command::Session => Ok(self
    .query("RETURN <string>$session")
    .await?
    .take::<Option<String>>(0)?
    .unwrap_or("No session data found!".into()))
```

The `Command::SignUp` and `Command::SignIn` variants are a bit more interesting.

A user is allowed to choose a name and a password when signing up as a record user, but to make the process as quick as possible we will use the `faker_rand` crate to generate two names: one for the username and one for the password.

The [`.signup()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signup) method actually returns a token (a [`JWT`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Jwt.html) struct) that can [display the actual token](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Jwt.html#method.as_insecure_token) if preferred, but these tokens are mostly useful when signing in via the [surreal sql](/docs/reference/cli/surrealdb-cli/commands/sql.md) command or through SurrealDB Studio. In our case, we can simply use the `.signin()` method along with a name and password and so we don't need to display the token.

The `Params` struct is our own struct, which holds a `name` and a `pass` field because those are the two fields that we specified in the `DEFINE ACCESS` statement which creates a new `user` record every time a record user is signed up. Similarly, the `access` field inside `.signup()` takes the input "account" because that is the name that we have to the `DEFINE ACCESS` statement.

```rust
// DEFINE ACCESS account ON DATABASE TYPE RECORD
// SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
// SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
// DURATION FOR TOKEN 15m, FOR SESSION 12h

#[derive(Serialize, Deserialize)]
struct Params<'a> {
    name: &'a str,
    pass: &'a str,
}

Command::SignUp => {
    let name = rand::random::<FirstName>().to_string();
    let pass = rand::random::<FirstName>().to_string();
    self.signup(Record {
        access: "account",
        namespace: "main",
        database: "main",
        params: Params {
            name: &name,
            pass: &pass,
        },
    })
    .await?;
    Ok(format!(
        "New user created!\n\n{{ \"name\": \"{name}\", \n \"pass\": \"{pass}\" }}"
    ))
}
```

The output when the button is clicked to sign up a new user is in `JSON` format so that the user can copy and paste it to sign in.

```text
New user created!

{ "name": "Rebecca", 
 "pass": "Neha" }
```

Signing in is pretty similar, except that it begins by trying to deserialise the user input into a `Params` struct. The `.signin()` method also returns a `Jwt` that we don't need, so the output will just let the user know that it has signed in under a certain name.

```rust
Command::SignIn(s) => {
    let Ok(Params { name, pass }) = serde_json::from_str::<Params>(&s) else {
        return Ok("Params don't work!".to_string());
    };
    self.signin(Record {
        access: "account",
        namespace: "main",
        database: "main",
        params: Params { name, pass },
    })
    .await?;
    Ok(format!("Signed in as {name}!"))
}
```

The last command will allow the user to sign back in as the root user. To make it easy to experiment with the database as a record user vs. a root user, we won't make the user type in the root user's name and password each time.

```rust
Command::SignInRoot => {
    self.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    Ok(format!("Back to root!"))
}
```

### The Egui app struct

The struct for the Egui app is quite simple, with only four fields. Two of them hold the user input and database results, which will be displayed on the screen at all times. The other two fields are for the sending and receiving end of the two channels.

```rust
struct SurrealDbApp {
    input: String,
    results: String,
    command_sender: Sender<Command>,
    response_receiver: Receiver<String>,
}
```

Since the [`.send()`](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html#method.send) function for channels in Rust returns a `Result` but Egui's `.update()` function doesn't, we'll save ourselves a lot of typing by putting together a quick method for this struct called `.send()` that does the error handling. All it will do is turn any errors into a `String` which it will then give to the `results` field.

```rust
impl SurrealDbApp {
    fn send(&mut self, command: Command) {
        if let Err(e) = self.command_sender.send(command) {
            self.results = e.to_string()
        }
    }
}
```

After that, the final task left to us is to create the layout and buttons for the Egui app inside the `update()` function that all Egui app structs are required to implement. The first line will be the one where the app checks to see if the database has sent it a message. All of these messages are simple Strings, so it will just set the `results` field with them so the user can see what was sent. Note that the `Ok` here just means that `.try_recv()` has successfully received a message, not that the database has succeeded at what it was instructed to do. The `String` might contain successful data, or an error message.

```rust
if let Ok(response) = self.response_receiver.try_recv() {
    self.results = response;
}
```

After this come the buttons. The [`SidePanel::left()`](https://docs.rs/egui/latest/egui/containers/panel/struct.SidePanel.html) will create a panel on the left side of the screen inside which we can add the buttons. If the button is clicked, the app will send off a command that may or might not include the data from the `input` field.

```rust
egui::SidePanel::left("left").show(ctx, |ui| {
    if let Ok(response) = self.response_receiver.try_recv() {
        self.results = response;
    }
    if ui.button("Create person").clicked() {
        self.send(Command::CreatePerson(self.input.clone()))
    };
    if ui.button("Delete person").clicked() {
        self.send(Command::DeletePerson(self.input.clone()))
    }
    if ui.button("List people").clicked() {
        self.send(Command::ListPeople)
    }
    if ui.button("Session data").clicked() {
        self.send(Command::Session)
    }
    if ui.button("New user").clicked() {
        self.send(Command::SignUp)
    }
    if ui.button("Sign in as record user").clicked() {
        self.send(Command::SignIn(self.input.clone()));
    }
    if ui.button("Sign in as root").clicked() {
        self.send(Command::SignInRoot)
    }
    if ui.button("Raw query").clicked() {
        self.send(Command::RawQuery(self.input.clone()))
    }
});
```

The final bit of code just involves creating two more panels, one in the centre and one on the right. The one on the right will add a [`ScrollArea`](https://docs.rs/egui/latest/egui/containers/scroll_area/struct.ScrollArea.html) so that the user can scroll through any results that are larger than the space in the right panel.

```rust
egui::CentralPanel::default().show(ctx, |ui| {
    ui.label(RichText::new("Input:").heading());
    ui.text_edit_multiline(&mut self.input);
});
egui::SidePanel::right("right").show(ctx, |ui| {
    egui::ScrollArea::vertical().show(ui, |ui| {
        ui.label(RichText::new("Results:").heading());
        ui.text_edit_multiline(&mut self.results);
    });
});
```

And that's all the code!

## Experimenting with the app

The final code looks like this:

```rust
use std::{
    ops::Deref,
    sync::mpsc::{channel, Receiver, Sender},
};

use egui::RichText;
use surrealdb::{
    engine::remote::ws::{Client, Ws},
    opt::auth::{Record, Root},
    RecordId, RecordIdKey, Surreal,
};

use anyhow::Error;
use faker_rand::en_us::names::FirstName;
use serde::{Deserialize, Serialize};

const PERSON: &str = "person";

#[derive(Serialize, Deserialize)]
struct Params<'a> {
    name: &'a str,
    pass: &'a str,
}

#[derive(Serialize, Deserialize, Clone, Default)]
pub struct PersonData {
    name: String,
    id: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Person {
    name: String,
    id: RecordId,
    created_by: Option<RecordId>,
}

#[derive(Debug, Clone)]
enum Command {
    CreatePerson(String),
    DeletePerson(String),
    ListPeople,
    RawQuery(String),
    SignUp,
    SignIn(String),
    SignInRoot,
    Session,
}

struct Database {
    client: Surreal<Client>,
    command_receiver: Receiver<Command>,
    response_sender: Sender<String>,
}

impl Deref for Database {
    type Target = Surreal<Client>;

    fn deref(&self) -> &Self::Target {
        &self.client
    }
}

trait StringIt {
    fn string(self) -> Result<String, Error>;
}

impl StringIt for Option<Person> {
    fn string(self) -> Result<String, Error> {
        match self {
            Some(t) => Ok(format!("{t:?}")),
            None => Ok("[]".into()),
        }
    }
}

impl Database {
    async fn handle_command(&self, command: Command) -> Result<String, Error> {
        match command {
            Command::CreatePerson(s) => {
                let person_data: PersonData = serde_json::from_str(&s)?;
                self.create::<Option<Person>>(PERSON)
                    .content(person_data)
                    .await?
                    .string()
            }
            Command::DeletePerson(s) => {
                if s.is_empty() {
                    let res: Vec<Person> = self.delete(PERSON).await?;
                    Ok(format!("{res:?}"))
                } else {
                    let key = RecordIdKey::from(s);
                    self.delete::<Option<Person>>((PERSON, key)).await?.string()
                }
            }
            Command::ListPeople => {
                let person: Vec<Person> = self.select(PERSON).await?;
                Ok(format!("{person:?}"))
            }
            Command::SignUp => {
                let name = rand::random::<FirstName>().to_string();
                let pass = rand::random::<FirstName>().to_string();
                self.signup(Record {
                    access: "account",
                    namespace: "main",
                    database: "main",
                    params: Params {
                        name: &name,
                        pass: &pass,
                    },
                })
                .await?;
                Ok(format!(
                    "New user created!\n\n{{ \"name\": \"{name}\", \n \"pass\": \"{pass}\" }}"
                ))
            }
            Command::RawQuery(q) => match self.query(q).await {
                Ok(ok) => Ok(format!("{ok:?}")),
                Err(e) => Ok(e.to_string()),
            },
            Command::SignIn(s) => {
                let Ok(Params { name, pass }) = serde_json::from_str::<Params>(&s) else {
                    return Ok("Params don't work!".to_string());
                };
                self.signin(Record {
                    access: "account",
                    namespace: "main",
                    database: "main",
                    params: Params { name, pass },
                })
                .await?;
                Ok(format!("Signed in as {name}!"))
            }
            Command::SignInRoot => {
                self.signin(Root {
                    username: "root",
                    password: "secret",
                })
                .await?;
                Ok(format!("Back to root!"))
            }
            Command::Session => Ok(self
                .query("RETURN <string>$session")
                .await?
                .take::<Option<String>>(0)?
                .unwrap_or("No session data found!".into())),
        }
    }
}

struct SurrealDbApp {
    input: String,
    results: String,
    command_sender: Sender<Command>,
    response_receiver: Receiver<String>,
}

impl SurrealDbApp {
    fn send(&mut self, command: Command) {
        if let Err(e) = self.command_sender.send(command) {
            self.results = e.to_string()
        }
    }
}

impl eframe::App for SurrealDbApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::SidePanel::left("left").show(ctx, |ui| {
            if let Ok(response) = self.response_receiver.try_recv() {
                self.results = response;
            }
            if ui.button("Create person").clicked() {
                self.send(Command::CreatePerson(self.input.clone()))
            };
            if ui.button("Delete person").clicked() {
                self.send(Command::DeletePerson(self.input.clone()))
            }
            if ui.button("List people").clicked() {
                self.send(Command::ListPeople)
            }
            if ui.button("Session data").clicked() {
                self.send(Command::Session)
            }
            if ui.button("New user").clicked() {
                self.send(Command::SignUp)
            }
            if ui.button("Sign in as record user").clicked() {
                self.send(Command::SignIn(self.input.clone()));
            }
            if ui.button("Sign in as root").clicked() {
                self.send(Command::SignInRoot)
            }
            if ui.button("Raw query").clicked() {
                self.send(Command::RawQuery(self.input.clone()))
            }
        });
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.label(RichText::new("Input:").heading());
            ui.text_edit_multiline(&mut self.input);
        });
        egui::SidePanel::right("right").show(ctx, |ui| {
            egui::ScrollArea::vertical().show(ui, |ui| {
                ui.label(RichText::new("Results:").heading());
                ui.text_edit_multiline(&mut self.results);
            });
        });
    }
}

fn main() -> Result<(), Error> {
    let (command_sender, command_receiver) = channel();
    let (response_sender, response_receiver) = channel();

    std::thread::spawn(|| -> Result<(), Error> {
        let rt = tokio::runtime::Runtime::new()?;

        rt.block_on(async {

            let client = Surreal::new::<Ws>("localhost:8000").await?;
      
            let db = Database {
                client,
                command_receiver,
                response_sender
            };

            db.signin(Root {
                username: "root",
                password: "secret",
            })
            .await?;

            db.use_ns("main").use_db("main").await?;

            db.query(
                "    DEFINE TABLE person SCHEMALESS
                PERMISSIONS FOR
                    CREATE, SELECT WHERE $auth,
                    FOR UPDATE, DELETE WHERE created_by = $auth;
            DEFINE FIELD name ON TABLE person TYPE string;
            DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

            DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
            DEFINE ACCESS account ON DATABASE TYPE RECORD
            SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
            SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
            DURATION FOR TOKEN 15m, FOR SESSION 12h
        ;",
            )
            .await?;

        loop {
            if let Ok(command) = db.command_receiver.try_recv() {
                match db.handle_command(command).await {
                    Ok(s) => db.response_sender.send(s)?,
                    Err(e) => db.response_sender.send(e.to_string())?
                }
            }
        }
        })
    });

    let app = SurrealDbApp {
        input: String::new(),
        results: String::new(),
        command_sender,
        response_receiver,
    };

    let native_options = eframe::NativeOptions::default();
    let _ = eframe::run_native(
        "SurrealDB App",
        native_options,
        Box::new(|_cc| Ok(Box::new(app))),
    );
    Ok(())
}
```

Here are some experiments you can do now that the app is up and running.

* `Create person` button: pass in `{ "name": "Billy", "id": "billy" }` and see the return value `Person { name: "Billy", id: RecordId { table: "person", key: String("billy") }, created_by: None }`.
* `Delete person` button: pass in `billy` to delete `person:billy`, or leave it blank to delete all the `person` records. As `.delete()` in the Rust SDK returns the records that are deleted, you will see `Person { name: "Billy", id: RecordId { table: "person", key: String("billy") }, created_by: None }` here as well.
* `List people`, `Session data`, and `Sign in as root` buttons, which only require a single click.
* `New user` button: will return an output like `New user created! { "name": "Estrella", "pass": "Jeromy" }`.
* `Sign in as record user` button: if you paste in the output from the `New user` button, you will see an output like `Signed in as Lonnie!`. Note that this query will take a fraction of a second to compute. This is because the algorithms behind cryptographic functions like [`crypto::argon2::compare()`](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) are [meant to be computationally expensive](/docs/learn/security/best-practices/security-best-practices.md#passwords) so that comparing real passwords to hashed and salted passwords takes as long as possible - but just quick enough that a single comparison is barely noticed by a legitimate user.
* `Raw query` button: runs [a raw query](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.query) and returns a [Response](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html). The output is not particularly pretty, but being able to run any query is convenient in a pinch.

Since the app lets you sign in as both a record user and a root user, let's use this to compare the permissions between the two.

First, paste in `{ "name": "Billy", "id": "billy" }` as a root user and click `Create person`, then again with `{ "name": "Billina", "id": "billina" }`.

Next, click on `New user`, copy the `JSON` output, paste it into the input in the central panel, and click on `Sign in as record user`.

Now try creating a `person` by entering `{ "name": "recorduserperson" }` into the input box and clicking on `Create person`. You should see a different output this time, as now the `created_by` field has been filled in because the `$auth` parameter currently holds the record user's ID. It should look something like this. `Person { name: "recorduserperson", id: RecordId { table: "person", key: String("70fpp3dd72hriekgclfb") }, created_by: Some(RecordId { table: "user", key: String("141datbkzq5tum9h5xvk") }) }`

We'll now imagine that the record user wants to delete all of the `person` records in the database. Delete everything in the Input box and click on `Delete person`. You should see the same output as before: just the `person` record that the record user created.

Finally, click on `List people`. The results will show that the Billy and Billina `person` records are safe and sound, because record users can only delete `person` records that they have created.

## Further steps

Now that you have a running Egui app with SurrealDB as the backend, here are some other ideas that you might want to explore.

* Compiling to WASM (and even deploying to a website). Egui can be compiled to WASM, and SurrealDB can use IndexedDB as [one of its backends](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument).

---

Source: https://surrealdb.com/docs/reference/rust/frameworks/rocket

# Rocket

The SDK for Rust allows SurrealDB to be used as the storage backend for a Rocket web server

**3.x**

The following tutorial will set up a server with SurrealDB and [Rocket](https://rocket.rs/) that has a few endpoints:

* Some endpoints to demonstrate how the HTTP endpoints work to create, select, modify etc. a `person` table in a database,
* Other endpoints to display some helpful info for the user,
* Two endpoints to allow signing up and signing in as a [record user](/docs/reference/query-language/statements/define/access/record.md).

## Getting started

First, open up a terminal window and use the following command to start an empty database. This will also define a namespace and database by the name of "main" by default.

```bash
surreal start --user root --pass secret
```

You can also use the [Start serving](/docs/explore/studio.md) button on [SurrealDB Studio](/docs/explore/studio.md) to do the same if you have it installed locally.

The database initiated by the [surreal start](/docs/reference/cli/surrealdb-cli/commands/start.md) command stores data in memory by default, which then disappears every time the database is shut down. As such, you can simply use Ctrl+C every time you want to start the database anew with no existing definitions or data. To save data to disk which will persist after shutting down, add a [positional argument](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument) for one of the storage backends such as `rocksdb://mydatabase` or `surrealkv://mydatabase` (or `surrealkv+versioned//mydatabase` to include SurrealKV versioning).

With the database running, we will now connect to the database "main" located in the namespace "main". You can connect to it by [creating a connection](/docs/explore/studio.md) inside SurrealDB Studio, or by using the following command to start an interactive shell.

```bash
surreal sql --user root --pass secret --pretty
```

Next, create a new Rust project with the command `cargo new your_project_name`, go into the newly created directory, and use `cargo add` to add each of the following dependencies:

* `surrealdb` (of course),
* `rocket`,
* `serde`, for serialising and deserialising Rust structs passed to and from the database and Rocket,
* `thiserror`, to make it easy to convert between SurrealDB's error type, other errors and Rocket's response types,
* `fake`, to create random user names that can be used to sign in to the database as a record user.

The `serde` crate will need the `derive` flag, and `rocket` will need the `json` flag enabled. Your `cargo.toml` dependencies should look like this:

```text
fake = "4.4.0"
rocket = { version = "0.5.1", features = ["json"] }
serde = { version = "1.0.228", features = ["derive"] }
surrealdb = "3.2.0"
thiserror = "2.0.18"
```

## Starting the Rust code

The first thing to do is a bit of groundwork to convert database errors into an error type of our own. Implementing `From<surrealdb::Error>` for this type will let it be used with the `?` operator when handling results. Finally, it will also need to implement Rocket's `Responder` trait so that it can be used as output for the server. All of this can be done manually if you prefer, but the `thiserror` crate saves a certain amount of typing.

```rust
mod error {
    use rocket::http::Status;
    use rocket::response::{self, Responder, Response};
    use rocket::Request;
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db,
    }

    impl<'r> Responder<'r, 'static> for Error {
        fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
            let error_message = format!(r#"{{ "error": "{self}" }}"#);
            Response::build()
                .status(Status::InternalServerError)
                .header(rocket::http::ContentType::JSON)
                .sized_body(error_message.len(), std::io::Cursor::new(error_message))
                .ok()
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db
        }
    }
}
```

Next, we will put the database client together. Rocket provides a [`.manage()`](https://rocket.rs/guide/v0.5/state/) method when starting a router that would give us access to the database inside its functions. However, for simplicity we can instead wrap the client inside a `LazyLock` to make it into a global static.

```rust
use std::sync::LazyLock;
static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);
```

Inside a method called `init()` to initiate the database, we will do the following:

* Connect to the database running at `localhost:8000`
* Sign in as the root user that was created through the `surreal start` command
* Use (move to) the namespace "main" and database "main"
* Use the `.query()` method to pass in a few definitions for the database.

```rust
async fn init() -> Result<(), surrealdb::Error> {
    DB.connect::<Ws>("localhost:8000").await?;

    DB.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    DB.use_ns("main").use_db("main").await?;

    DB.query(
        "    DEFINE TABLE person SCHEMALESS
        PERMISSIONS FOR 
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD name ON TABLE person TYPE string;
    DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;
    Ok(())
}
```

## What the database definitions do

The first item that stands out with the definitions above is that they all contain a `IF NOT EXISTS` clause. As the `DEFINE` statements will be executed every time the app starts, it is possible that they might be executed on a database that already has the definitions in place. SurrealDB returns an error if a definition already exists, requiring the `OVERWRITE` clause if a definition needs to be redone.

Note that this would not affect our app, as this would still be a successful usage of the `.query()` method. Instead, its output would contain a number of error results that could be handled individually:

```text
IndexedResults { results: {0: (DbResultStats { execution_time: Some(44.75µs), query_type: Some(Other) }, Err(Thrown("The table 'person' already exists"))), 1: (DbResultStats { execution_time: Some(87.042µs), query_type: Some(Other) }, Err(Thrown("The field 'name' already exists"))), 2: (DbResultStats { execution_time: Some(21.125µs), query_type: Some(Other) }, Err(Thrown("The field 'created_by' already exists"))), 3: (DbResultStats { execution_time: Some(13.584µs), query_type: Some(Other) }, Err(Thrown("The index 'unique_name' already exists"))), 4: (DbResultStats { execution_time: Some(36.125µs), query_type: Some(Other) }, Err(Thrown("The access method 'account' already exists in the database 'main'")))}, live_queries: {} }
```

However, adding `IF NOT EXISTS` is a nice way to change the results from errors into successful results, and to avoid the rare case in which they end up applied to some other version 1.x database that would rewrite its definitions if `IF NOT EXISTS` is present. So while not necessary in our case, it is a good practice to follow and makes for cleaner output.

Now let's go over each of the definitions to see what they do.

The first three statements define a `person` table. This table is schemaless, but has one required field `name`, which must be present and must be a string. This table has defined permissions by which a record user is able to use `CREATE` and `SELECT` on the `person` table, but can only `UPDATE` and `DELETE` records that it has created. The root user, however, is not subject to permissions rules.

The way these permissions are set is by using the `$auth` parameter. This parameter has a value whenever a record user is set as the authorised used for the database. The `WHERE $auth` clause simply means "where a value exists for the parameter `$auth`" (`WHERE $auth IS NOT NONE` would also work in this case). But for `UPDATE` and `DELETE` queries, it is not enough for `$auth` to just be present, the `created_by` field of a `person` record must also match the ID of the currently authenticated user.

This `created_by` field is automatically generated from its definition in the `DEFINE FIELD` statement. It is given the value of `$auth`, and is `READONLY` and thus cannot be changed. When logged in as a system user (like a root user), its value will be `NONE`. But when logged in as a record user, its value will be something like `user:qx2apv5oc8mh03wtah0q`.

```surql
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;
```

So where does an ID like `user:qx2apv5oc8mh03wtah0q` come from? This is thanks to the following definitions that set the signup and signin behaviour of the record users. A typical [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) statement will create some sort of record on signup (in this case, a `user`) record, and will compare it against a password during signin. Note that the access has a name that we gave it (`account`), so that it can be referenced elsewhere.

In addition, a `DEFINE INDEX` statement with a `UNIQUE` clause is used to ensure that no two users can have the same name.

```surql
    DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
```

For an actual user in production, you would probably want to require an email and some other fields. Functions like [`string::is_email`](/docs/reference/query-language/functions/database-functions/string.md#stringis_email) can be used to ensure that the value passed in is valid.

```surql
DEFINE FIELD email ON TABLE user TYPE string ASSERT $value.is_email();
```

However, for this simple example, each user will simply have a unique name and a password. The password will be stored in hashed and salted form on the database, making it unique and unreadable every time it is generated. The only way to check if it is correct is by using a [compare function](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) of the output with an attempted password. Here is a short SurrealQL sample to show how the process works.

```surql
LET $hash1 = crypto::argon2::generate("myPaSSWord");
LET $hash2 = crypto::argon2::generate("myPaSSWord");

RETURN [$hash1, $hash2];
-- First returns true, second returns false
RETURN [
    crypto::argon2::compare($hash1, "myPaSSWord"),
    crypto::argon2::compare($hash1, "Wrongpassword")
];
```

## The rest of the code

The last step is where the majority of the work takes place: setting up the paths for Rocket to handle, and writing the functions that handle the endpoints and (usually) access the database to handle the request. To start, we'll create a function for the `"/"` root path to display a helpful message to anybody giving the server a try via the browser or an app like curl or Postman. These paths and curl examples can all be seen on [the page for SurrealDB's HTTP endpoints](/docs/reference/rest-api/http-protocol.md).

```rust
    #[get("/")]
    pub async fn paths() -> &'static str {
        r#"

-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John Doe"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane Doe"}' http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                          http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }
```

Each of these functions will be put into a mod called `routes`, leading to the following code inside Rocket's `rocket()` function (its equivalent of `main()`).

```rust
#[launch]
pub async fn rocket() -> _ {
    init().await.expect("Something went wrong, shutting down");
    rocket::build().mount(
        "/",
        routes![
            routes::create_person,
            routes::read_person,
            routes::update_person,
            routes::delete_person,
            routes::list_people,
            routes::paths,
            routes::make_new_user,
            routes::get_new_token,
            routes::session
        ],
    )
}
```

Many functions require some JSON data from the user, which will be deserialised into a `PersonData` struct. The database can then use it in methods like `.create().content()`. The output returned will now have a `name` and an `id`, which the `Person` struct holds.

```rust
#[derive(SurrealValue, Serialize, Deserialize, Clone)]
pub struct PersonData {
    name: String,
}

#[derive(SurrealValue, Serialize, Deserialize)]
pub struct Person {
    name: String,
    id: RecordId,
}
```

Each of these functions are pretty straightforward: obtain some user input, initiate a query, feed the user input into it, and return it as JSON.

```rust
const PERSON: &str = "person";

#[post("/person/<id>", data = "<person>")]
pub async fn create_person(
    id: String,
    person: Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB
        .create((PERSON, &*id))
        .content(person.into_inner())
        .await?;
    Ok(Json(person))
}

#[get("/person/<id>")]
pub async fn read_person(id: String) -> Result<Json<Option<Person>>, Error> {
    let person = DB.select((PERSON, &*id)).await?;
    Ok(Json(person))
}

#[put("/person/<id>", data = "<person>")]
pub async fn update_person(
    id: String,
    person: Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB
        .update((PERSON, &*id))
        .content(person.into_inner())
        .await?;
    Ok(Json(person))
}

#[delete("/person/<id>")]
pub async fn delete_person(id: String) -> Result<Json<Option<Person>>, Error> {
    let person = DB.delete((PERSON, &*id)).await?;
    Ok(Json(person))
}

#[get("/people")]
pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
    let people = DB.select(PERSON).await?;
    Ok(Json(people))
}
```

The `session()` function is also quite small, and is just a convenience for a user curious about the current session data. As the `.query()` method can take more than one statement, it returns each of these responses in order with an index for each (starting at 0). The `.take()` method can then be used to access the response at that index, and turn it into anything that can be deserialised back into a Rust type. In our case, a `String` is all we need here as the output will only be used to show the user the current session info.

```rust
#[get("/session")]
pub async fn session() -> Result<Json<String>, Error> {
    let res: Option<String> = DB.query("<string>$session").await?.take(0)?;

    Ok(Json(res.unwrap_or("No session data found!".into())))
}
```

The most interesting function is the one used to create a new record user. To make it really easy to try out the experience of logging in as a record user, this function will use create a random name and password each time it is accessed. It will then pass in a [`Record`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Record.html) struct which is used to sign up a new record user. Note the following:

* The access name is `"account"`, which is the name we chose in the `DEFINE ACCESS` statement above.
* The `params` field takes anything that implements `SurrealValue`, in this case a struct we put together called `Params`.
* The `.signup()` method returns a `Token` which itself contains an `AccessToken`, which is the JWT. To make the token visible, you can use the `.into_insecure_token()` method as we have done here. As a small guide to getting started, this example is not concerned about security. However, if you are looking to create something more production-worthy, do take a look at the [security](/docs/learn/security.md) section of the documentation and the [security best practices](/docs/learn/security/best-practices/security-best-practices.md) page.

The function will then end with an output showing the username, password, token, and instructions for how to log in using the CLI. This can be copied and pasted to begin making queries immediately.

```rust
#[derive(SurrealValue)]
struct Params {
    name: String,
    pass: String,
}

#[get("/new_user")]
pub async fn make_new_user() -> Result<String, Error> {
    let name = rand::random::<FirstName>().to_string();
    let pass = rand::random::<FirstName>().to_string();
    let jwt = DB
        .signup(Record {
            access: "account",
            namespace: "namespace",
            database: "database",
            params: Params {
                name: &name,
                pass: &pass,
            },
        })
        .await?
        .into_insecure_token();
    Ok(format!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --pretty --token \"{jwt}\""))
}
```

A record user with an expired token can use the `/signin` endpoint to get a new token. Since this requires passing in a username and password, we'll just have this function return a `String` that contains a curl example to get a new token.

```rust
#[get("/new_token")]
pub async fn get_new_token() -> String {
    let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
    format!("Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --pretty --token YOUR_TOKEN_HERE")
}
```

## Experimenting with the app

The final code looks like this. To run it, use `ROCKET_PORT=8080 cargo run` to first set the `ROCKET_PORT` env var to 8080.

```rust
#[macro_use]
extern crate rocket;

use std::sync::LazyLock;
use surrealdb::engine::remote::ws::Client;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);

mod error {
    use rocket::http::Status;
    use rocket::response::{self, Responder, Response};
    use rocket::Request;
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db,
    }

    impl<'r> Responder<'r, 'static> for Error {
        fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
            let error_message = format!(r#"{{ "error": "{self}" }}"#);
            Response::build()
                .status(Status::InternalServerError)
                .header(rocket::http::ContentType::JSON)
                .sized_body(error_message.len(), std::io::Cursor::new(error_message))
                .ok()
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db
        }
    }
}

mod routes {

    use fake::Fake;
    use fake::faker::name::en::FirstName;
    use surrealdb::opt::auth::Record;
    use surrealdb::types::{RecordId, SurrealValue};

    use crate::error::Error;
    use crate::DB;
    use rocket::serde::json::Json;
    use rocket::{delete, get, post, put};
    use serde::{Deserialize, Serialize};
    const PERSON: &str = "person";

    #[derive(SurrealValue)]
    struct Params {
        name: String,
        pass: String,
    }

    #[derive(SurrealValue, Serialize, Deserialize, Clone)]
    pub struct PersonData {
        name: String,
    }

    #[derive(SurrealValue, Serialize, Deserialize)]
    pub struct Person {
        name: String,
        id: RecordId,
    }

    #[get("/")]
    pub async fn paths() -> &'static str {
        r#"

-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John Doe"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane Doe"}' http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                          http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }

    #[get("/session")]
    pub async fn session() -> Result<Json<String>, Error> {
        let res: Option<String> = DB.query("<string>$session").await?.take(0)?;

        Ok(Json(res.unwrap_or("No session data found!".into())))
    }

    #[post("/person/<id>", data = "<person>")]
    pub async fn create_person(
        id: String,
        person: Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB
            .create((PERSON, &*id))
            .content(person.into_inner())
            .await?;
        Ok(Json(person))
    }

    #[get("/person/<id>")]
    pub async fn read_person(id: String) -> Result<Json<Option<Person>>, Error> {
        let person = DB.select((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    #[put("/person/<id>", data = "<person>")]
    pub async fn update_person(
        id: String,
        person: Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB
            .update((PERSON, &*id))
            .content(person.into_inner())
            .await?;
        Ok(Json(person))
    }

    #[delete("/person/<id>")]
    pub async fn delete_person(id: String) -> Result<Json<Option<Person>>, Error> {
        let person = DB.delete((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    #[get("/people")]
    pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
        let people = DB.select(PERSON).await?;
        Ok(Json(people))
    }

    #[get("/new_user")]
    pub async fn make_new_user() -> Result<String, Error> {
        let name: String = FirstName().fake();
        let pass: String = FirstName().fake();
        let jwt = DB
            .signup(Record {
                access: "account".to_string(),
                namespace: "main".to_string(),
                database: "main".to_string(),
                params: Params {
                    name: name.clone(),
                    pass: pass.clone(),
                },
            })
            .await?
            .access
            .into_insecure_token();
        Ok(format!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --pretty --token \"{jwt}\""))
    }

    #[get("/new_token")]
    pub async fn get_new_token() -> String {
        let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
        format!("Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --pretty --token YOUR_TOKEN_HERE")
    }
}

async fn init() -> Result<(), surrealdb::Error> {
    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?;

    DB.query(
        "    DEFINE TABLE person SCHEMALESS
        PERMISSIONS FOR 
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD name ON TABLE person TYPE string;
    DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;
    Ok(())
}

#[launch]
pub async fn rocket() -> _ {
    init().await.expect("Something went wrong, shutting down");
    rocket::build().mount(
        "/",
        routes![
            routes::create_person,
            routes::read_person,
            routes::update_person,
            routes::delete_person,
            routes::list_people,
            routes::paths,
            routes::make_new_user,
            routes::get_new_token,
            routes::session
        ],
    )
}
```

As the database client is logged in as a root user, the `/person/` routes can be used to perform any operation on the `person` records of the database.

You can also log in to the CLI or SurrealDB Studio as a root user and separately as a record user using the output of the `/new_user` endpoint to compare the experience between the two.

For example, the output when creating a `person` record as a root user will look like this:

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]
```

But as a record user, it will include a `created_by` field, set by the value found at the `$auth` paremeter.

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8syfiq2ovztn2tbr8mhb,
		name: 'Aeon'
	}
]
```

As a result, a `DELETE person RETURN BEFORE` statement (which deletes all `person` records and returns the records deleted) used by a record user will only delete the single record that it created earlier. The following `SELECT` statement shows that the `person` record created by the root user cannot be deleted or modified by the record user.

```bash
main/main> DELETE person RETURN BEFORE;
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8y06y06jmmb7e58trckz,
		name: 'Aeon'
	}
]

main/main> SELECT * FROM person;
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]

main/main> UPDATE person SET name = "Yogurt";
-- Query 1
[]
```

Also note that the root user is able to see the `user` tables and their information. A record user cannot, as a record user by default has no permissions except what it is given by the `PERMISSIONS` clause. If you create a record user using the `/new_user` endpoint, the root user will be able to view it. However, the password has been obscured by the `crypto::argon2::generate` function so that nobody else can use it.

```surql
[
	{
		id: user:qx2apv5oc8mh03wtah0q,
		name: 'Gerard',
		pass: '$argon2id$v=19$m=19456,t=2,p=1$j0ktTqUxRjOWYnwS5LoMFQ$2NcGkf5+IuLml6NorPy/Le6T8RppYXTXakwY5cDiZPY'
	}
]
```

## Further steps

Now that you have a running Rocket server with SurrealDB as the backend, here are some other ideas that you might want to explore.

* Using the [`AUTHENTICATE`](/docs/reference/query-language/statements/define/access/record.md#example-failing-authentication) clause inside the `DEFINE ACCESS` statement. This will result in increased performance thanks to only being executed once, compared to permissions checks which are executed for each query.
* Adding some interesting behaviour to the database such as [changefeeds](/docs/reference/query-language/statements/define/table.md#example-usage) or [events](/docs/reference/query-language/statements/define/event.md).

**2.x**

The following tutorial will set up a server with SurrealDB and [Rocket](https://rocket.rs/) that has a few endpoints:

* Some endpoints to demonstrate how the HTTP endpoints work to create, select, modify etc. a `person` table in a database,
* Other endpoints to display some helpful info for the user,
* Two endpoints to allow signing up and signing in as a [record user](/docs/reference/query-language/statements/define/access/record.md).

## Getting started

First, open up a terminal window and use the following command to start an empty database. This will also define a namespace and database by the name of "main" by default.

```bash
surreal start --user root --pass secret
```

You can also use the [Start serving](/docs/explore/studio.md) button on [SurrealDB Studio](/docs/explore/studio.md) to do the same if you have it installed locally.

The database initiated by the [surreal start](/docs/reference/cli/surrealdb-cli/commands/start.md) command stores data in memory by default, which then disappears every time the database is shut down. As such, you can simply use Ctrl+C every time you want to start the database anew with no existing definitions or data. To save data to disk which will persist after shutting down, add a [positional argument](/docs/reference/cli/surrealdb-cli/commands/start.md#positional-argument) for one of the storage backends such as `rocksdb://mydatabase` or `surrealkv://mydatabase`.

With the database running, we will now connect to the database "main" located in the namespace "main". You can connect to it by [creating a connection](/docs/explore/studio.md) inside SurrealDB Studio, or by using the following command to start an interactive shell.

```bash
surreal sql --user root --pass secret --pretty
```

Next, create a new Rust project with the command `cargo new your_project_name`, go into the newly created directory, and use `cargo add` to add each of the following dependencies:

* `surrealdb` (of course),
* `rocket`,
* `serde`, for serialising and deserialising Rust structs passed to and from the database and Rocket,
* `thiserror`, to make it easy to convert between SurrealDB's error type, other errors and Rocket's response types,
* `rand` and `faker_rand`, to create random user names that can be used to sign in to the database as a record user.

The `serde` crate will need the `derive` flag, and `rocket` will need the `json` flag enabled. Your `cargo.toml` dependencies should look like this:

```text
faker_rand = "0.1.1"
rand = "0.8.5"
rocket = { version = "0.5.1", features = ["json"] }
serde = { version = "1.0.228", features = ["derive"] }
surrealdb = "2.4.1"
thiserror = "2.0.18"
```

## Starting the Rust code

The first thing to do is a bit of groundwork to convert database errors into an error type of our own. Implementing `From<surrealdb::Error>` for this type will let it be used with the `?` operator when handling results. Finally, it will also need to implement Rocket's `Responder` trait so that it can be used as output for the server. All of this can be done manually if you prefer, but the `thiserror` crate saves a certain amount of typing.

```rust
mod error {
    use rocket::http::Status;
    use rocket::response::{self, Responder, Response};
    use rocket::Request;
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db,
    }

    impl<'r> Responder<'r, 'static> for Error {
        fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
            let error_message = format!(r#"{{ "error": "{self}" }}"#);
            Response::build()
                .status(Status::InternalServerError)
                .header(rocket::http::ContentType::JSON)
                .sized_body(error_message.len(), std::io::Cursor::new(error_message))
                .ok()
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db
        }
    }
}
```

Next, we will put the database client together. Rocket provides a [`.manage()`](https://rocket.rs/guide/v0.5/state/) method when starting a router that would give us access to the database inside its functions. However, for simplicity we can instead wrap the client inside a `LazyLock` to make it into a global static.

```rust
use std::sync::LazyLock;
static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);
```

Inside a method called `init()` to initiate the database, we will do the following:

* Connect to the database running at `localhost:8000`
* Sign in as the root user that was created through the `surreal start` command
* Use (move to) the namespace "main" and database "main"
* Use the `.query()` method to pass in a few definitions for the database.

```rust
async fn init() -> Result<(), surrealdb::Error> {
    DB.connect::<Ws>("localhost:8000").await?;

    DB.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    DB.use_ns("namespace").use_db("database").await?;

    DB.query(
        "    DEFINE TABLE person SCHEMALESS
        PERMISSIONS FOR 
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD name ON TABLE person TYPE string;
    DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;
    Ok(())
}
```

## What the database definitions do

The first item that stands out with the definitions above is that they all contain a `IF NOT EXISTS` clause. As the `DEFINE` statements will be executed every time the app starts, it is possible that they might be executed on a database that already has the definitions in place. SurrealDB returns an error if a definition already exists, requiring the `OVERWRITE` clause if a definition needs to be redone.

Note that this would not affect our app, as this would still be a successful usage of the `.query()` method. Instead, its output would contain a number of error results that could be handled individually:

```text
Response { client: Surreal { router: OnceLock(Router { sender: Sender { .. }, last_id: 4, features: {LiveQueries} }), engine: PhantomData<surrealdb::api::engine::any::Any> }, results: {0: (Stats { execution_time: Some(252.625µs) }, Err(Api(Query("The table 'person' already exists")))), 1: (Stats { execution_time: Some(79.167µs) }, Err(Api(Query("The field 'name' already exists")))), 2: (Stats { execution_time: Some(69.5µs) }, Err(Api(Query("The field 'created_by' already exists")))), 3: (Stats { execution_time: Some(73.625µs) }, Err(Api(Query("The index 'unique_name' already exists")))), 4: (Stats { execution_time: Some(73.583µs) }, Err(Api(Query("The access method 'account' already exists in the database 'main'"))))}, live_queries: {} }
```

However, adding `IF NOT EXISTS` is a nice way to change the results from errors into successful results, and to avoid the rare case in which they end up applied to some other version 1.x database that would rewrite its definitions if `IF NOT EXISTS` is present. So while not necessary in our case, it is a good practice to follow and makes for cleaner output.

Now let's go over each of the definitions to see what they do.

The first three statements define a `person` table. This table is schemaless, but has one required field `name`, which must be present and must be a string. This table has defined permissions by which a record user is able to use `CREATE` and `SELECT` on the `person` table, but can only `UPDATE` and `DELETE` records that it has created. The root user, however, is not subject to permissions rules.

The way these permissions are set is by using the `$auth` parameter. This parameter has a value whenever a record user is set as the authorised used for the database. The `WHERE $auth` clause simply means "where a value exists for the parameter `$auth`" (`WHERE $auth IS NOT NONE` would also work in this case). But for `UPDATE` and `DELETE` queries, it is not enough for `$auth` to just be present, the `created_by` field of a `person` record must also match the ID of the currently authenticated user.

This `created_by` field is automatically generated from its definition in the `DEFINE FIELD` statement. It is given the value of `$auth`, and is `READONLY` and thus cannot be changed. When logged in as a system user (like a root user), its value will be `NONE`. But when logged in as a record user, its value will be something like `user:qx2apv5oc8mh03wtah0q`.

```surql
DEFINE TABLE IF NOT EXISTS person SCHEMALESS
    PERMISSIONS FOR 
        CREATE, SELECT WHERE $auth,
        FOR UPDATE, DELETE WHERE created_by = $auth;
DEFINE FIELD IF NOT EXISTS name ON TABLE person TYPE string;
DEFINE FIELD IF NOT EXISTS created_by ON TABLE person VALUE $auth READONLY;
```

So where does an ID like `user:qx2apv5oc8mh03wtah0q` come from? This is thanks to the following definitions that set the signup and signin behaviour of the record users. A typical [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) statement will create some sort of record on signup (in this case, a `user`) record, and will compare it against a password during signin. Note that the access has a name that we gave it (`account`), so that it can be referenced elsewhere.

In addition, a `DEFINE INDEX` statement with a `UNIQUE` clause is used to ensure that no two users can have the same name.

```surql
    DEFINE INDEX IF NOT EXISTS unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
```

For an actual user in production, you would probably want to require an email and some other fields. Functions like [`string::is_email`](/docs/reference/query-language/functions/database-functions/string.md#stringis_email) can be used to ensure that the value passed in is valid.

```surql
DEFINE FIELD email ON TABLE user TYPE string ASSERT $value.is_email();
```

However, for this simple example, each user will simply have a unique name and a password. The password will be stored in hashed and salted form on the database, making it unique and unreadable every time it is generated. The only way to check if it is correct is by using a [compare function](/docs/reference/query-language/functions/database-functions/crypto.md#cryptoargon2compare) of the output with an attempted password. Here is a short SurrealQL sample to show how the process works.

```surql
LET $hash1 = crypto::argon2::generate("myPaSSWord");
LET $hash2 = crypto::argon2::generate("myPaSSWord");

RETURN [$hash1, $hash2];
-- First returns true, second returns false
RETURN [
    crypto::argon2::compare($hash1, "myPaSSWord")
    crypto::argon2::compare($hash1, "Wrongpassword")
];
```

## The rest of the code

The last step is where the majority of the work takes place: setting up the paths for Rocket to handle, and writing the functions that handle the endpoints and (usually) access the database to handle the request. To start, we'll create a function for the `"/"` root path to display a helpful message to anybody giving the server a try via the browser or an app like curl or Postman. These paths and curl examples can all be seen on [the page for SurrealDB's HTTP endpoints](/docs/reference/rest-api/http-protocol.md).

```rust
    #[get("/")]
    pub async fn paths() -> &'static str {
        r#"

-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John Doe"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane Doe"}' http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                          http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }
```

Each of these functions will be put into a mod called `routes`, leading to the following code inside Rocket's `rocket()` function (its equivalent of `main()`).

```rust
#[launch]
pub async fn rocket() -> _ {
    std::env::set_var("ROCKET_PORT", "8080");
    init().await.expect("Something went wrong, shutting down");
    rocket::build().mount(
        "/",
        routes![
            routes::create_person,
            routes::read_person,
            routes::update_person,
            routes::delete_person,
            routes::list_people,
            routes::paths,
            routes::make_new_user,
            routes::get_new_token,
            routes::session
        ],
    )
}
```

Many functions require some JSON data from the user, which will be deserialised into a `PersonData` struct. The database can then use it in methods like `.create().content()`. The output returned will now have a `name` and an `id`, which the `Person` struct holds.

```rust
#[derive(Serialize, Deserialize, Clone)]
pub struct PersonData {
    name: String,
}

#[derive(Serialize, Deserialize)]
pub struct Person {
    name: String,
    id: RecordId,
}
```

Each of these functions are pretty straightforward: obtain some user input, initiate a query, feed the user input into it, and return it as JSON.

```rust
const PERSON: &str = "person";

#[post("/person/<id>", data = "<person>")]
pub async fn create_person(
    id: String,
    person: Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB
        .create((PERSON, &*id))
        .content(person.into_inner())
        .await?;
    Ok(Json(person))
}

#[get("/person/<id>")]
pub async fn read_person(id: String) -> Result<Json<Option<Person>>, Error> {
    let person = DB.select((PERSON, &*id)).await?;
    Ok(Json(person))
}

#[put("/person/<id>", data = "<person>")]
pub async fn update_person(
    id: String,
    person: Json<PersonData>,
) -> Result<Json<Option<Person>>, Error> {
    let person = DB
        .update((PERSON, &*id))
        .content(person.into_inner())
        .await?;
    Ok(Json(person))
}

#[delete("/person/<id>")]
pub async fn delete_person(id: String) -> Result<Json<Option<Person>>, Error> {
    let person = DB.delete((PERSON, &*id)).await?;
    Ok(Json(person))
}

#[get("/people")]
pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
    let people = DB.select(PERSON).await?;
    Ok(Json(people))
}
```

The `session()` function is also quite small, and is just a convenience for a user curious about the current session data. As the `.query()` method can take more than one statement, it returns each of these responses in order with an index for each (starting at 0). The `.take()` method can then be used to access the response at that index, and turn it into anything that can be deserialised back into a Rust type. In our case, a `String` is all we need here as the output will only be used to show the user the current session info.

```rust
#[get("/session")]
pub async fn session() -> Result<Json<String>, Error> {
    let res: Option<String> = DB.query("RETURN <string>$session").await?.take(0)?;

    Ok(Json(res.unwrap_or("No session data found!".into())))
}
```

The most interesting function is the one used to create a new record user. To make it really easy to try out the experience of logging in as a record user, this function will use create a random name and password each time it is accessed. It will then pass in a [`Record`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Record.html) struct which is used to sign up a new record user. Note the following:

* The access name is `"account"`, which is the name we chose in the `DEFINE ACCESS` statement above.
* The `params` field takes anything that implements `Serialize`, in this case a struct we put together called `Params`.
* The `.signup()` method returns a redacted `Jwt` by default. To make the token visible, you can use the `.into_insecure_token()` method as we have done here. As a small guide to getting started, this example is not concerned about security. However, if you are looking to create something more production-worthy, do take a look at the [security](/docs/learn/security.md) section of the documentation and the [security best practices](/docs/learn/security/best-practices/security-best-practices.md) page.

The function will then end with an output showing the username, password, token, and instructions for how to log in using the CLI. This can be copied and pasted to begin making queries immediately.

```rust
#[derive(Serialize, Deserialize)]
struct Params<'a> {
    name: &'a str,
    pass: &'a str,
}

#[get("/new_user")]
pub async fn make_new_user() -> Result<String, Error> {
    let name = rand::random::<FirstName>().to_string();
    let pass = rand::random::<FirstName>().to_string();
    let jwt = DB
        .signup(Record {
            access: "account",
            namespace: "namespace",
            database: "database",
            params: Params {
                name: &name,
                pass: &pass,
            },
        })
        .await?
        .into_insecure_token();
    Ok(format!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --namespace namespace --database database --pretty --token \"{jwt}\""))
}
```

A record user with an expired token can use the `/signin` endpoint to get a new token. Since this requires passing in a username and password, we'll just have this function return a `String` that contains a curl example to get a new token.

```rust
#[get("/new_token")]
pub async fn get_new_token() -> String {
    let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"namespace","db":"database","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
    format!("Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --namespace namespace --database database --pretty --token YOUR_TOKEN_HERE")
}
```

## Experimenting with the app

The final code looks like this:

```rust
#[macro_use]
extern crate rocket;

use std::sync::LazyLock;
use surrealdb::engine::remote::ws::Client;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);

mod error {
    use rocket::http::Status;
    use rocket::response::{self, Responder, Response};
    use rocket::Request;
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum Error {
        #[error("database error")]
        Db,
    }

    impl<'r> Responder<'r, 'static> for Error {
        fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
            let error_message = format!(r#"{{ "error": "{self}" }}"#);
            Response::build()
                .status(Status::InternalServerError)
                .header(rocket::http::ContentType::JSON)
                .sized_body(error_message.len(), std::io::Cursor::new(error_message))
                .ok()
        }
    }

    impl From<surrealdb::Error> for Error {
        fn from(error: surrealdb::Error) -> Self {
            eprintln!("{error}");
            Self::Db
        }
    }
}

mod routes {

    use faker_rand::en_us::names::FirstName;
    use surrealdb::opt::auth::Record;

    use crate::error::Error;
    use crate::DB;
    use rocket::serde::json::Json;
    use rocket::{delete, get, post, put};
    use serde::{Deserialize, Serialize};
    use surrealdb::RecordId;
    const PERSON: &str = "person";

    #[derive(Serialize, Deserialize)]
    struct Params<'a> {
        name: &'a str,
        pass: &'a str,
    }

    #[derive(Serialize, Deserialize, Clone)]
    pub struct PersonData {
        name: String,
    }

    #[derive(Serialize, Deserialize)]
    pub struct Person {
        name: String,
        id: RecordId,
    }

    #[get("/")]
    pub async fn paths() -> &'static str {
        r#"

-----------------------------------------------------------------------------------------------------------------------------------------
        PATH                |           SAMPLE COMMAND                                                                                  
-----------------------------------------------------------------------------------------------------------------------------------------
/session: See session data  |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/session
                            |
/person/{id}:               |
  Create a person           |  curl -X POST   -H "Content-Type: application/json" -d '{"name":"John Doe"}' http://localhost:8080/person/one
  Get a person              |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/person/one
  Update a person           |  curl -X PUT    -H "Content-Type: application/json" -d '{"name":"Jane Doe"}' http://localhost:8080/person/one
  Delete a person           |  curl -X DELETE -H "Content-Type: application/json"                          http://localhost:8080/person/one
                            |
/people: List all people    |  curl -X GET    -H "Content-Type: application/json"                          http://localhost:8080/people

/new_user:  Create a new record user
/new_token: Get instructions for a new token if yours has expired"#
    }

    #[get("/session")]
    pub async fn session() -> Result<Json<String>, Error> {
        let res: Option<String> = DB.query("RETURN <string>$session").await?.take(0)?;

        Ok(Json(res.unwrap_or("No session data found!".into())))
    }

    #[post("/person/<id>", data = "<person>")]
    pub async fn create_person(
        id: String,
        person: Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB
            .create((PERSON, &*id))
            .content(person.into_inner())
            .await?;
        Ok(Json(person))
    }

    #[get("/person/<id>")]
    pub async fn read_person(id: String) -> Result<Json<Option<Person>>, Error> {
        let person = DB.select((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    #[put("/person/<id>", data = "<person>")]
    pub async fn update_person(
        id: String,
        person: Json<PersonData>,
    ) -> Result<Json<Option<Person>>, Error> {
        let person = DB
            .update((PERSON, &*id))
            .content(person.into_inner())
            .await?;
        Ok(Json(person))
    }

    #[delete("/person/<id>")]
    pub async fn delete_person(id: String) -> Result<Json<Option<Person>>, Error> {
        let person = DB.delete((PERSON, &*id)).await?;
        Ok(Json(person))
    }

    #[get("/people")]
    pub async fn list_people() -> Result<Json<Vec<Person>>, Error> {
        let people = DB.select(PERSON).await?;
        Ok(Json(people))
    }

    #[get("/new_user")]
    pub async fn make_new_user() -> Result<String, Error> {
        let name = rand::random::<FirstName>().to_string();
        let pass = rand::random::<FirstName>().to_string();
        let jwt = DB
            .signup(Record {
                access: "account",
                namespace: "namespace",
                database: "database",
                params: Params {
                    name: &name,
                    pass: &pass,
                },
            })
            .await?
            .into_insecure_token();
        Ok(format!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --namespace namespace --database database --pretty --token \"{jwt}\""))
    }

    #[get("/new_token")]
    pub async fn get_new_token() -> String {
        let command = r#"curl -X POST -H "Accept: application/json" -d '{"ns":"namespace","db":"database","ac":"account","user":"your_username","pass":"your_password"}' http://localhost:8000/signin"#;
        format!("Need a new token? Use this command:\n\n{command}\n\nThen log in with surreal sql --pretty --token YOUR_TOKEN_HERE")
    }
}

async fn init() -> Result<(), surrealdb::Error> {
    DB.connect::<Ws>("localhost:8000").await?;

    DB.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    DB.use_ns("namespace").use_db("database").await?;

    DB.query(
        "    DEFINE TABLE person SCHEMALESS
        PERMISSIONS FOR 
            CREATE, SELECT WHERE $auth,
            FOR UPDATE, DELETE WHERE created_by = $auth;
    DEFINE FIELD name ON TABLE person TYPE string;
    DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;

    DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE;
    DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) )
	SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
	DURATION FOR TOKEN 15m, FOR SESSION 12h
;",
    )
    .await?;
    Ok(())
}

#[launch]
pub async fn rocket() -> _ {
    std::env::set_var("ROCKET_PORT", "8080");
    init().await.expect("Something went wrong, shutting down");
    rocket::build().mount(
        "/",
        routes![
            routes::create_person,
            routes::read_person,
            routes::update_person,
            routes::delete_person,
            routes::list_people,
            routes::paths,
            routes::make_new_user,
            routes::get_new_token,
            routes::session
        ],
    )
}
```

As the database client is logged in as a root user, the `/person/` routes can be used to perform any operation on the `person` records of the database.

You can also log in to the CLI or SurrealDB Studio as a root user and separately as a record user using the output of the `/new_user` endpoint to compare the experience between the two.

For example, the output when creating a `person` record as a root user will look like this:

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]
```

But as a record user, it will include a `created_by` field, set by the value found at the `$auth` paremeter.

```bash
main/main> CREATE person SET name = 'Aeon';
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8syfiq2ovztn2tbr8mhb,
		name: 'Aeon'
	}
]
```

As a result, a `DELETE person RETURN BEFORE` statement (which deletes all `person` records and returns the records deleted) used by a record user will only delete the single record that it created earlier. The following `SELECT` statement shows that the `person` record created by the root user cannot be deleted or modified by the record user.

```bash
main/main> DELETE person RETURN BEFORE;
-- Query 1
[
	{
		created_by: user:qx2apv5oc8mh03wtah0q,
		id: person:8y06y06jmmb7e58trckz,
		name: 'Aeon'
	}
]

main/main> SELECT * FROM person;
-- Query 1
[
	{
		id: person:hdl0unwts4atic65nh7l,
		name: 'Aeon'
	}
]

main/main> UPDATE person SET name = "Yogurt";
-- Query 1
[]
```

Also note that the root user is able to see the `user` tables and their information. A record user cannot, as a record user by default has no permissions except what it is given by the `PERMISSIONS` clause. If you create a record user using the `/new_user` endpoint, the root user will be able to view it. However, the password has been obscured by the `crypto::argon2::generate` function so that nobody else can use it.

```surql
[
	{
		id: user:qx2apv5oc8mh03wtah0q,
		name: 'Gerard',
		pass: '$argon2id$v=19$m=19456,t=2,p=1$j0ktTqUxRjOWYnwS5LoMFQ$2NcGkf5+IuLml6NorPy/Le6T8RppYXTXakwY5cDiZPY'
	}
]
```

## Further steps

Now that you have a running Rocket server with SurrealDB as the backend, here are some other ideas that you might want to explore.

* Using the [`AUTHENTICATE`](/docs/reference/query-language/statements/define/access/record.md#example-failing-authentication) clause inside the `DEFINE ACCESS` statement. This will result in increased performance thanks to only being executed once, compared to permissions checks which are executed for each query.
* Adding some interesting behaviour to the database such as [changefeeds](/docs/reference/query-language/statements/define/table.md#example-usage) or [events](/docs/reference/query-language/statements/define/event.md).

---

Source: https://surrealdb.com/docs/reference/rust/methods

# SDK methods

Most methods in the SurrealDB SDK involve either working with or creating an instance of the Surreal struct, which serves as the database client instance for embedded or remote databases.

Most methods in the SurrealDB SDK involve either working with or creating an instance of the [`Surreal`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html) struct, which serves as the database client instance for embedded or remote databases.

The table below lists documented methods **in alphabetical order** (by page name).

## All 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/rust/methods/authenticate.md"> <code>db.authenticate()</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/rust/methods/begin.md"> <code>db.begin()</code></a></td>
			<td scope="row" data-label="Description">Start a session-scoped multi-statement transaction, returning a <code>Transaction</code> handle (commit, cancel, query, and CRUD)</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/connect.md"> <code>db.connect()</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/rust/methods/create.md"> <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="Function"><a href="/docs/reference/rust/methods/delete.md"> <code>db.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="Function"><a href="/docs/reference/rust/methods/export.md"> <code>db.export()</code></a></td>
			<td scope="row" data-label="Description">Exports the database to a file or a live stream of bytes</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/get.md"> <code>value.get()</code></a></td>
			<td scope="row" data-label="Description">On <code>Value</code> (and query results), reads a field on an object or an index in an array</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/health.md"> <code>db.health()</code></a></td>
			<td scope="row" data-label="Description">Runs a health check to verify the server accepts commands</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/import.md"> <code>db.import()</code></a></td>
			<td scope="row" data-label="Description">Imports the contents of another database from a file</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/init.md"> <code>Surreal::init()</code></a></td>
			<td scope="row" data-label="Description">Initializes a non-connected instance of the database client</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/insert.md"> <code>db.insert()</code></a></td>
			<td scope="row" data-label="Description">Inserts one or multiple records or relations in the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/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/rust/methods/new.md"> <code>Surreal::new()</code></a></td>
			<td scope="row" data-label="Description">Initializes a connected instance of the database client</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/query.md"> <code>db.query()</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/rust/methods/run.md"> <code>db.run()</code></a></td>
			<td scope="row" data-label="Description">Runs a SurrealQL function</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/select.md"> <code>db.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="Function"><a href="/docs/reference/rust/methods/select-live.md"> <code>db.select().live()</code></a></td>
			<td scope="row" data-label="Description">Performs a LIVE SELECT query on the database</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/set.md"> <code>db.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="Function"><a href="/docs/reference/rust/methods/signin.md"> <code>db.signin()</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/rust/methods/signup.md"> <code>db.signup()</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/rust/methods/unset.md"> <code>db.unset()</code></a></td>
			<td scope="row" data-label="Description">Removes a parameter for this connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/update.md"> <code>db.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="Function"><a href="/docs/reference/rust/methods/upsert.md"> <code>db.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="Function"><a href="/docs/reference/rust/methods/use.md"> <code>db.use_ns().use_db()</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/rust/methods/use-defaults.md"> <code>db.use_defaults()</code></a></td>
			<td scope="row" data-label="Description">Select the default namespace and database for this connection</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/version.md"> <code>db.version()</code></a></td>
			<td scope="row" data-label="Description">Returns the current database version</td>
		</tr>
		<tr>
			<td scope="row" data-label="Function"><a href="/docs/reference/rust/methods/wait-for.md"> <code>db.wait_for()</code></a></td>
			<td scope="row" data-label="Description">Blocks until a connection or database session event (such as <code>WaitFor::Connection</code>) is ready</td>
		</tr>
	</tbody>
</table>

---

Source: https://surrealdb.com/docs/reference/rust/methods/authenticate

# authenticate

The .authenticate() method for the SurrealDB Rust SDK authenticates the current connection with a JWT token.

**3.x**

Authenticates the current connection with a JWT token.

```rust title="Method Syntax"
db.authenticate(token)
```

## 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 authentication token.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

Note: the following example uses the `ureq` crate with the `json` feature to first send a request to the database's [`/signup`](/docs/reference/rest-api/http-protocol.md#signup) endpoint which returns a token. The `reqwest` crate and others can be used here instead.

Alternatively, you could use a command like the following, copy the returned token, and paste it into the `.authenticate()` method.

```bash
curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"info@surrealdb.com","pass":"123456"}' http://localhost:8000/signup
```

As the `DEFINE ACCESS` statement below shows, a token will remain valid by default for 15 minutes.

```rust
// Use the following statement to set up the 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
// ;

// DEFINE TABLE cat SCHEMALESS
//     PERMISSIONS for select, update, delete, create
//     WHERE $auth.id;

use serde::{Deserialize, Serialize};
use std::fmt::Display;
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::types::SurrealValue;

#[derive(Deserialize, SurrealValue)]
struct Response {
    token: String,
}

impl Display for Response {
        fn fmt(&self,
        f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.token)
    }
}

#[derive(Serialize)]
struct Signup {
    ns: String,
    db: String,
    ac: String,
    email: String,
    pass: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    let response_string = ureq::post("http://127.0.0.1:8000/signup")
        .header("Accept", "application/json")
        .send_json(Signup {
            ns: "main".to_string(),
            db: "main".to_string(),
            ac: "account".to_string(),
            email: "info@surrealdb.com".to_string(),
            pass: "123456".to_string(),
        })
        .unwrap()
        .into_body()
        .read_to_string()
        .unwrap();

    let response = serde_json::from_str::<Response>(&response_string).unwrap();

    // Not signed in, doesn't work
    println!("{:?}", db.query("CREATE cat;").await);
    db.authenticate(response.token).await?;
    // Now it works
    println!("{:?}", db.query("CREATE cat;").await?);

    Ok(())
}
```

## Refreshing a session (`.refresh()`)

When the server issues a token that includes a **refresh** component, you can obtain a new access token without signing in again. Build the usual [`db.authenticate(token)`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.authenticate) future, then call [`.refresh()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Authenticate.html#method.refresh). The inner future runs the refresh command and returns a new [`Token`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Token.html). If the token has no refresh material, the SDK returns an error (`Missing refresh token`).

```rust
// Get a token from signin
let token = db.signin(credentials).await?;

// Later, refresh the token
let new_token = db.authenticate(token).refresh().await?;
```

This pairs with the access and refresh model configured via [`DEFINE ACCESS`](/docs/reference/query-language/statements/define/access/record.md) (token duration, refresh behaviour, and scope depend on your statement).

The following example demonstrates how to use a refresh token for a user and how the same user is authenticated throughout:

```rust
// Two dependencies:
// cargo add surrealdb --features kv-mem tokio

use surrealdb::{
    Error, Surreal,
    engine::local::Mem,
    opt::{Config, auth::Record},
    types::{SurrealValue, ToSql, Value},
};

const NAMESPACE: &str = "ns";
const DATABASE: &str = "db";
const ACCESS: &str = "account";
const EMAIL: &str = "jane@example.com";
const PASSWORD: &str = "password123";

// What you persist across app restarts
// (access JWT stays in RAM only)
#[derive(Debug, SurrealValue)]
struct PersistedSession {
    namespace: String,
    database: String,
    access_method: String,
    refresh: String,
}

// Signin / signup credentials
// flattened into the RPC payload by `Record`
#[derive(Debug, SurrealValue)]
struct EmailPassword {
    email: String,
    pass: String,
}

#[derive(Debug, SurrealValue)]
struct RefreshOnly {
    refresh: String,
}

fn truncate(token: &str) -> &str {
    &token[..token.len().min(24)]
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let db = Surreal::new::<Mem>(Config::default()).await?;
    db.use_ns("ns").use_db("db").await?;

    // Schema setup
    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)
            )
            WITH REFRESH
            DURATION FOR SESSION 1d FOR TOKEN 1h;
        ",
    )
    .await?
    .check()?;

    // First sign up a user
    let _ = db
        .signup(Record {
            namespace: NAMESPACE.into(),
            database: DATABASE.into(),
            access: ACCESS.into(),
            params: EmailPassword {
                email: EMAIL.into(),
                pass: PASSWORD.into(),
            },
        })
        .await;

    // Sign in with same email + password used to sign in
    let token = db
        .signin(Record {
            namespace: NAMESPACE.into(),
            database: DATABASE.into(),
            access: ACCESS.into(),
            params: EmailPassword {
                email: EMAIL.into(),
                pass: PASSWORD.into(),
            },
        })
        .await?;

    println!(
        "Signed in.\n   access JWT:  {}...\n   refresh JWT: {}...",
        truncate(token.access.as_insecure_token()),
        truncate(token.refresh.as_ref().unwrap().as_insecure_token())
    );

    let old_access = token.access.as_insecure_token().to_string();

    // Use the access token on this connection
    // (refresh is ignored here)
    db.authenticate(token.access.clone()).await?;
    let me = db
        .query("RETURN $auth")
        .await?
        .take::<Option<Value>>(0)?
        .into_value()
        .to_sql_pretty();
    println!("Authenticated as: {me:?}");

    //  Refresh - pass the full Token (access + refresh)
    //
    //  Server decodes the access JWT without checking expiry
    //  to recover ns/db/ac/id, then validates the refresh grant.
    //  Old refresh is single-use so is revoked,
    //  and a new pair is returned.
    let refreshed = db.authenticate(token).refresh().await?;

    assert_ne!(old_access, refreshed.access.as_insecure_token());
    println!(
        "Refreshed.   \n   new access:  {}...\n   new refresh: {}...",
        truncate(refreshed.access.as_insecure_token()),
        truncate(refreshed.refresh.as_ref().unwrap().as_insecure_token())
    );

    // Persist refresh only (simulate writing to disk)
    let persisted = PersistedSession {
        namespace: NAMESPACE.into(),
        database: DATABASE.into(),
        access_method: ACCESS.into(),
        refresh: refreshed
            .refresh
            .as_ref()
            .unwrap()
            .as_insecure_token()
            .to_string(),
    };
    let json = persisted.into_value();
    println!("\nPersisted session:\n{}", json.clone().to_sql_pretty());

    // Simulate app restart: no access JWT in memory,
    // only persisted refresh
    db.invalidate().await?;

    let loaded = PersistedSession::from_value(json).unwrap();

    let cold_token = db
        .signin(Record {
            namespace: loaded.namespace,
            database: loaded.database,
            access: loaded.access_method,
            params: RefreshOnly {
                refresh: loaded.refresh,
            },
        })
        .await?;

    db.authenticate(cold_token.access.clone()).await?;
    let me_again = db
        .query("RETURN $auth")
        .await?
        .take::<Option<Value>>(0)?
        .unwrap()
        .to_sql_pretty();
    println!("After cold-start refresh signin: {me_again:?}");

    // Refresh again on the warm path
    let _ = db.authenticate(cold_token).refresh().await?;
    println!("Second refresh OK.");

    Ok(())
}
```

## See also

* [`Authenticate::refresh` on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Authenticate.html#method.refresh)

**2.x**

Authenticates the current connection with a JWT token.

```rust title="Method Syntax"
db.authenticate(token)
```

## 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 authentication token.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

Note: the following example uses the `ureq` crate with the `json` feature to first send a request to the database's [`/signup`](/docs/reference/rest-api/http-protocol.md#signup) endpoint which returns a token. The `reqwest` crate and others can be used here instead.

Alternatively, you could use a command like the following, copy the returned token, and paste it into the `.authenticate()` method.

```bash
curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"info@surrealdb.com","pass":"123456"}' http://localhost:8000/signup`
```

As the `DEFINE ACCESS` statement below shows, a token will remain valid by default for 15 minutes.

```rust
// Use the following statements to set up the 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
// ;

// DEFINE TABLE cat SCHEMALESS
//     PERMISSIONS for select, update, delete, create
//     WHERE $auth.id;

use serde::Deserialize;
use std::fmt::Display;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::Surreal;

#[derive(Deserialize)]
struct Response {
    token: String,
}

impl Display for Response {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.token)
    }
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    let token = ureq::post("http://127.0.0.1:8000/signup")
        .set("Accept", "application/json")
        .send_json(ureq::json!({
            "ns": "main",
            "db": "main",
            "ac": "account",
            "email": "info@surrealdb.com",
            "pass": "123456"
        }))
        .unwrap()
        .into_json::<Response>()
        .unwrap()
        .to_string();

    // Not signed in, doesn't work
    dbg!(db.query("CREATE cat;").await?);
    db.authenticate(token).await?;
    // Now it works
    dbg!(db.query("CREATE cat;").await?);

    Ok(())
}
```

## See also

* [.authenticate() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.authenticate)

---

Source: https://surrealdb.com/docs/reference/rust/methods/begin

# begin

The .begin() method on the SurrealDB Rust SDK client starts a multi-statement transaction and returns a handle for running queries, commits, and rollbacks.

Starts a transaction. The connection is taken into a [`Transaction`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html) that exposes the same query and CRUD entry points as `Surreal` (scoped to the transaction) plus [`commit()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html#method.commit) and [`cancel()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html#method.cancel).

Note that this method takes by value (taking a `self`), which is then passed on to the `Transaction`. The `.commit()` and `.cancel()` methods are used to finalise the transaction and return the `Surreal` client for reuse.

On a remote WebSocket server, each open client-managed transaction counts toward [`SURREAL_MAX_TRANSACTIONS_PER_CONNECTION`](/docs/reference/cli/surrealdb-cli/environment-variables.md#websocket-config) (connection default session) or [`SURREAL_MAX_TRANSACTIONS_PER_SESSION`](/docs/reference/cli/surrealdb-cli/environment-variables.md#websocket-config) (attached session). Exceeding the limit returns `Too many open transactions`. Detach/`reset` cancel that session's open transactions.

```rust title="Method Syntax"
let tx = db.begin().await?;
// tx.query(...), .select(), .create(), .insert(), .upsert(), .update(), .delete()
```

## `.commit()`

[`tx.commit().await?`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html#method.commit) applies every statement run on the handle and returns ownership of the underlying `Surreal` client so the connection can run further work outside the transaction.

```rust
// let db = tx.commit().await?;
```

## `.cancel()`

[`tx.cancel().await?`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html#method.cancel) rolls back the transaction and **also** returns the `Surreal` client for reuse.

```rust
// let db = tx.cancel().await?;
```

For a broader discussion and more detailed examples, see the concept page on [Manual transactions](/docs/reference/rust/concepts/transaction.md).

### See also

* [`.begin()` on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.begin)
* [`Transaction` in the Rust API](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Transaction.html)

---

Source: https://surrealdb.com/docs/reference/rust/methods/connect

# connect

The .connect() method for the SurrealDB Rust SDK connects to a local or remote database endpoint.

Connects to a local or remote database endpoint.

```rust title="Method Syntax"
db.connect(address)
```

## 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.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

The `.connect()` method will usually take a `String` or a type that implements `Into<String>`. Note that the final `.connect()` with a `Config` is possible because of the implementation `impl<T> IntoEndpoint for (T, Config)
where T: Into<String>`.

```rust
use std::sync::LazyLock;
use std::time::Duration;
use surrealdb::engine::remote::ws::{Client, Ws, Wss};
use surrealdb::opt::Config;
use surrealdb::Surreal;

static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Connect to a local endpoint
    DB.connect::<Ws>("127.0.0.1:8000").await?;
    // Connect to a remote endpoint
    DB.connect::<Wss>("cloud.surrealdb.com").await?;
    // A tuple with a Config struct can also be passed in for fine tuning of the connection
    let config = Config::default().query_timeout(Duration::from_millis(1500));
    DB.connect::<Ws>(("127.0.0.1:8000", config)).await?;
    Ok(())
}
```

## Connecting over gRPC

_(since v3.3.0)_

Alongside WebSocket and HTTP, the SDK can talk to a server over gRPC. The server exposes it on the same address and port as the other two, so no extra server configuration is needed.

gRPC is the only remote protocol that delivers query results incrementally, which is what makes [`.stream_items()`](/docs/reference/rust/methods/query.md#stream-items) worthwhile on a remote connection. On WebSocket and HTTP the results are buffered and replayed instead.

The engine is behind the `protocol-grpc` feature, which is not enabled by default:

```toml title="Cargo.toml"
surrealdb = { version = "3", features = ["protocol-grpc"] }
```

Use `Grpc` for a plain connection and `Grpcs` for a TLS one, in the same way as `Ws` and `Wss`.

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::grpc::Grpc;
use surrealdb::opt::auth::Root;
use surrealdb::types::Value;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Grpc>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    let mut res = db.query("RETURN 1 + 1").await?;
    println!("{:?}", res.take::<Value>(0)?);
    Ok(())
}
```

The `any` engine accepts `grpc://` and `grpcs://` URLs, so a connection can be chosen at runtime in the same way as `ws://` or `http://`.

```rust
let db = surrealdb::engine::any::connect("grpc://127.0.0.1:8000").await?;
```

> [!NOTE]
> A `grpc://` URL passed to `any::connect()` in a build without the `protocol-grpc` feature fails at connection time rather than at compile time, with `Cannot connect to the gRPC remote engine as it is not enabled in this build of SurrealDB`. The engine is also unavailable on `wasm32` targets, as its underlying transport does not build for them.

## See also

* [.connect() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/engine/any/fn.connect.html)
* [Streaming query results with `.stream_items()`](/docs/reference/rust/methods/query.md#stream-items)

---

Source: https://surrealdb.com/docs/reference/rust/methods/create

# create

The .create() method for the SurrealDB Rust SDK creates one or more records in the database.

**3.x**

Creates one or more records in the database.

```rust title="Method Syntax"
db.create(resource).content(data)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::{RecordId, SurrealValue};

#[derive(Debug, SurrealValue)]
struct Person {
    name: Option<String>,
    marketing: Option<bool>,
}

#[derive(Debug, SurrealValue)]
struct Record {
    id: RecordId,
}

#[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?;

    // Create a record with a random ID
    let person: Option<Person> = db.create("person").await?;
    dbg!(person);
    // Create a record with a specific ID
    let record: Option<Record> = db
        .create(("person", "tobie"))
        .content(Person {
            name: Some("Tobie".into()),
            marketing: Some(true),
        })
        .await?;
    dbg!(record);
    Ok(())
}
```

**2.x**

Creates one or more records in the database.

```rust title="Method Syntax"
db.create(resource).content(data)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::RecordId;

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: Option<String>,
    marketing: Option<bool>
}

#[derive(Debug, Deserialize)]
struct Record {
    id: RecordId,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    db.use_ns("ns").use_db("db").await?;

    // Create a record with a random ID
    let person: Option<Person> = db.create("person").await?;
    dbg!(person);
    // Create a record with a specific ID
    let record: Option<Record> = db
        .create(("person", "tobie"))
        .content(Person {
            name: Some("Tobie".into()),
            marketing: Some(true),
        })
        .await?;
    dbg!(record);
    Ok(())
}
```

## Translated query
This function will run the following query in the database:

```surql
CREATE $resource CONTENT $data;
```

## See also

* [.create() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.create)

---

Source: https://surrealdb.com/docs/reference/rust/methods/delete

# delete

The .delete() method for the SurrealDB Rust SDK deletes all or specific records from the database.

**3.x**

Deletes all records in a table, or a specific record, from the database.

```rust title="Method Syntax"
db.delete(resource)
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select. Will also accept a tuple of record name and ID.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;
use surrealdb::types::{RecordId, SurrealValue};

#[derive(Debug, SurrealValue)]
struct Person {
    id: RecordId,
}

#[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("ns").use_db("db").await?;

    // Create three `person` records
    db.create(Resource::from(("person", "one"))).await?;
    db.create(Resource::from(("person", "two"))).await?;
    db.create(Resource::from(("person", "three"))).await?;

        let deleted_one: Option<Person> = db.delete(("person",
        "one")).await?;
    dbg!(deleted_one);
    let deleted_rest: Vec<Person> = db.delete("person").await?;
    dbg!(deleted_rest);
    Ok(())
}
```

## Restrict records with `.range()`

For deletes targeting every record in a table, chain [`.range(...)`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Delete.html#method.range-1) so only record IDs inside the [`RecordIdKeyRange`](https://docs.rs/surrealdb/latest/surrealdb/types/struct.RecordIdKeyRange.html) are removed.

```rust
use surrealdb::{
    engine::any::connect,
    opt::Resource,
    types::{ToSql, Value},
};

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("ns").use_db("db").await.unwrap();

    db.query("CREATE person:alucard, person:plato, person:vlad")
        .await
        .unwrap();

    let res = db
        .delete::<Value>(Resource::from("person"))
        .range("n"..="z")
        .await
        .unwrap()
        .to_sql();
    println!("{res:?}");
}
```

**2.x**

Deletes all records in a table, or a specific record, from the database.

```rust title="Method Syntax"
db.delete(resource)
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select. Will also accept a tuple of record name and ID.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;
use surrealdb::RecordId;

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    id: RecordId,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    db.use_ns("ns").use_db("db").await?;

    // Create three `person` records
    db.create(Resource::from(("person", "one"))).await?;
    db.create(Resource::from(("person", "two"))).await?;
    db.create(Resource::from(("person", "three"))).await?;

        let deleted_one: Option<Person> = db.delete(("person",
        "one")).await?;
    dbg!(deleted_one);
    let deleted_rest: Vec<Person> = db.delete("person").await?;
    dbg!(deleted_rest);
    Ok(())
}
```

## Translated query

While SurrealQL's `DELETE` statement returns an empty array by default, this function translates into a query that adds a `RETURN BEFORE` clause to return the deleted items.

```surql
DELETE FROM $resource RETURN BEFORE;
```

## See also

* [.delete() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.delete)

---

Source: https://surrealdb.com/docs/reference/rust/methods/export

# export

The .export() method for the SurrealDB Rust SDK dumps the database contents to a file.

**3.x**

Dumps the database contents to a file.

> [!NOTE]
> WebSocket connections currently do not support exports and imports. Be sure to use an HTTP endpoint and the `protocol-http` feature when using this method.

```rust title="Method Syntax"
db.export(target)
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select. Will also accept a tuple of record name and ID.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

The `.export()` method can be used to save the contents of a database to a file.

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("http://localhost:8000").await?;
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;
    db.use_ns("main").use_db("main").await?;

    // Create a `person` record
    db.create(Resource::from("person")).await?;

    db.export("backup.surql").await?;
    Ok(())
}
```

If an empty tuple is passed in for the file name, the `.export()` method will instead return an async stream of bytes.

```rust
use futures::StreamExt;
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("http://localhost:8000").await?;
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;
    db.use_ns("main").use_db("main").await?;

    // Create a `person` record
    db.create(Resource::from("person")).await?;

    let mut stream = db.export(()).await?;

    while let Some(Ok(line)) = stream.next().await {
        let content = String::from_utf8(line).unwrap();
        println!("{content}");
    }
    Ok(())
}
```

The output for the above sample should look like the following.

```surql
-- ------------------------------

-- OPTION

-- ------------------------------


OPTION IMPORT;


-- ------------------------------

-- TABLE: person

-- ------------------------------


DEFINE TABLE person TYPE ANY SCHEMALESS PERMISSIONS NONE;


-- ------------------------------

-- TABLE DATA: person

-- ------------------------------


INSERT [ { id: person:bgq0b0rblnozrufizdjm } ];
```

## Export configuration

The [`Export`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Export.html) struct has a method called `.with_config()` that gives access to the configuration parameters for the export. These can be chained one after another inside a single line of code. The majority of these functions take a single `bool`:

* `.versions()`: whether to include [version information](/docs/reference/query-language/statements/select.md#the-version-clause) for the SurrealKV storage backend
* `.accesses()`: whether to include [`DEFINE ACCESS` statements](/docs/reference/query-language/statements/define/access/record.md)
* `.analyzers()`: whether to include [`DEFINE ANALYZER` statements](/docs/reference/query-language/statements/define/analyzer.md)
* `.functions()`: whether to include [`DEFINE FUNCTION` statements](/docs/reference/query-language/statements/define/function.md)
* `.apis()`: whether to include API definitions in the export
* `.buckets()`: whether to include bucket definitions
* `.modules()`: whether to include [`DEFINE MODULE`](/docs/reference/query-language/statements/define/module.md) statements
* `.configs()`: whether to include stored configuration definitions
* `.records()`: whether to include the existing records in the database
* `.params()`: whether to include [`DEFINE PARAM` statements](/docs/reference/query-language/statements/define/param.md)
* `.users()`: whether to include [`DEFINE USER` statements](/docs/reference/query-language/statements/define/user.md)

`.tables()` takes a `Vec` of strings in addition to a boolean.

* `.tables()`: a list of tables to export, as opposed to all of the tables in the database.

Example of export configuration:

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("http://localhost:8000").await?;
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;
    db.use_ns("ns").use_db("db").await?;

    db.query(
        "
    DEFINE FUNCTION fn::get_cats() -> array<object> { RETURN SELECT * FROM cat };
    DEFINE TABLE person SCHEMAFULL;
    DEFINE FIELD name ON person TYPE string;
    DEFINE FIELD age ON person TYPE int;
    CREATE person SET name = 'Aeon', age = 20;
    CREATE cat SET name = 'Cat of Aeon';
    ",
    )
    .await?;

    // Cat-related implementation is still experimental
    // so don't export the cat table or get_cats() function
    db.export("backup.surql")
        .with_config()
        .tables(vec!["person"])
        .functions(false)
        .await?;
    Ok(())
}
```

## See also

* [.export() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.export)

**2.x**

Dumps the database contents to a file.

> [!NOTE]
> WebSocket connections currently do not support exports and imports. Be sure to use an HTTP endpoint when using this method.

```rust title="Method Syntax"
db.export(target)
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select. Will also accept a tuple of record name and ID.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

The `.export()` method can be used to save the contents of a database to a file.

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("http://localhost:8000").await?;
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    db.use_ns("ns").use_db("db").await?;

    // Create a `person` record
    db.create(Resource::from("person")).await?;

    db.export("backup.surql").await?;
    Ok(())
}
```

If an empty tuple is passed in for the file name, the `.export()` method will instead return an async stream of bytes.

```rust
use futures::StreamExt;
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::opt::Resource;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("http://localhost:8000").await?;
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    db.use_ns("ns").use_db("db").await?;

    // Create a `person` record
    db.create(Resource::from("person")).await?;

    let mut stream = db.export(()).await?;

    while let Some(Ok(line)) = stream.next().await {
        let content = String::from_utf8(line).unwrap();
        println!("{content}");
    }
    Ok(())
}
```

The output for the above sample should look like the following.

```surql
-- ------------------------------

-- OPTION

-- ------------------------------


OPTION IMPORT;


-- ------------------------------

-- TABLE: person

-- ------------------------------


DEFINE TABLE person TYPE ANY SCHEMALESS PERMISSIONS NONE;


-- ------------------------------

-- TABLE DATA: person

-- ------------------------------


INSERT [ { id: person:bgq0b0rblnozrufizdjm } ];
```

## Export configuration

The [`Export`](https://docs.rs/surrealdb/2/surrealdb/method/struct.Export.html) struct has a method called `.with_config()` that gives access to the configuration parameters for the export. These can be chained one after another inside a single line of code. The majority of these functions take a single `bool`:

* `.versions()`: whether to include [version information](/docs/reference/query-language/statements/select.md#the-version-clause) for backends with versioning enabled
* `.accesses()`: whether to include [`DEFINE ACCESS` statements](/docs/reference/query-language/statements/define/access/record.md)
* `.analyzers()`: whether to include [`DEFINE ANALYZER` statements](/docs/reference/query-language/statements/define/analyzer.md)
* `.functions()`: whether to include [`DEFINE FUNCTION` statements](/docs/reference/query-language/statements/define/function.md)
* `.apis()`: whether to include [defined APIs](/docs/reference/query-language/statements/define/api.md) in the export
* `.buckets()`: whether to include [defined buckets](/docs/reference/query-language/statements/define/bucket.md)
* `.modules()`: whether to include [`DEFINE MODULE`](/docs/reference/query-language/statements/define/module.md) statements
* `.configs()`: whether to include stored configuration definitions
* `.records()`: whether to include the existing records in the database
* `.params()`: whether to include [`DEFINE PARAM` statements](/docs/reference/query-language/statements/define/param.md)
* `.users()`: whether to include [`DEFINE USER` statements](/docs/reference/query-language/statements/define/user.md)

`.tables()` takes a `Vec` of strings in addition to a boolean.

* `.tables()`: a list of tables to export, as opposed to all of the tables in the database.

Example of export configuration:

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("http://localhost:8000").await?;
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    db.use_ns("ns").use_db("db").await?;

    db.query(
        "
    DEFINE FUNCTION fn::get_cats() -> array<object> { RETURN SELECT * FROM cat };
    DEFINE TABLE person SCHEMAFULL;
    DEFINE FIELD name ON person TYPE string;
    DEFINE FIELD age ON person TYPE int;
    CREATE person SET name = 'Aeon', age = 20;
    CREATE cat SET name = 'Cat of Aeon';
    ",
    )
    .await?;

    // Cat-related implementation is still experimental
    // so don't export the cat table or get_cats() function
    db.export("backup.surql")
        .with_config()
        .tables(vec!["person"])
        .functions(false)
        .await?;
    Ok(())
}
```

## See also

* [.export() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.export)

---

Source: https://surrealdb.com/docs/reference/rust/methods/get

# get

The .get() method for the SurrealDB Rust SDK retrieves the value at a certain field or index.

_(since v3.0.0)_

The `.get()` method for the `Value` struct retrieves the value at a certain field for an object, or a certain index for an array. The method takes a `&str` or a `usize` as an argument.

```rust title="Method Syntax"
value.get(target)
```

## Example usage

```rust
use surrealdb::engine::any::connect;
use surrealdb::types::{ToSql, Value};

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("mem://").await?;
    db.use_ns("main").use_db("main").await?;

    let mut res = db
        .query(
            "[
	{
		a: {
			big: [
				'nested',
				'object'
			]
		}
	},
	{
		another: {
			big: [
				'nested',
				'object'
			]
		}
	}
];",
        )
        .await?;
    let as_value = res.take::<Value>(0)?;
    // Get the value at index 0, field 'a'
    // Output: { big: ['nested', 'object'] }
    println!("{}", as_value.get(0).get("a").to_sql());
    Ok(())
}
```

As the `.get()` method will always return a `Value`, internally a `Value::None` is returned when nothing is found at a certain index or field. The methods `.is_none()` and `.into_option()` can be used on the `Value` struct to check if the `.get()` method has returned a non-None value or not.

## See also

* [.get() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/types/enum.Value.html#method.get)

---

Source: https://surrealdb.com/docs/reference/rust/methods/health

# health

The .health() method for the SurrealDB Rust SDK checks that the server-side connection is able to run a health command (useful for readiness and liveness checks).

Runs a lightweight health check on the current session/connection. On success, the future resolves to `Ok(())`; otherwise you get a [`surrealdb::Error`](https://docs.rs/surrealdb/latest/surrealdb/struct.Error.html).

```rust title="Method Syntax"
db.health().await?
```

This is a simple way to assert the database is reachable and accepting commands after `connect` (or periodically in long-lived processes) without sending a full SurrealQL query.

## See also

* [`.health()` on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.health)

---

Source: https://surrealdb.com/docs/reference/rust/methods/import

# import

The .import() method for the SurrealDB Rust SDK restores the database from a file.

**3.x**

Restores the database from a file.

> [!NOTE]
> WebSocket connections currently do not support exports and imports. Be sure to use an HTTP endpoint and the `protocol-http` feature when using this method.

```rust title="Method Syntax"
db.import(source)
```

## Example usage

The following example assumes the presence of a file called `backup.surql` in the same directory as the current project. To quickly create it, copy and paste [the example for the .export() method](/docs/reference/rust/methods/export.md).

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("http://localhost:8000").await?;
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;
    db.use_ns("main").use_db("main").await?;
    db.import("backup.surql").await?;
    Ok(())
}
```

## See also

* [.import() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.import)

**2.x**

Restores the database from a file.

> [!NOTE]
> WebSocket connections currently do not support exports and imports. Be sure to use an HTTP endpoint when using this method.

```rust title="Method Syntax"
db.import(source)
```

## Example usage

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("http://localhost:8000").await?;
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    db.use_ns("main").use_db("main").await?;
    db.import("backup.surql").await?;
    Ok(())
}
```

## See also

* [.import() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.import)

---

Source: https://surrealdb.com/docs/reference/rust/methods/init

# init

The .init() method for the SurrealDB Rust SDK initializes a new unconnected instance.

The .init() method initializes a new unconnected instance of the client.
This is typically used to create a global, static instance of the client.

```rust title="Method Syntax"
Surreal::init()
```

## Example usage

```rust
use std::sync::LazyLock;
use surrealdb::engine::remote::ws::{Client, Ws};
use surrealdb::Surreal;

static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Connect to the database
    DB.connect::<Ws>("127.0.0.1:8000").await?;
    Ok(())
}
```

`Surreal::init()` can also be used to create an instance of `Surreal<Any>`, allowing you to choose at runtime which way to connect.

```rust
use std::env;
use std::sync::LazyLock;
use surrealdb::engine::any::Any;
use surrealdb::Surreal;

static DB: LazyLock<Surreal<Any>> = LazyLock::new(Surreal::init);

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Choose an endpoint at runtime using the `DB_ENDPOINT` environment variable
    // or fallback to the memory engine.
    let endpoint = env::var("DB_ENDPOINT").unwrap_or_else(|_| "mem://".to_owned());
    DB.connect(endpoint).await?;
    Ok(())
}
```

## See also

* [.init() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.init)

---

Source: https://surrealdb.com/docs/reference/rust/methods/insert

# insert

The .insert() method for the SurrealDB Rust SDK inserts a record or records into a table.

`insert` adds one record or many to a table, and inserts graph edges through `.relation()`.

**3.x**

```rust title="Method Syntax"
db.insert(resource).content(data);
db.insert(resource).relation(data);
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The relation table data to insert.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

Inserting a record with a specific ID:

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(SurrealValue)]
struct Data {
    name: String,
    settings: Settings,
}

#[derive(Debug, SurrealValue)]
struct Person {
    name: String,
    settings: Settings,
}

#[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("ns").use_db("db").await?;

    let person: Option<Person> = db
        .insert(("person", "tobie"))
        .content(Data {
            name: "Tobie".to_string(),
            settings: Settings {
                active: true,
                marketing: true,
            },
        })
        .await?;
    dbg!(person);
    Ok(())
}
```

Inserting multiple records into a table:

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(SurrealValue)]
struct Data {
    name: String,
    settings: Settings,
}

#[derive(Debug, SurrealValue)]
struct Person {
    name: String,
    settings: Settings,
}

#[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("ns").use_db("db").await?;

    let people: Vec<Person> = db
        .insert("person")
        .content(vec![
            Data {
                name: "Tobie".to_string(),
                settings: Settings {
                    active: true,
                    marketing: false,
                },
            },
            Data {
                name: "Jaime".to_string(),
                settings: Settings {
                    active: true,
                    marketing: true,
                },
            },
        ])
        .await?;
    dbg!(people);
    Ok(())
}
```

An example of two `person` records and one `company` record, followed by `.insert().relation()` to create a relation between them. Note the usage of the `#[surreal(rename)]` attribute to interface between the Rust struct `Founded` and the original relation table, which must have [an `in` and an `out` field](/docs/reference/query-language/statements/relate.md).

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::{RecordId, SurrealValue};

#[derive(SurrealValue)]
struct Data {
    id: RecordId,
    name: String,
}

#[derive(Debug, SurrealValue)]
struct Record {
    name: String,
    id: RecordId,
}

#[derive(Debug, SurrealValue)]
struct Founded {
    #[surreal(rename = "in")]
    founder: RecordId,
    #[surreal(rename = "out")]
    company: RecordId,
}

#[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 records: Vec<Record> = db
        .insert("person")
        .content(vec![
            Data {
                id: RecordId::new("person", "tobie"),
                name: "Tobie".to_string(),
            },
            Data {
                id: RecordId::new("person", "jaime"),
                name: "Jaime".to_string(),
            },
            Data {
                id: RecordId::new("company", "surrealdb"),
                name: "SurrealDB".to_string(),
            },
        ])
        .await?;
    dbg!(records);

    let founded: Vec<Founded> = db
        .insert("founded")
        .relation(vec![
            Founded {
                founder: RecordId::new("person", "tobie"),
                company: RecordId::new("company", "surrealdb"),
            },
            Founded {
                founder: RecordId::new("person", "jaime"),
                company: RecordId::new("company", "surrealdb"),
            },
        ])
        .await?;
    dbg!(founded);
    Ok(())
}
```

The equivalent SurrealQL statements to create and query the relations are:

```surql
RELATE [person:jaime, person:tobie]->founded->company:surrealdb;
SELECT ->founded->company FROM person;
```

## See also

* [.insert() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.insert)

**2.x**

```rust title="Method Syntax"
db.insert(resource).content(data);
db.insert(resource).relation(data);
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The relation table data to insert.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

Inserting a record with a specific ID:

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Serialize)]
struct Data<'a> {
    name: &'a str,
    settings: Settings,
}

#[derive(Debug, Deserialize)]
struct Person {
    name: String,
    settings: Settings,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    let person: Option<Person> = db
        .insert(("person", "tobie"))
        .content(Data {
            name: "Tobie",
            settings: Settings {
                active: true,
                marketing: true,
            },
        })
        .await?;
    dbg!(person);
    Ok(())
}
```

Inserting multiple records into a table:

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Serialize)]
struct Data<'a> {
    name: &'a str,
    settings: Settings,
}

#[derive(Debug, Deserialize)]
struct Person {
    name: String,
    settings: Settings,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    let people: Vec<Person> = db
        .insert("person")
        .content(vec![
            Data {
                name: "Tobie",
                settings: Settings {
                    active: true,
                    marketing: false,
                },
            },
            Data {
                name: "Jaime",
                settings: Settings {
                    active: true,
                    marketing: true,
                },
            },
        ])
        .await?;
    dbg!(people);
    Ok(())
}
```

The `.insert()` method can take an empty tuple instead of a table ID if the following method contains [a record ID](https://docs.rs/surrealdb/latest/surrealdb/struct.RecordId.html).

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::RecordId;

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Serialize)]
struct Data<'a> {
    id: RecordId,
    name: &'a str,
}

#[derive(Debug, Deserialize)]
struct Person {
    name: String,
    id: RecordId,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    let people: Vec<Person> = db
        .insert(())
        .content(vec![
            Data {
                id: RecordId::from(("person", "tobie")),
                name: "Tobie",
            },
            Data {
                id: RecordId::from(("person", "jaime")),
                name: "Jaime",
            },
        ])
        .await?;
    dbg!(people);
    Ok(())
}
```

An example of two `person` records and one `company` record, followed by `.insert().relation()` to create a relation between them. Note the usage of the `#[serde(rename)]` attribute to interface between the Rust struct `Founded` and the original relation table, which must have [an `in` and an `out` field](/docs/reference/query-language/statements/relate.md).

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::RecordId;

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Serialize)]
struct Data<'a> {
    id: RecordId,
    name: &'a str,
}

#[derive(Debug, Deserialize)]
struct Record {
    name: String,
    id: RecordId,
}

#[derive(Debug, Serialize, Deserialize)]
struct Founded {
    #[serde(rename = "in")]
    founder: RecordId,
    #[serde(rename = "out")]
    company: RecordId,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    let records: Vec<Record> = db
        .insert(())
        .content(vec![
            Data {
                id: RecordId::from(("person", "tobie")),
                name: "Tobie",
            },
            Data {
                id: RecordId::from(("person", "jaime")),
                name: "Jaime",
            },
            Data {
                id: RecordId::from(("company", "surrealdb")),
                name: "SurrealDB",
            },
        ])
        .await?;
    dbg!(records);

    let founded: Vec<Founded> = db
        .insert("founded")
        .relation(vec![
            Founded {
                founder: RecordId::from(("person", "tobie")),
                company: RecordId::from(("company", "surrealdb")),
            },
            Founded {
                founder: RecordId::from(("person", "jaime")),
                company: RecordId::from(("company", "surrealdb")),
            },
        ])
        .await?;
    dbg!(founded);
    Ok(())
}
```

The equivalent SurrealQL statements to create and query the relations are:

```surql
RELATE [person:jaime, person:tobie]->founded->company:surrealdb;
SELECT ->founded->company FROM person;
```

## See also

* [.insert() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.insert)

---

Source: https://surrealdb.com/docs/reference/rust/methods/invalidate

# invalidate

The .invalidate() method for the SurrealDB Rust SDK invalidates the authentication for the current connection.

Invalidates the authentication for the current connection.

```rust title="Method Syntax"
db.invalidate()
```

**3.x**

## Example usage

Note: the following example uses the `ureq` crate with the `json` feature to first send a request to the database's [`/signup`](/docs/reference/rest-api/http-protocol.md#signup) endpoint which returns a token. The `reqwest` crate and others can be used here instead.

Alternatively, you could use a command like the following, copy the returned token, and paste it into the `.authenticate()` method which is used before `.invalidate()`.

```bash
curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"info@surrealdb.com","pass":"123456"}' http://localhost:8000/signup`
```

As the `DEFINE ACCESS` method below shows, a token will remain valid by default for 15 minutes.

```rust
// Use the following statement to set up the 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
// ;

// DEFINE TABLE cat SCHEMALESS
//     PERMISSIONS for select, update, delete, create
//     WHERE $auth.id;

use serde::{Deserialize, Serialize};
use std::fmt::Display;
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::types::SurrealValue;

#[derive(Deserialize, SurrealValue)]
struct Response {
    token: String,
}

impl Display for Response {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.token)
    }
}

#[derive(Serialize)]
struct Signup {
    ns: String,
    db: String,
    ac: String,
    email: String,
    pass: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    let response_string = ureq::post("http://127.0.0.1:8000/signup")
        .header("Accept", "application/json")
        .send_json(Signup {
            ns: "main".to_string(),
            db: "main".to_string(),
            ac: "account".to_string(),
            email: "info@surrealdb.com".to_string(),
            pass: "123456".to_string(),
        })
        .unwrap()
        .into_body()
        .read_to_string()
        .unwrap();

    let response = serde_json::from_str::<Response>(&response_string).unwrap();

    db.authenticate(response.token).await?;
    // User is present inside the $auth parameter
    println!("{:?}", db.query("$auth").await?);

    db.invalidate().await?;
    // Invalidated, now unable to see this parameter
    println!("{:?}", db.query("$auth;").await?);

    Ok(())
}
```

The output for both `println!` statements should look like this.

```text
IndexedResults { results: {0: (DbResultStats { execution_time: Some(26.458µs), query_type: Some(Other) }, Ok(RecordId(RecordId { table: Table("user"), key: String("ajdvh7uwk0sc79j48liw") })))}, live_queries: {} }
Error: Error { code: -32002, message: "Anonymous access not allowed: Not enough permissions to perform this action", details: NotAllowed(Some(Auth(NotAllowed { actor: "anonymous", action: "process", resource: "query" }))) }
```

## Revoking a refresh token (`.refresh(token)`)

[`db.invalidate()`](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.invalidate) normally clears the whole session. To revoke only the refresh token carried inside a [`Token`](https://docs.rs/surrealdb/latest/surrealdb/opt/auth/struct.Token.html) (without invalidating the entire session), call [`.refresh(token)`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Invalidate.html#method.refresh) on the invalidate builder, then await:

```rust
// Get a token from signin
let token = db.signin(credentials).await?;

// Later, explicitly revoke the refresh token
db.invalidate().refresh(token).await?;
```

Use this when you need to drop refresh capability for a specific token pair while leaving other session state intact.

## See also

* [.invalidate() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.invalidate)
* [`Invalidate::refresh` on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Invalidate.html#method.refresh)

**2.x**

## Example usage

Note: the following example uses the `ureq` crate with the `json` feature to first send a request to the database's [`/signup`](/docs/reference/rest-api/http-protocol.md#signup) endpoint which returns a token. The `reqwest` crate and others can be used here instead.

Alternatively, you could use a command like the following, copy the returned token, and paste it into the `.authenticate()` method which is used before `.invalidate()`.

```bash
curl -X POST -H "Accept: application/json" -d '{"ns":"main","db":"main","ac":"account","user":"info@surrealdb.com","pass":"123456"}' http://localhost:8000/signup`
```

As the `DEFINE ACCESS` method below shows, a token will remain valid by default for 15 minutes.

```rust
// Use the following statement to set up the 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
// ;

// DEFINE TABLE cat SCHEMALESS
//     PERMISSIONS for select, update, delete, create
//     WHERE $auth.id;

use serde::Deserialize;
use std::fmt::Display;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::Surreal;

#[derive(Deserialize)]
struct Response {
    token: String,
}

impl Display for Response {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.token)
    }
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    let token = ureq::post("http://127.0.0.1:8000/signup")
        .set("Accept", "application/json")
        .send_json(ureq::json!({
            "ns": "main",
            "db": "main",
            "ac": "account",
            "email": "info@surrealdb.com",
            "pass": "123456"
        }))
        .unwrap()
        .into_json::<Response>()
        .unwrap()
        .to_string();

    db.authenticate(token).await?;
    // User is present inside the $auth parameter
    dbg!(db.query("RETURN $auth").await?);

    db.invalidate().await?;
    // User is now gone
    dbg!(db.query("RETURN $auth;").await?);

    Ok(())
}
```

## See also

* [.invalidate() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.invalidate)

---

Source: https://surrealdb.com/docs/reference/rust/methods/new

# new

The .new() method for the SurrealDB Rust SDK connects to a local or remote database endpoint.

**3.x**

Connects to a local or remote database endpoint.

```rust title="Method Syntax"
Surreal::new::<T>(address)
```

## 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.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

### Basic example

```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::Surreal;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    Ok(())
}
```

### Configuring the database

The `new()` function takes an argument of [`impl IntoEndpoint`](https://docs.rs/surrealdb/latest/surrealdb/opt/trait.IntoEndpoint.html#foreign-impls), which is implemented not only for strings and string-like structs like [`PathBuf`](https://doc.rust-lang.org/std/path/struct.PathBuf.html) and [`SocketAddr`](https://doc.rust-lang.org/std/net/enum.SocketAddr.html), but also a tuple of one of these types for the address along with a second [`Config`](https://docs.rs/surrealdb/latest/surrealdb/opt/struct.Config.html) struct for the configuration.

```rust title="Example with all capabilities enabled except one function"
use surrealdb::{Error, engine::any::connect, opt::{Config, capabilities::Capabilities}};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let mut capabilities = Capabilities::all();
    capabilities.deny_function("math::abs").unwrap();
    let config = Config::default()
        .capabilities(capabilities);
    let db = connect(("mem://", config)).await?;

    db.use_ns("main").use_db("main").await?;

    // Result: "Function 'math::abs' is not allowed to be executed"
    println!("{:?}", db.query("math::abs(-10)").await?);

    Ok(())
}
```

#### Config options

`Config` is a builder with methods that can be chained. `Config::new()` and `Config::default()` are equivalent starting points.

<table>
    <thead>
        <tr>
            <th scope="col">Method</th>
            <th scope="col">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Method"><code>.user(root)</code></td>
            <td scope="row" data-label="Description">Sets the root user the local engines start with. Takes an <code>opt::auth::Root</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><code>.capabilities(caps)</code></td>
            <td scope="row" data-label="Description">Sets which functions, network targets, scripting and live queries are permitted. See <a href="/docs/learn/security/authorization/capabilities.md">Capabilities</a>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><code>.query_timeout(duration)</code></td>
            <td scope="row" data-label="Description">Maximum time a single query may run for.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><code>.transaction_timeout(duration)</code></td>
            <td scope="row" data-label="Description">Maximum time a transaction may stay open for.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><code>.ast_payload()</code></td>
            <td scope="row" data-label="Description">Sends queries as a parsed AST rather than as text. <code>.set_ast_payload(bool)</code> sets the same option from a variable.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><code>.websocket(config)</code></td>
            <td scope="row" data-label="Description">Sets the WebSocket buffer sizes. Returns a <code>Result</code>, as the maximum write buffer must be larger than the write buffer.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><code>.rustls(config)</code>, <code>.native_tls(config)</code></td>
            <td scope="row" data-label="Description">Configures TLS. Each requires the matching feature flag. Neither is covered by the SDK's stability guarantee, as the underlying crate is not yet stable.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><code>.temporary_directory(path)</code></td>
            <td scope="row" data-label="Description">Where the local engines spill temporary data. Requires a storage backend feature flag.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><code>.changefeed_gc_interval(duration)</code></td>
            <td scope="row" data-label="Description">How often expired change feed entries are collected.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><code>.node_membership_refresh_interval(duration)</code>, <code>.node_membership_check_interval(duration)</code>, <code>.node_membership_cleanup_interval(duration)</code></td>
            <td scope="row" data-label="Description">Intervals for the node maintenance tasks the local engines run. A zero duration is treated as unset.</td>
        </tr>
    </tbody>
</table>

An example of the `Config` builder used to set a number of attributes in a single chain:

```rust
use std::time::Duration;

use surrealdb::engine::any::connect;
use surrealdb::opt::Config;
use surrealdb::opt::auth::Root;
use surrealdb::opt::capabilities::Capabilities;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let config = Config::new()
        .user(Root {
            username: "root".to_string(),
            password: "secret".to_string(),
        })
        .query_timeout(Duration::from_secs(5))
        .transaction_timeout(Duration::from_secs(10))
        .capabilities(
            Capabilities::all()
                .with_function_denied("http::*")
                .unwrap(),
        );

    let db = connect(("mem://", config)).await?;
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;
    db.use_ns("main").use_db("main").await?;

    println!("{:?}", db.query("RETURN http::get('https://example.com')").await?);
    Ok(())
}
```

The denied function shows up as an error on the statement rather than on the call as a whole:

```text
Err(Error { code: -32602, message: "Function 'http::get' is not allowed to be executed", details: NotAllowed(Some(Function { name: "http::get" })), cause: None })
```

Note that the `Capabilities` methods that take a function or network target parse their argument, so they return a `Result`. The pairs come in two shapes: `allow_function` / `deny_function` take `&mut self`, while `with_function_allowed` / `with_function_denied` consume and return the value, which is what makes them chainable.

### Using a backend with versioning

To make a new connection that includes SurrealKV versioning, add the "kv-surrealkv" feature flag to the `surrealdb` dependency in `Cargo.toml`, add the path to the folder containing the database inside `new()`, and call the `.versioned()` method. Versioning is also available with the memory backend.

```rust
use surrealdb::{Surreal, engine::local::{Mem, SurrealKv}};

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // SurrealKV with versioning
    let db = Surreal::new::<SurrealKv>("path/to/database-folder")
        .versioned()
        .await?;

    // In-memory DB with versioning
    let mem_db = Surreal::new::<Mem>(())
        .versioned()
        .await?;
    Ok(())
}
```

## See also

* [.new() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.new)

**2.x**

Connects to a local or remote database endpoint.

```rust title="Method Syntax"
Surreal::new::<T>(address)
```

## 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.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

### Basic example

```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::Surreal;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    Ok(())
}
```

### Configuring the database

The `new()` function takes an argument of [`impl IntoEndpoint`](https://docs.rs/surrealdb/latest/surrealdb/opt/trait.IntoEndpoint.html#foreign-impls), which is implemented not only for strings and string-like structs like [`PathBuf`](https://doc.rust-lang.org/std/path/struct.PathBuf.html) and [`SocketAddr`](https://doc.rust-lang.org/std/net/enum.SocketAddr.html), but also a tuple of one of these types for the address along with a second [`Config`](https://docs.rs/surrealdb/latest/surrealdb/opt/struct.Config.html) struct for the configuration.

```rust title="Example with all capabilities enabled except one function"
#[tokio::main]
async fn main() -> Result<(), Error> {
    let config = Config::default()
        .capabilities(Capabilities::all().with_deny_function("math::abs")?);
    let db = connect(("mem://", config)).await?;

    db.use_ns("ns").use_db("db").await?;

    // Result: Err(Db(FunctionNotAllowed("math::abs")))
    println!("{:?}", db.query("math::abs(-10)").await?);
    println!("{:?}", db.run::<i32>("math::abs").args(-10).await);

    Ok(())
}
```

### Using SurrealKV with versioning

To make a new connection that includes SurrealKV versioning, add the `kv-surrealkv` feature flag to the `surrealdb` dependency in `Cargo.toml`, add the path to the folder containing the database inside `new()`, and call the `.versioned()` method.

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

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<SurrealKv>("path/to/database-folder").versioned().await?;
    Ok(())
}
```

## See also

* [.new() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.new)

---

Source: https://surrealdb.com/docs/reference/rust/methods/query

# query

The .query() method for the SurrealDB Rust SDK runs one or more SurrealQL statements against the database.

**3.x**

Runs one or more SurrealQL statements against the database.

```rust title="Method Syntax"
db.query(query)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>query</code>
            </td>
            <td colspan="2" scope="row" data-label="Type">
                <code>query</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

The `.query()` method serves as a default way to pass queries into the Rust SDK. The simplest usage of this method is by passing in a `&str` and returning an `IndexedResults`.

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[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 query = r#"
        LET $data = ["J. Jonah Jameson", "James Earl Jones"];
        RETURN $data.map(|$name| {
            LET $names = $name.split(' ');
            {
                first_name:  $names[0],
                middle_name: $names[1],
                last_name:   $names[2]
            }
        });
    "#;

    let result = db.query(query).await?;
    println!("Number of statements: {}", result.num_statements());
    dbg!(result);
    Ok(())
}
```

The `.take()` method can be used to pull out one of the responses into a deserialised format. Note that in the next example the `LET` statement is the first statement received by the database, and thus `.take(1)` is used to grab the output of the second statement to deserialise into a `Person` struct.

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Person {
    first_name: String,
    middle_name: String,
    last_name: 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 query = r#"
    LET $data = ["J. Jonah Jameson", "James Earl Jones"];
    RETURN $data.map(|$name| {
    LET $names = $name.split(' ');
    {
       first_name:  $names[0],
       middle_name: $names[1],
       last_name:   $names[2]
    }
    });"#;

    let mut result = db.query(query).await?;
    let people: Vec<Person> = result.take(1)?;
    dbg!(people);
    Ok(())
}
```

The return value from this method is `Result<Response, Error>`. A `Result::Ok(Response)` only means that the query or queries were successfully executed, but does not mean that each query contained in the `Response` was successful.

Take the following code for example which contains one successful query, followed by one with incorrect syntax (an integer where a string is expected).

```rust
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    let res = db.query("
        LET $x = 9;
        LET $x: string = 9") // valid SurrealQL but wrong type
        .await;
    println!("{res:?}");
}
```

The `.query()` method returns an `Ok(IndexedResults)`, showing that the database was able to understand and process the queries, even though the latter returned an error.

```text
Ok(IndexedResults { results: {0: (DbResultStats { execution_time: Some(392.875µs), query_type: Some(Other) }, Ok(None)), 1: (DbResultStats { execution_time: Some(426.042µs), query_type: Some(Other) }, Err(InternalError("Tried to set `$x`, but couldn't coerce value: Expected `string` but found `9`")))}, live_queries: {} })
```

But if the function contains input that the database is unable to parse into a query in the first place, an `Err` will be returned for the entire `.query()` call.
If the `string` syntax is changed to something nonsensical like `Hi how are you?`, the database is unable to process the query in the first place and `.query()` will return an `Err` for the whole call.

```rust
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    let res = db.query("
        LET $x = 9;
        Hi how are you?;") // invalid SurrealQL
        .await;
    println!("{res:?}");
}
```

```text
Err(ParseError("Parse error: Unexpected token `an identifier`, expected Eof\n --> [3:12]\n  |\n3 | Hi how are you?;\n  |    ^^^\n"))
```

The `IndexedResults` struct contains helper metods such as `.check()` to check for errors, or `.take_errors()` which removes the errors from the main `IndexedResults`.

```rust
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("ns").use_db("db").await.unwrap();
    let mut res = db
        .query(
        "LET $x = 9;
        LET $x: string = 9;
        LET $x: bool = 9;
        CREATE person",
        )
        .await
        .unwrap();

    println!("Errors: {:?}\n", res.take_errors());
    println!("Successes: {:?}", res);
}
```

Output:

```text
Errors: {2: InternalError("Tried to set `$x`, but couldn't coerce value: Expected `bool` but found `9`"), 1: InternalError("Tried to set `$x`, but couldn't coerce value: Expected `string` but found `9`")}

Successes: IndexedResults { results: {0: (DbResultStats { execution_time: Some(301.375µs), query_type: Some(Other) }, Ok(None)), 3: (DbResultStats { execution_time: Some(2.278083ms), query_type: Some(Other) }, Ok(Array(Array([Object(Object({"id": RecordId(RecordId { table: Table("person"), key: String("yq7gxgm3ffkr4kibumeb") })}))]))))}, live_queries: {} }
```

## Binding parameters (`.bind()`) {#binding-parameters}

The [`.bind()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Query.html#method.bind) method sets the parameters that a query refers to with SurrealQL's `$` syntax. It accepts anything that implements `IntoVariables`, which covers any type implementing `SurrealValue` that converts into an object, plus key-value pairs.

<table>
    <thead>
        <tr>
            <th scope="col">Form</th>
            <th scope="col">Example</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Form">A single key-value pair</td>
            <td scope="row" data-label="Example"><code>.bind(("table", "person"))</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Form">The <code>vars!</code> macro</td>
            <td scope="row" data-label="Example"><code>.bind(vars! { table: "person", min_age: 18 })</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Form">The <code>object!</code> macro</td>
            <td scope="row" data-label="Example"><code>.bind(object! { table: "person" })</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Form">A struct deriving <code>SurrealValue</code></td>
            <td scope="row" data-label="Example"><code>.bind(Filters { min_age: 18 })</code></td>
        </tr>
        <tr>
            <td scope="row" data-label="Form">A map, such as <code>HashMap&lt;String, Value&gt;</code></td>
            <td scope="row" data-label="Example"><code>.bind(map)</code></td>
        </tr>
    </tbody>
</table>

The [`vars!`](/docs/reference/rust/concepts/working-with-types.md#the-vars-macro) macro is the most direct way to set several parameters at once, as each pair is written in place rather than chained one call at a time.

```rust
use surrealdb::engine::any::connect;
use surrealdb::types::{RecordId, SurrealValue, vars};

#[derive(Debug, SurrealValue)]
struct Person {
    id: RecordId,
    name: String,
    age: i64,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("memory").await?;
    db.use_ns("ns").use_db("db").await?;

    let statements = "
        CREATE type::table($table) SET name = $name, age = $age;
        SELECT * FROM type::table($table) WHERE age >= $min_age;
    ";

    let mut result = db
        .query(statements)
        .bind(vars! {
            table: "person",
            name: "Aeon",
            age: 30,
            min_age: 18,
        })
        .await?;

    let created: Option<Person> = result.take(0)?;
    dbg!(created);
    let adults: Vec<Person> = result.take(1)?;
    dbg!(adults);
    Ok(())
}
```

Calls to `.bind()` accumulate rather than replace, so parameters can be gathered from more than one place before the query is awaited. A later call wins if it repeats a name.

```rust
let mut result = db
    .query("RETURN [$a, $b]")
    .bind(vars! { a: 1 })
    .bind(vars! { b: 2 })
    .await?;
```

> [!NOTE]
> A binding error is not raised at the point of the `.bind()` call. It is held until the query is awaited, and surfaces there as the result of the whole query.

## Per-statement stats (`.with_stats()`)

The [`.with_stats()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Query.html#method.with_stats) method can be used on the query builder before awaiting the future. The awaited value is [`WithStats<IndexedResults>`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.WithStats.html), which wraps the usual [`IndexedResults`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html) so each `.take(...)` can return both [`Stats`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Stats.html) (including execution time) and the deserialised statement result.

```rust
use surrealdb::engine::any::connect;
use surrealdb::types::Value;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("memory").await?;
    db.use_ns("ns").use_db("db").await?;

    let mut response = db
        .query("CREATE person:ada SET name = 'Ada'; SELECT * FROM person;")
        .with_stats()
        .await?;

    if let Some((stats, res)) = response.take(1) {
        let records: Vec<Value> = res?;
        println!("time = {:?}, records = {:?}", stats.execution_time, records);
    }
    Ok(())
}
```

See [`WithStats::take`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.WithStats.html#method.take) on Docs.rs for the supported `.take` shapes (statement index, nested paths, and tuples).

## Stream `LIVE SELECT` output (`.stream()`) {#live-select-stream}

After awaiting `.query(...)`, [`IndexedResults::stream`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html#method.stream) turns the live-query slot at a given statement index into a [`QueryStream`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.QueryStream.html). Pass a statement index (`0`, `1`, …), or pass `()` to merge every `LIVE SELECT` in that response. The stream yields [`Notification`](https://docs.rs/surrealdb/latest/surrealdb/struct.Notification.html) values (or raw [`Value`](https://docs.rs/surrealdb/latest/surrealdb/types/enum.Value.html)) and implements [`futures::Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html). This can be polled with the [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html) trait from the `futures` crate.

If you prefer not to embed `LIVE SELECT` in SurrealQL, the same live subscription can be started with [`db.select(resource).live()`](/docs/reference/rust/methods/select-live.md) on top of [`select()`](/docs/reference/rust/methods/select.md); both approaches yield a stream of notifications.

```rust
use futures::StreamExt;
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::Value;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;
    db.use_ns("main").use_db("main").await?;
    db.signin(Root {
        username: "root".into(),
        password: "secret".into(),
    })
    .await?;

    // Use 2 or 3 instead of () to only LIVE SELECT
    // either person or cat
    let mut response = db
        .query(
            "DEFINE TABLE IF NOT EXISTS person SCHEMALESS;
            DEFINE TABLE IF NOT EXISTS cat SCHEMALESS;
             LIVE SELECT * FROM person;
             LIVE SELECT * FROM cat;",
        )
        .await?;
    let mut stream = response.stream::<Value>(())?;

    while let Some(item) = stream.next().await {
        let notification = item?;
        println!("{:?}", notification);
    }
    Ok(())
}
```

To test the live stream, either log in using SurrealDB Studio or the CLI using the `surreal sql --user root --pass secret` command in another terminal window. You should see notifications similar to the following whenever a new record is created from the `person` or `cat` table, but not for others.

```bash
Notification { query_id: Uuid(3bad02bb-fd1e-402b-9a43-5b3eae88f279), action: Create, data: Object(Object({"id": RecordId(RecordId { table: Table("person"), key: String("zhby5ibqh8b2hfyyao30") })})) }
Notification { query_id: Uuid(829d7ec7-d67c-47ef-bd7c-8b3e10b8d149), action: Create, data: Object(Object({"id": RecordId(RecordId { table: Table("cat"), key: String("cgu921pkfco7uk7ajeym") })})) }
```

## Stream results as they arrive (`.stream_items()`) {#stream-items}

_(since v3.3.0)_

Awaiting a query gives an `IndexedResults`, which holds the entire result set in memory and yields nothing until the last row has been read. The [`.stream_items()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Query.html#method.stream_items) method returns an [`ItemStream`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.ItemStream.html) instead, so a large `SELECT` can be processed while the server is still producing it. The stream implements [`futures::Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html) and yields [`StreamItem`](https://docs.rs/surrealdb/latest/surrealdb/method/enum.StreamItem.html) values, of which there are two:

* `StreamItem::Row` carries one row along with the index of the statement that produced it.
* `StreamItem::StatementEnd` marks a statement as finished, and carries its stats and its `Result`.

```rust
use futures::StreamExt;
use surrealdb::engine::any::connect;
use surrealdb::method::StreamItem;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("memory").await?;
    db.use_ns("ns").use_db("db").await?;

    db.query("CREATE person:ada SET name = 'Ada'; CREATE person:grace SET name = 'Grace';")
        .await?
        .check()?;

    let mut rows = db.query("SELECT name FROM person").stream_items()?;

    while let Some(item) = rows.next().await {
        match item? {
            StreamItem::Row { statement, value } => {
                println!("statement {statement} produced {value:?}");
            }
            StreamItem::StatementEnd { statement, stats, result } => {
                result?;
                println!("statement {statement} finished in {:?}", stats.execution_time);
            }
        }
    }
    Ok(())
}
```

Output:

```text
statement 0 produced Object(Object({"name": String("Ada")}))
statement 0 produced Object(Object({"name": String("Grace")}))
statement 0 finished in Some(1.8045ms)
```

> [!IMPORTANT]
> Rows are provisional until their statement ends. A statement can still fail on a later row, and a `BEGIN … COMMIT` block can still roll back, so a `StatementEnd` carrying an error retracts every row that preceded it. Code that acts on rows as they arrive has to be able to undo that.

Two further points worth knowing:

* Only some engines stream incrementally. The embedded engines (`mem://`, `rocksdb://`, `surrealkv://` and file paths) and the gRPC remote engine (`grpc://`) deliver rows as the server produces them. The WebSocket and HTTP engines have no incremental path, so they run the query to completion and then replay the items. Every engine yields the same items in the same order; on those two they simply do not arrive any earlier than awaiting the query would.
* Dropping the stream stops the query, as the execution behind it holds an open transaction that has to be finalised rather than abandoned.

`LIVE SELECT` is not served by this method. A live query's ID arrives as an ordinary row and nothing subscribes to it. Use the awaited form described in [Stream `LIVE SELECT` output](#live-select-stream) for live queries, and `.stream_items()` for reading rows.

## Security when using the .query() method

As the `.query()` method can be used to pass any SurrealQL query on to the database, it is an easy go-to when using complex queries. However, be sure to keep [the following best practices in mind](/docs/learn/security/best-practices/security-best-practices.md#query-safety) when doing so.

<blockquote>
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 as a method to query, 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 vulnerabilities. This practice is known as prepared statements or parameterised queries.
</blockquote>

Thus, instead of using user input to directly construct a string:

```rust
let bad_sql = format!("
CREATE {user_input};
SELECT * FROM {user_input};");
```

You can insert a parameter using SurrealQL's `$` parameter syntax,

```rust
let sql = "
    CREATE person;
    SELECT * FROM type::table($table);
";
```

And then apply the `.bind()` method to pass the parameter in.

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::{RecordId, SurrealValue};

#[derive(Debug, SurrealValue)]
struct Person {
    id: RecordId,
}

#[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 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)?;
    dbg!(created);
    // Get all of the results from the second query
    let people: Vec<Person> = result.take(1)?;
    dbg!(people);
    Ok(())
}
```

## See also

* [.query() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.query)
* [`Query::with_stats`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Query.html#method.with_stats), [`WithStats`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.WithStats.html), [`IndexedResults::stream`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html#method.stream)
* [Live queries via `select().live()`](/docs/reference/rust/methods/select-live.md) (alternative to [`LIVE SELECT`](#live-select-stream) in this page)

**2.x**

Runs one or more SurrealQL statements against the database.

```rust title="Method Syntax"
db.query(query)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>query</code>
            </td>
            <td colspan="2" scope="row" data-label="Type">
                <code>query</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the SurrealQL statements.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

The `.query()` method serves as a default way to pass queries into the Rust SDK. The simplest usage of this method is by passing in a `&str` and returning an [`IndexedResults`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html).

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    let query = r#"
        LET $data = ["J. Jonah Jameson", "James Earl Jones"];
        RETURN $data.map(|$name| {
            LET $names = $name.split(' ');
            {
                first_name:  $names[0],
                middle_name: $names[1],
                last_name:   $names[2]
            }
        });
    "#;

    let result = db.query(query).await?;
    println!("Number of statements: {}", result.num_statements());
    dbg!(result);
    Ok(())
}
```

The `.take()` method can be used to pull out one of the responses into a deserialised format. Note that in the next example the `LET` statement is the first statement received by the database, and thus `.take(1)` is used to grab the output of the second statement to deserialise into a `Person` struct.

```rust
use serde::Deserialize;
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[derive(Debug, Deserialize)]
struct Person {
    first_name: String,
    middle_name: String,
    last_name: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    let query = r#"
    LET $data = ["J. Jonah Jameson", "James Earl Jones"];
    RETURN $data.map(|$name| {
    LET $names = $name.split(' ');
    {
       first_name:  $names[0],
       middle_name: $names[1],
       last_name:   $names[2]
    }
    });"#;

    let mut result = db.query(query).await?;
    let people: Vec<Person> = result.take(1)?;
    dbg!(people);
    Ok(())
}
```

The return value from this method is `Result<Response, Error>`. A `Result::Ok(Response)` only means that the query or queries were successfully executed, but does not mean that each query contained in the `Response` was successful.

Take the following code for example which contains one successful query, followed by one with incorrect syntax (an integer where a string is expected).

```rust
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    let res = db.query("
        LET $x = 9;
        LET $x: string = 9") // valid SurrealQL but wrong type
        .await;
    println!("{res:?}");
}
```

The `.query()` method returns an `Ok(Response)`, showing that the database was able to understand and process the queries, even though the latter returned an error.

```text
Ok(Response { results: {0: (Stats { execution_time: Some(197.875µs) }, Ok(None)), 1: (Stats { execution_time: Some(207.625µs) }, Err(Db(SetCheck { value: "9", name: "x", check: "string" })))}, live_queries: {} })
```

But if the function contains input that the database is unable to parse into a query in the first place, an `Err` will be returned for the entire `.query()` call.
If the `string` syntax is changed to something nonsensical like `Hi how are you?`, the database is unable to process the query in the first place and `.query()` will return an `Err` for the whole call.

```rust
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    let res = db.query("
        LET $x = 9;
        Hi how are you?;") // invalid SurrealQL
        .await;
    println!("{res:?}");
}
```

```text
Err(Db(InvalidQuery(RenderedError { errors: ["Unexpected token `an identifier`, expected Eof"], snippets: [Snippet { source: "LET $x = 9; Hi how are you?", truncation: None, location: Location { line: 1, column: 16 }, offset: 15, length: 3, label: None, kind: Error }] })))
```

The `IndexedResults` struct (named `Response` before SurrealDB 3.0) contains helper methods such as [`.check()`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html#method.check) to check for errors, or [`.take_errors()`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html#method.take_errors) which removes the errors from the main `IndexedResults`.

```rust
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("ns").use_db("db").await.unwrap();
    let mut res = db
        .query(
        "LET $x = 9;
        LET $x: string = 9;
        LET $x: bool = 9;
        CREATE person",
        )
        .await
        .unwrap();

    println!("Errors: {:?}\n", res.take_errors());
    println!("Successes: {:?}", res);
}
```

Output:

```text
Errors: {1: Db(SetCheck { value: "9", name: "x", check: "string" }), 2: Db(SetCheck { value: "9", name: "x", check: "bool" })}

Successes: Response { results: {0: (Stats { execution_time: Some(143.458µs) }, Ok(None)), 3: (Stats { execution_time: Some(1.463583ms) }, Ok(Array(Array([Object(Object({"id": RecordId(RecordId { table: "person", key: String("aokn0fp36pmqlxprjhre") })}))]))))}, live_queries: {} }
```

## Security when using the .query() method

As the `.query()` method can be used to pass any SurrealQL query on to the database, it is an easy go-to when using complex queries. However, be sure to keep [the following best practices in mind](/docs/learn/security/best-practices/security-best-practices.md#query-safety) when doing so.

<blockquote>
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 as a method to query, 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 vulnerabilities. This practice is known as prepared statements or parameterised queries.
</blockquote>

Thus, instead of using user input to directly construct a string:

```rust
let bad_sql = format!("
CREATE {user_input};
SELECT * FROM {user_input};");
```

You can insert a parameter using SurrealQL's `$` parameter syntax,

```rust
let sql = "
    CREATE person;
    SELECT * FROM type::table($table);
";
```

And then apply the `.bind()` method to pass the parameter in.

```rust
use serde::Deserialize;
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::RecordId;

#[derive(Debug, Deserialize)]
struct Person {
    id: RecordId,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    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)?;
    dbg!(created);
    // Get all of the results from the second query
    let people: Vec<Person> = result.take(1)?;
    dbg!(people);
    Ok(())
}
```

## See also

* [.query() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.query)

---

Source: https://surrealdb.com/docs/reference/rust/methods/run

# run

The .run() method for the SurrealDB Rust SDK runs a SurrealQL function.

**3.x**

Runs a SurrealQL function.

```rust title="Method Syntax"
db.run(function)
```

## 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>function</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the path of the function.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

Calling an existing SurrealQL function:

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root".into(),
        password: "secret".into()
    })
    .await?;

    let res: f32 = db.run("rand::float").await?;
    dbg!(res);
    Ok(())
}
```

User-defined functions can be called as well.

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;
    db.signin(Root {
        username: "root".into(),
        password: "secret".into(),
    })
    .await?;
    db.use_ns("ns").use_db("db").await?;

    db.query("DEFINE FUNCTION fn::return_one() -> int { RETURN 1 };")
        .await?;

    let res: i32 = db.run("fn::return_one").await?;
    dbg!(res);
    Ok(())
}
```

The return value of the `.run()` function can be deserialised in the same way as any other database function.

```rust title="Deserialize to user-defined struct"
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Person {
    first_name: String,
    middle_name: String,
    last_name: 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("ns").use_db("db").await?;

    db.query(
        "DEFINE FUNCTION fn::j_jonah_jameson() -> object { RETURN
        {
          first_name: 'J',
          middle_name: 'Jonah',
          last_name: 'Jameson'
        }
    };",
    )
    .await?;

    let res: Person = db.run("fn::j_jonah_jameson").await?;
    dbg!(res);
    Ok(())
}
```

```rust title="Deserialize as std library type"
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("main").use_db("main").await.unwrap();
    db.query("DEFINE FUNCTION fn::array() -> array<int> { [1,2,3] };")
        .await
        .unwrap();

    let res: Vec<i32> = db.run("fn::array").await.unwrap();
    println!("{res:?}");
}
```

**2.x**

Runs a SurrealQL function.

```rust title="Method Syntax"
db.run(function)
```

## 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>function</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Specifies the path of the function.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

Calling an existing SurrealQL function:

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret"
    })
    .await?;

    let res: f32 = db.run("rand::float").await?;
    dbg!(res);
    Ok(())
}
```

User-defined functions can be called as well.

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    db.use_ns("ns").use_db("db").await?;

    db.query("DEFINE FUNCTION fn::return_one() -> int { RETURN 1 };")
        .await?;

    let res: i32 = db.run("fn::return_one").await?;
    dbg!(res);
    Ok(())
}
```

The return value of the `.run()` function can be deserialised in the same way as any other database function.

```rust
use serde::Deserialize;
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;

#[derive(Debug, Deserialize)]
struct Person {
    first_name: String,
    middle_name: String,
    last_name: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;
    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;
    db.use_ns("ns").use_db("db").await?;

    db.query(
        "DEFINE FUNCTION fn::j_jonah_jameson() -> object { RETURN 
        { 
          first_name: 'J',
          middle_name: 'Jonah',
          last_name: 'Jameson'
        } 
    };",
    )
    .await?;

    let res: Person = db.run("fn::j_jonah_jameson").await?;
    dbg!(res);
    Ok(())
}
```

## See also

* [.run() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.run)

---

Source: https://surrealdb.com/docs/reference/rust/methods/select

# select

The .select() method for the SurrealDB Rust SDK selects all or specific records from the database.

**3.x**

Selects all records in a table, or a specific record, from the database.

```rust title="Method Syntax"
db.select(resource)
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
// Select all records from a table
let people: Vec<Person> = db.select("person").await?;
// Select a specific record from a table
let person: Option<Person> = db.select(("person",
    "h5wxrf2ewk8xjxosxtyc")).await?;
```

## Example usage: Retrieve unique id of a record

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::types::{RecordId, SurrealValue};

#[derive(Debug, SurrealValue)]
struct Person {
    id: RecordId,
    name: String,
    age: u8,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    // Connect to the database
    let db = Surreal::new::<Ws>("localhost:8000").await?;

    // Sign in
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    // Select namespace and database to use
    db.use_ns("main").use_db("main").await?;

    // Create a person
    db.query("CREATE person:john SET name = 'John Doe', age = 25")
        .await?
        .check()?;

    // Query that person
    let john: Option<Person> = db.select(("person", "john")).await?;
    dbg!(john);

    Ok(())
}
```

## Restrict records with `.range()`

When selecting all records in a table (not a single record id), you can restrict results to a record-id range by chaining [`.range(...)`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Select.html#method.range-1). The argument implements [`Into<RecordIdKeyRange>`](https://docs.rs/surrealdb/latest/surrealdb/types/struct.RecordIdKeyRange.html): strings and tuples such as `"a"..="z"` or `(Bound::Included(x), Bound::Excluded(y))` express inclusive or exclusive bounds on the table’s record keys.

```rust
use surrealdb::{
    engine::any::connect,
    opt::Resource,
    types::{ToSql, Value},
};

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("ns").use_db("db").await.unwrap();

    db.query("CREATE person:alucard, person:plato, person:vlad")
        .await
        .unwrap();

    let res = db
        .select::<Value>(Resource::from("person"))
        .range("n"..="z")
        .await
        .unwrap()
        .to_sql();
    println!("{res:?}");
}
```

## Translated query
This function will run the following query in the database:

```surql
SELECT * FROM $resource;
```

## See also

* [.select() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.select)
* [`Select::range`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Select.html#method.range-1)
* [Live queries (`select().live()`)](/docs/reference/rust/methods/select-live.md); alternatively [`query()`](/docs/reference/rust/methods/query.md#live-select-stream) with `LIVE SELECT` and [`.stream()`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html#method.stream)

**2.x**

Selects all records in a table, or a specific record, from the database.

```rust title="Method Syntax"
db.select(resource)
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
// Select all records from a table
let people: Vec<Person> = db.select("person").await?;
// Select a specific record from a table
let person: Option<Person> = db.select(("person",
    "h5wxrf2ewk8xjxosxtyc")).await?;
```

## Example usage: retrieve unique id of a record

```rust
use serde::Deserialize;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::RecordId;
use surrealdb::Surreal;

#[derive(Debug, Deserialize)]
struct Person {
	id: RecordId,
	name: String,
	age: u8,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
	// Connect to the database
	let db = Surreal::new::<Ws>("localhost:8000").await?;

	// Sign in
	db.signin(Root {
		username: "root",
		password: "secret",
	})
	.await?;

	// Select namespace and database to use
	db.use_ns("namespace").use_db("database").await?;

	// Create a person
		db.query("CREATE person:john SET name = 'John Doe',
	    age = 25").await?.check()?;

	// Query that person
	let john: Option<Person> = db.select(("person", "john")).await?;
	dbg!(john);

	Ok(())
}
```

## Translated query
This function will run the following query in the database:

```surql
SELECT * FROM $resource;
```

## See also

* [.select() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.select)

---

Source: https://surrealdb.com/docs/reference/rust/methods/select-live

# select_live

The .select().live() methods for the SurrealDB Rust SDK initiate live queries for a live stream of notifications.

**3.x**

Initiate live queries for a live stream of notifications.

You can achieve the same live subscription by running `LIVE SELECT` inside [`query()`](/docs/reference/rust/methods/query.md#live-select-stream) and consuming results with [`IndexedResults::stream`](https://docs.rs/surrealdb/latest/surrealdb/struct.IndexedResults.html#method.stream). This can be useful when one or more live statements are part of a larger batch or if you prefer to use raw SurrealQL.

```rust title="Method Syntax"
db.select(resource).live()
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select.
            </td>
        </tr>
    </tbody>
</table>

## Example usage: Listen for live updates

The following example requires adding the `futures` crate with `cargo add futures` in order to work with the results of the async stream. Once run, the program will continue to wait and listen for events for the `person` table to happen.

```rust
use futures::StreamExt;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::{Notification, Surreal};
use surrealdb::types::{RecordId, SurrealValue};

#[derive(Debug, SurrealValue)]
struct Person {
    id: RecordId,
}

// Handle the result of the live query notification
fn handle(result: Result<Notification<Person>, surrealdb::Error>) {
    println!("Received notification: {:?}", result);
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".into(),
        password: "secret".into(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    db.query("DEFINE TABLE person").await?;

    // Select the "person" table and listen for live updates.
    let mut stream = db.select("person").live().await?;

    // Process updates as they come in.
    while let Some(result) = stream.next().await {
        // Do something with the notification
        handle(result);
    }
    Ok(())
}
```

Then connect to it using SurrealDB Studio or open a new terminal window with the following command.

```bash
surreal sql --user root --pass secret --pretty
```

You can then use queries like the following to work with some `person` records.

```surql
CREATE person;
UPDATE person SET is_nice_person = true;
DELETE person;
```

The following output will then show up in the terminal window running the Rust example.

```text
Received notification: Ok(Notification { query_id: Uuid(ab57b7f9-00b1-47e8-8f29-9d11d0572264), action: Create, data: Person { id: RecordId { table: Table("person"), key: String("pbpm2xhmaofyne383455") } } })
Received notification: Ok(Notification { query_id: Uuid(ab57b7f9-00b1-47e8-8f29-9d11d0572264), action: Update, data: Person { id: RecordId { table: Table("person"), key: String("pbpm2xhmaofyne383455") } } })
Received notification: Ok(Notification { query_id: Uuid(ab57b7f9-00b1-47e8-8f29-9d11d0572264), action: Delete, data: Person { id: RecordId { table: Table("person"), key: String("pbpm2xhmaofyne383455") } } })
```

## See also

* [.live() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Select.html#method.live)
* [`query()` with streaming `LIVE SELECT`](/docs/reference/rust/methods/query.md#live-select-stream)

**2.x**

Initiate live queries for a live stream of notifications.

```rust title="Method Syntax"
db.select(resource).live()
```

## 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or a record ID to select.
            </td>
        </tr>
    </tbody>
</table>

## Example usage: Listen for live updates

The following example requires adding the `futures` crate with `cargo add futures` in order to work with the results of the async stream. Once run, the program will continue to wait and listen for events for the `person` table to happen.

```rust
use futures::StreamExt;
use serde::Deserialize;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::{Notification, Surreal};
use surrealdb::RecordId;

#[derive(Debug, Deserialize)]
struct Person {
    id: RecordId,
}

// Handle the result of the live query notification
fn handle(result: Result<Notification<Person>, surrealdb::Error>) {
    println!("Received notification: {:?}", result);
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    // Select the "person" table and listen for live updates.
    let mut stream = db.select("person").live().await?;

    // Process updates as they come in.
    while let Some(result) = stream.next().await {
        // Do something with the notification
        handle(result);
    }
    Ok(())
}
```

Then connect to it using SurrealDB Studio or open a new terminal window with the following command.

```bash
surreal sql --namespace ns --database db --user root --pass secret --pretty
```

You can then use queries like the following to work with some `person` records.

```surql
CREATE person;
UPDATE person SET is_nice_person = true;
DELETE person;
```

The following output will then show up in the terminal window running the Rust example.

```text
Received notification: Ok(Notification { query_id: b55d31dc-e657-4a6b-a32b-f5abed4ef459, action: Create, data: Person { id: RecordId { table: "person", key: String("334mabva9ibitsypabm5") } } })
Received notification: Ok(Notification { query_id: b55d31dc-e657-4a6b-a32b-f5abed4ef459, action: Update, data: Person { id: RecordId { table: "person", key: String("334mabva9ibitsypabm5") } } })
Received notification: Ok(Notification { query_id: b55d31dc-e657-4a6b-a32b-f5abed4ef459, action: Delete, data: Person { id: RecordId { table: "person", key: String("334mabva9ibitsypabm5") } } })
```

## See also

* [.live() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Select.html#method.live)

---

Source: https://surrealdb.com/docs/reference/rust/methods/set

# set

The .set() method for the SurrealDB Rust SDK assigns a value as a parameter for this connection.

**3.x**

Assigns a value as a parameter for this connection.

```rust title="Method Syntax"
db.set(key, value)
```

This is equivalent to using a [`LET`](/docs/reference/query-language/statements/let.md) statement in SurrealQL, such as this one.

```surql
LET $name = {
    first: "Tobie",
    last: "Morgan Hitchcock",
};
```

## 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>key</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>val</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns the value to the variable name.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Name {
    first: String,
    last: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    // Assign the variable on the connection
    db.set(
        "name",
        Name {
            first: "Tobie".to_string(),
            last: "Morgan Hitchcock".to_string(),
        },
    )
    .await?;
    // Use the variable in a subsequent query
    let create = db.query("CREATE person SET name = $name").await?;
    dbg!(create);
    // Use the variable in a subsequent query
    let select = db
        .query("SELECT * FROM person WHERE name.first = $name.first")
        .await?;
    dbg!(select);
    Ok(())
}
```

## See also

* [.set() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.set)

**2.x**

Assigns a value as a parameter for this connection.

```rust title="Method Syntax"
db.set(key, value)
```

This is equivalent to using a [`LET`](/docs/reference/query-language/statements/let.md) statement in SurrealQL, such as this one.

```surql
LET $name = {
    first: "Tobie",
    last: "Morgan Hitchcock",
};
```

## 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>key</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>val</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Assigns the value to the variable name.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
use serde::Serialize;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

#[derive(Debug, Serialize)]
struct Name<'a> {
    first: &'a str,
    last: &'a str,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    // Assign the variable on the connection
    db.set(
        "name",
        Name {
            first: "Tobie",
            last: "Morgan Hitchcock",
        },
    )
    .await?;
    // Use the variable in a subsequent query
    let create = db.query("CREATE person SET name = $name").await?;
    dbg!(create);
    // Use the variable in a subsequent query
    let select = db
        .query("SELECT * FROM person WHERE name.first = $name.first")
        .await?;
    dbg!(select);
    Ok(())
}
```

## See also

* [.set() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.set)

---

Source: https://surrealdb.com/docs/reference/rust/methods/signin

# signin

The .signin() method for the SurrealDB Rust SDK signs in to a specific access method.

**3.x**

Signs in to a specific access method for an already signed up record user.

```rust title="Method Syntax"
db.signin(credentials)
```

## 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</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Variables used in a signin query.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
// Use the following statement to set up the 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
// ;

use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Record;
use surrealdb::Surreal;
use surrealdb::types::SurrealValue;

#[derive(SurrealValue)]
struct Credentials {
    email: String,
    pass: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.use_ns("main").use_db("main").await?;

    let token = db
        .signin(Record {
            namespace: "main".to_string(),
            database: "main".to_string(),
            access: "account".to_string(),
            params: Credentials {
                email: "info@surrealdb.com".to_string(),
                pass: "123456".to_string(),
            },
        })
        .await?;

    // ⚠️: It is important to note that the token should be handled securely and protected from unauthorized access.
    let jwt = token.access.as_insecure_token();
    dbg!(jwt);
    Ok(())
}
```

## See also

* [.signin() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signin)

**2.x**

Signs in to a specific access method for an already signed up record user.

```rust title="Method Syntax"
db.signin(credentials)
```

## 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</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Variables used in a signin query.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
// Use the following statement to set up the 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
// ;

use serde::Serialize;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Record;
use surrealdb::Surreal;

#[derive(Serialize)]
struct Credentials<'a> {
    email: &'a str,
    pass: &'a str,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    let jwt = db
        .signin(Record {
            namespace: "main",
            database: "main",
            access: "account",
            params: Credentials {
                email: "info@surrealdb.com",
                pass: "123456",
            },
        })
        .await?;

    // ⚠️: It is important to note that the token should be handled securely and protected from unauthorized access.
    let token = jwt.as_insecure_token();
    dbg!(token);
    Ok(())
}
```

## See also

* [.signin() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signin)

---

Source: https://surrealdb.com/docs/reference/rust/methods/signup

# signup

The .signup() method for the SurrealDB Rust SDK signs up to a specific access method.

**3.x**

Signs up as a record user (formerly known as a scope user) to a specific access method.

```rust title="Method Syntax"
db.signup(credentials)
```

## 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</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Variables used in a signup query.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
// Use the following statement to set up the 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
// ;

use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Record;
use surrealdb::types::SurrealValue;

#[derive(SurrealValue)]
struct Credentials {
    email: String,
    pass: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    let jwt = db
        .signup(Record {
            namespace: "main".to_string(),
            database: "main".to_string(),
            access: "account".to_string(),
            params: Credentials {
                email: "info@surrealdb.com".to_string(),
                pass: "123456".to_string(),
            },
        })
        .await?;

    // ⚠️: It is important to note that the token should be handled securely and protected from unauthorized access.
    let token = jwt.access.as_insecure_token();
    dbg!(token);
    Ok(())
}
```

## See also

* [.signup() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signup)

**2.x**

Signs up as a record user (formerly known as a scope user) to a specific access method.

```rust title="Method Syntax"
db.signup(credentials)
```

## 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</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Variables used in a signup query.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```rust
// Use the following statement to set up the 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
// ;

use serde::Serialize;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Record;
use surrealdb::Surreal;

#[derive(Serialize)]
struct Credentials<'a> {
    email: &'a str,
    pass: &'a str,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    let jwt = db
        .signup(Record {
            namespace: "main",
            database: "main",
            access: "account",
            params: Credentials {
                email: "info@surrealdb.com",
                pass: "123456",
            },
        })
        .await?;

    // ⚠️: It is important to note that the token should be handled securely and protected from unauthorized access.
    let token = jwt.as_insecure_token();
    dbg!(token);
    Ok(())
}
```

## See also

* [.signup() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.signup)

---

Source: https://surrealdb.com/docs/reference/rust/methods/unset

# unset

The .unset() method for the SurrealDB Rust SDK removes a parameter from the connection.

**3.x**

Removes a parameter from this connection.

```rust title="Method Syntax"
db.unset(key)
```

## Example usage

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Name {
    first: String,
    last: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    // Assign the variable on the connection
    db.set(
        "name",
        Name {
            first: "Tobie".to_string(),
            last: "Morgan Hitchcock".to_string(),
        },
    )
    .await?;
    dbg!(db.query("$name").await?);

    db.unset("name").await?;
    // Aaaand now it's gone
    dbg!(db.query("$name").await?);
    Ok(())
}
```

## See also

* [.unset() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.unset)

**2.x**

Removes a parameter from this connection.

```rust title="Method Syntax"
db.unset(key)
```

## Example usage

```rust
use serde::Serialize;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

#[derive(Debug, Serialize)]
struct Name<'a> {
    first: &'a str,
    last: &'a str,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    // Assign the variable on the connection
    db.set(
        "name",
        Name {
            first: "Tobie",
            last: "Morgan Hitchcock",
        },
    )
    .await?;
    dbg!(db.query("RETURN $name").await?);

    db.unset("name").await?;
    // Aaaand now it's gone
    dbg!(db.query("RETURN $name").await?);
    Ok(())
}
```

## See also

* [.unset() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.unset)

---

Source: https://surrealdb.com/docs/reference/rust/methods/update

# update

The .update() method for the SurrealDB Rust SDK updates all or specific records in the database.

**3.x**

Update all or specific records in the database.

```rust title="Method Syntax"
db.update(resource)
```

The `.update()` method is followed by second method that refers to the type of update to use: an update with `.content()`, `.merge()`, or `.patch()`.

## `.update().content()`

Updates all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.update(resource).content(data)
```

> [!NOTE]
> This function replaces the current document / record data with the specified data.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue, Default)]
struct Person {
    name: Option<String>,
    company: Option<String>,
    settings: Option<Settings>,
}

#[derive(Debug, SurrealValue)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Debug, SurrealValue)]
struct Company {
    company: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    db.query("CREATE person:tobie, person:jaime").await?;

    // Update all records in a table
    let people: Vec<Person> = db
        .update("person")
        .content(Company {
            company: "SurrealDB".into(),
        })
        .await?;
    dbg!(people);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
UPDATE $resource CONTENT $data;
```

### Restrict records with `.range()`

Table-scoped updates accept [`.range(...)`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Update.html#method.range-1) before `.content`, `.merge`, or `.patch`, limiting which record IDs are affected:

```rust
use surrealdb::{
    engine::any::connect,
    opt::Resource,
    types::{SurrealValue, ToSql, Value},
};

#[derive(SurrealValue)]
struct Content {
    second_half_of_alphabet: bool,
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("ns").use_db("db").await.unwrap();

    db.query("CREATE person:alucard, person:plato, person:vlad")
        .await
        .unwrap();

    let res = db
        .update::<Value>(Resource::from("person"))
        .range("n"..="z")
        .content(Content {
            second_half_of_alphabet: true,
        })
        .await
        .unwrap()
        .to_sql();
    println!("{res:?}");
}
```

## `.update().merge()`

Modifies all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.update(resource).merge(data)
```

> [!NOTE]
> This function merges the current document / record data with the specified data.

### 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Type">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue, Default)]
struct Person {
    name: String,
    company: Option<String>,
    active: Option<bool>,
    marketing: Option<bool>,
}

#[derive(Debug, SurrealValue)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Debug, SurrealValue)]
struct Company {
    company: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    db.query("CREATE person:tobie SET name = 'Tobie'; CREATE person:jaime SET name = 'jaime';")
        .await?;

    // Update all records in a table
    let people: Vec<Person> = db
        .update("person")
        .merge(Company {
            company: "SurrealDB".into(),
        })
        .await?;
    dbg!(people);

    // Update a single record
    let person: Option<Person> = db
        .update(("person", "jaime"))
        .merge(Settings {
            active: true,
            marketing: true,
        })
        .await?;
    dbg!(person);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
UPDATE $resource MERGE $data;
```

## `.update().patch()`

Applies JSON Patch changes to all records, or a specific record, in the database.

```rust title="Method Syntax"
db.update(resource).patch(patch_op)
```

> [!NOTE]
> This function patches the current document / record data with the specified JSON Patch data.

### 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to modify.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The JSON Patch data with which to modify the records.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

The `.patch()` method uses a struct called a `PatchOp` that contains the four methods `add()`, `change()`, `remove()`, and `replace()`. Each of these methods takes different arguments depending on the operation. For example, `PathOp::remove()` only takes a single argument (a path), while `PathOp::replace()` takes a second value for the replacement value.

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::PatchOp;
use surrealdb::opt::auth::Root;
use surrealdb::types::{Datetime, SurrealValue};

#[derive(Debug, SurrealValue, Default)]
struct Person {
    name: String,
    company: Option<String>,
    settings: Option<Settings>,
    created_at: Option<Datetime>,
    tags: Option<Vec<String>>,
}

#[derive(Debug, SurrealValue)]
struct Settings {
    active: bool,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    db.query(
        "
        CREATE person:tobie SET name = 'Tobie', company = 'SurrealDB'; 
        CREATE person:jaime SET name = 'jaime', company = 'SurrealDB';",
    )
    .await?;

    // Update all records in a table
    let people: Vec<Person> = db
        .update("person")
        .patch(PatchOp::replace("/created_at", Datetime::default()))
        .await?;
    dbg!(people);

    // Update a record with a specific ID
    let person: Option<Person> = db
        .update(("person", "tobie"))
        .patch(PatchOp::replace("/settings/active", false))
        .patch(PatchOp::add("/tags", ["developer", "engineer"]))
        .patch(PatchOp::remove("/company"))
        .await?;
    dbg!(person);
    Ok(())
}
```

#### Multiple operations with `PatchOps`

Chaining `.patch()` once per operation works, but each call adds a level of nesting to the builder. [`PatchOps`](https://docs.rs/surrealdb/latest/surrealdb/opt/struct.PatchOps.html) collects the operations into a single value instead, and carries the same four methods so that they can be chained on one another.

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::PatchOps;
use surrealdb::types::{Datetime, SurrealValue};

#[derive(Debug, SurrealValue, Default)]
struct Person {
    name: String,
    company: Option<String>,
    settings: Option<Settings>,
    created_at: Option<Datetime>,
    tags: Option<Vec<String>>,
}

#[derive(Debug, SurrealValue)]
struct Settings {
    active: bool,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("memory").await?;
    db.use_ns("main").use_db("main").await?;

    db.query(
        "CREATE person:tobie SET name = 'Tobie', company = 'SurrealDB', settings = { active: true }",
    )
    .await?
    .check()?;

    let person: Option<Person> = db
        .update(("person", "tobie"))
        .patch(
            PatchOps::new()
                .replace("/settings/active", false)
                .add("/tags", ["developer", "engineer"])
                .remove("/company"),
        )
        .await?;
    dbg!(person);
    Ok(())
}
```

`PatchOps` is built up in order and applied in order, so an operation can depend on one before it. A single `PatchOp` still works wherever `PatchOps` is expected, as `.patch()` takes anything that converts into `PatchOps`, including a `Vec<PatchOp>`.

> [!NOTE]
> Removing a field the target struct declares as non-optional will make the response fail to deserialise. Take the result as a `Value`, or declare the field as an `Option`, when a patch removes it.

### Translated query
This function will run the following query in the database:

```surql
UPDATE $resource PATCH $data;
```

### See also

* [.update() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.update)

**2.x**

Update all or specific records in the database.

```rust title="Method Syntax"
db.update(resource)
```

The `.update()` method is followed by second method that refers to the type of update to use: an update with `.content()`, `.merge()`, or `.patch()`.

## `.update().content()`

Updates all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.update(resource).content(data)
```

> [!NOTE]
> This function replaces the current document / record data with the specified data.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

#[derive(Debug, Serialize, Deserialize, Default)]
struct Person {
    name: Option<String>,
    company: Option<String>,
    settings: Option<Settings>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Debug, Serialize)]
struct Company {
    company: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    db.query("CREATE person:tobie, person:jaime").await?;

    // Update all records in a table
    let people: Vec<Person> = db
        .update("person")
        .content(Company {
            company: "SurrealDB".into(),
        })
        .await?;
    dbg!(people);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
UPDATE $resource CONTENT $data;
```

## `.update().merge()`

Modifies all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.update(resource).merge(data)
```

> [!NOTE]
> This function merges the current document / record data with the specified data.

### 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Type">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage
```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

#[derive(Debug, Serialize, Deserialize, Default)]
struct Person {
    name: String,
    company: Option<String>,
    active: Option<bool>,
    marketing: Option<bool>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Debug, Serialize)]
struct Company {
    company: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    db.query("CREATE person:tobie SET name = 'Tobie'; CREATE person:jaime SET name = 'jaime';")
        .await?;

    // Update all records in a table
    let people: Vec<Person> = db
        .update("person")
        .merge(Company {
            company: "SurrealDB".into(),
        })
        .await?;
    dbg!(people);

    // Update a single record
    let person: Option<Person> = db
        .update(("person", "jaime"))
        .merge(Settings {
            active: true,
            marketing: true,
        })
        .await?;
    dbg!(person);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
UPDATE $resource MERGE $data;
```

## `.update().patch()`

Applies JSON Patch changes to all records, or a specific record, in the database.

```rust title="Method Syntax"
db.update(resource).patch(patch_op)
```

> [!NOTE]
> This function patches the current document / record data with the specified JSON Patch data.

### 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to modify.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The JSON Patch data with which to modify the records.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

The `.patch()` method uses a struct called a `PatchOp` that contains the four methods `add()`, `change()`, `remove()`, and `replace()`. Each of these methods takes different arguments depending on the operation. For example, `PathOp::remove()` only takes a single argument (a path), while `PathOp::replace()` takes a second value for the replacement value.

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::opt::PatchOp;
use surrealdb::sql::Datetime;
use surrealdb::Surreal;

#[derive(Debug, Serialize, Deserialize, Default)]
struct Person {
    name: String,
    company: Option<String>,
    settings: Option<Settings>,
    created_at: Option<Datetime>,
    tags: Option<Vec<String>>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    db.query(
        "
        CREATE person:tobie SET name = 'Tobie', company = 'SurrealDB'; 
        CREATE person:jaime SET name = 'jaime', company = 'SurrealDB';",
    )
    .await?;

    // Update all records in a table
    let people: Vec<Person> = db
        .update("person")
        .patch(PatchOp::replace("/created_at", Datetime::default()))
        .await?;
    dbg!(people);

    // Update a record with a specific ID
    let person: Option<Person> = db
        .update(("person", "tobie"))
        .patch(PatchOp::replace("/settings/active", false))
        .patch(PatchOp::add("/tags", &["developer", "engineer"]))
        .patch(PatchOp::remove("/company"))
        .await?;
    dbg!(person);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
UPDATE $resource PATCH $data;
```

### See also

* [.update() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.update)

---

Source: https://surrealdb.com/docs/reference/rust/methods/upsert

# upsert

The .upsert() method for the SurrealDB Rust SDK upserts all or specific records in a table.

**3.x**

Upserts all records in a table, or a specific record.

```rust title="Method Syntax"
db.upsert(resource)
```

The `.upsert()` method is followed by second method that refers to the type of upsert to use: an upsert with `.content()`, `.merge()`, or `.patch()`.

## `.upsert().content()`

Upserts all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.upsert(resource).content(data)
```

> [!NOTE]
> This function replaces the current document / record data with the specified data.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::types::{RecordId, SurrealValue};

#[derive(Debug, SurrealValue)]
struct Person {
    id: RecordId,
    name: Option<String>,
    company: Option<String>,
    settings: Option<Settings>,
}

#[derive(Debug, SurrealValue)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Debug, SurrealValue)]
struct Company {
    company: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    // upsert one record in a table
    let person: Option<Person> = db
        .upsert(("person", "jaime"))
        .content(Company {
            company: "SurrealDB".into(),
        })
        .await?;
    dbg!(person);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
upsert $resource CONTENT $data;
```

### Restrict records with `.range()`

For upserts against a **whole table**, use [`.range(...)`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Upsert.html#method.range-1) together with `.content`, `.merge`, or `.patch` if only keys inside the range should be considered:

```rust
use surrealdb::{
    engine::any::connect,
    opt::Resource,
    types::{SurrealValue, ToSql, Value},
};

#[derive(SurrealValue)]
struct Content {
    second_half_of_alphabet: bool,
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("ns").use_db("db").await.unwrap();

    db.query("CREATE person:alucard, person:plato, person:vlad")
        .await
        .unwrap();

    let res = db
        .upsert::<Value>(Resource::from("person"))
        .range("n"..="z")
        .content(Content {
            second_half_of_alphabet: true,
        })
        .await
        .unwrap()
        .to_sql();
    println!("{res:?}");
}
```

## `.upsert().merge()`

Modifies all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.upsert(resource).merge(data)
```

> [!NOTE]
> This function merges the current document / record data with the specified data.

### 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Type">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue, Default)]
struct Person {
    name: String,
    company: Option<String>,
    active: Option<bool>,
    marketing: Option<bool>,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    // upsert a single record
    let person: Option<Person> = db
        .upsert(("person", "tobie"))
        .merge(Person {
            name: "Tobie".into(),
            ..Default::default()
        })
        .await?;

    dbg!(person);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
upsert $resource MERGE $data;
```

## `.upsert().patch()`

Applies JSON Patch changes to all records, or a specific record, in the database.

```rust title="Method Syntax"
db.upsert(resource).patch(patch_op)
```

> [!NOTE]
> This function patches the current document / record data with the specified JSON Patch data.

### 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to modify.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The JSON Patch data with which to modify the records.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

The `.patch()` method uses a struct called a `PatchOp` that contains the four methods `add()`, `change()`, `remove()`, and `replace()`. Each of these methods takes different arguments depending on the operation. For example, `PathOp::remove()` only takes a single argument (a path), while `PathOp::replace()` takes a second value for the replacement value.

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::PatchOp;
use surrealdb::opt::auth::Root;
use surrealdb::types::{Datetime, SurrealValue};

#[derive(Debug, SurrealValue, Default)]
struct Person {
    name: String,
    company: Option<String>,
    settings: Option<Settings>,
    created_at: Option<Datetime>,
    tags: Option<Vec<String>>,
}

#[derive(Debug, SurrealValue)]
struct Settings {
    active: bool,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    // upsert a record with a specific ID
    let person: Option<Person> = db
        .upsert(("person", "tobie"))
        .patch(PatchOp::replace("/name", "Tobie"))
        .patch(PatchOp::replace("/settings/active", false))
        .patch(PatchOp::add("/tags", ["developer", "engineer"]))
        .patch(PatchOp::remove("/company"))
        .await?;
    dbg!(person);
    Ok(())
}
```

#### Multiple operations with `PatchOps`

Chaining `.patch()` once per operation works, but each call adds a level of nesting to the builder. [`PatchOps`](https://docs.rs/surrealdb/latest/surrealdb/opt/struct.PatchOps.html) collects the operations into a single value instead, and carries the same four methods so that they can be chained on one another.

```rust
use surrealdb::engine::any::connect;
use surrealdb::opt::PatchOps;
use surrealdb::types::{Datetime, SurrealValue};

#[derive(Debug, SurrealValue, Default)]
struct Person {
    name: String,
    company: Option<String>,
    settings: Option<Settings>,
    created_at: Option<Datetime>,
    tags: Option<Vec<String>>,
}

#[derive(Debug, SurrealValue)]
struct Settings {
    active: bool,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("memory").await?;
    db.use_ns("main").use_db("main").await?;

    db.query(
        "CREATE person:tobie SET name = 'Tobie', company = 'SurrealDB', settings = { active: true }",
    )
    .await?
    .check()?;

    let person: Option<Person> = db
        .upsert(("person", "tobie"))
        .patch(
            PatchOps::new()
                .replace("/settings/active", false)
                .add("/tags", ["developer", "engineer"])
                .remove("/company"),
        )
        .await?;
    dbg!(person);
    Ok(())
}
```

`PatchOps` is built up in order and applied in order, so an operation can depend on one before it. A single `PatchOp` still works wherever `PatchOps` is expected, as `.patch()` takes anything that converts into `PatchOps`, including a `Vec<PatchOp>`.

> [!NOTE]
> Removing a field the target struct declares as non-optional will make the response fail to deserialise. Take the result as a `Value`, or declare the field as an `Option`, when a patch removes it.

### Translated query
This function will run the following query in the database:

```surql
UPSERT $resource PATCH $data;
```

### See also

* [.upsert() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.upsert)

**2.x**

Upserts all records in a table, or a specific record.

```rust title="Method Syntax"
db.upsert(resource)
```

The `.upsert()` method is followed by second method that refers to the type of upsert to use: an upsert with `.content()`, `.merge()`, or `.patch()`.

## `.upsert().content()`

Upserts all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.upsert(resource).content(data)
```

> [!NOTE]
> This function replaces the current document / record data with the specified data.

### Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::RecordId;
use surrealdb::Surreal;

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    id: RecordId,
    name: Option<String>,
    company: Option<String>,
    settings: Option<Settings>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[derive(Debug, Serialize)]
struct Company {
    company: String,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    // upsert one record in a table
    let person: Option<Person> = db
        .upsert(("person", "jaime"))
        .content(Company {
            company: "SurrealDB".into(),
        })
        .await?;
    dbg!(person);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
upsert $resource CONTENT $data;
```

## `.upsert().merge()`

Modifies all records in a table, or a specific record, in the database.

```rust title="Method Syntax"
db.upsert(resource).merge(data)
```

> [!NOTE]
> This function merges the current document / record data with the specified data.

### 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to create.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Type">
                <code>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The document / record data to insert.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;

#[derive(Debug, Serialize, Deserialize, Default)]
struct Person {
    name: String,
    company: Option<String>,
    active: Option<bool>,
    marketing: Option<bool>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
    marketing: bool,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    // upsert a single record
    let person: Option<Person> = db
        .upsert(("person", "tobie"))
        .merge(Person {
            name: "Tobie".into(),
            ..Default::default()
        })
        .await?;

    dbg!(person);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
upsert $resource MERGE $data;
```

## `.upsert().patch()`

Applies JSON Patch changes to all records, or a specific record, in the database.

```rust title="Method Syntax"
db.upsert(resource).patch(patch_op)
```

> [!NOTE]
> This function patches the current document / record data with the specified JSON Patch data.

### 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>resource</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The table name or the specific record ID to modify.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>data</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The JSON Patch data with which to modify the records.
            </td>
        </tr>
    </tbody>
</table>

### Example usage

The `.patch()` method uses a struct called a `PatchOp` that contains the four methods `add()`, `change()`, `remove()`, and `replace()`. Each of these methods takes different arguments depending on the operation. For example, `PathOp::remove()` only takes a single argument (a path), while `PathOp::replace()` takes a second value for the replacement value.

```rust
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::opt::PatchOp;
use surrealdb::sql::Datetime;
use surrealdb::Surreal;

#[derive(Debug, Serialize, Deserialize, Default)]
struct Person {
    name: String,
    company: Option<String>,
    settings: Option<Settings>,
    created_at: Option<Datetime>,
    tags: Option<Vec<String>>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Settings {
    active: bool,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root",
        password: "secret",
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    // upsert a record with a specific ID
    let person: Option<Person> = db
        .upsert(("person", "tobie"))
        .patch(PatchOp::replace("/settings/active", false))
        .patch(PatchOp::add("/tags", &["developer", "engineer"]))
        .patch(PatchOp::remove("/company"))
        .await?;
    dbg!(person);
    Ok(())
}
```

### Translated query
This function will run the following query in the database:

```surql
UPSERT $resource PATCH $data;
```

### See also

* [.upsert() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.upsert)

---

Source: https://surrealdb.com/docs/reference/rust/methods/use

# use_ns and use_db

The .use_ns() and .use_db() methods for the SurrealDB Rust SDK switch to a specific namespace and database.

Switch to a specific namespace and database.

```rust title="Method Syntax"
db.use_ns(ns).use_db(db)
```

## Arguments

<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>ns</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Switches to a specific namespace.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Argument">
                <code>db</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Switches to a specific database.
            </td>
        </tr>
    </tbody>
</table>

## Example usage
```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::Surreal;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    db.use_ns("main").use_db("main").await?;
    Ok(())
}
```

## See also

* [.use_db() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.use_db)
* [.use_ns() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.use_ns)

---

Source: https://surrealdb.com/docs/reference/rust/methods/use-defaults

# use_defaults

The .use_defaults() method for the SurrealDB Rust SDK selects the default namespace and database for this connection, if the server and client are configured to provide one.

Selects the namespace and database set by [`DEFINE CONFIG DEFAULT`](/docs/reference/query-language/statements/define/config.md). If no default is configured, the session is left unchanged.

```rust title="Method Syntax"
db.use_defaults().await?
```

This complements [`use_ns()` and `use_db()`](/docs/reference/rust/methods/use.md) when you want the configured default instead of a specific pair.

The defaults only fill in what is missing. A session that has already selected a namespace - from a token, for example - keeps that selection.

## See also

* [`.use_defaults()` on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.use_defaults)

---

Source: https://surrealdb.com/docs/reference/rust/methods/version

# version

The .version() method for the SurrealDB Rust SDK returns the version of the server.

Returns the version of the server.

```rust title="Method Syntax"
db.version()
```

## Example usage

```rust
use surrealdb::engine::any::connect;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    println!("{:?}", db.version().await?);
    Ok(())
}
```

The function resolves to a [`Version`](https://docs.rs/semver/latest/semver/struct.Version.html) struct as defined in the `semver` crate. Sample output:

```text
Version { major: 3, minor: 0, patch: 1 }
```

## See also

* [.version() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.version)

---

Source: https://surrealdb.com/docs/reference/rust/methods/wait-for

# wait_for

The .wait_for() method for the SurrealDB Rust SDK waits for the selected event to happen before proceeding.

Wait for the selected event to happen before proceeding.

```rust title="Method Syntax"
db.wait_for(event)
```

## Arguments
<table>
    <thead>
        <tr>
            <th colspan="2" scope="col">Argument</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="Argument">
                <code>event</code>
            </td>
            <td colspan="2" scope="row" data-label="Type">
                <code>WaitFor</code>
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The event to wait for before proceeding.
            </td>
        </tr>
    </tbody>
</table>

`WaitFor` is a simple enum with two variants representing the event to wait for.

```rust
pub enum WaitFor {
    Connection,
    Database,
}
```

## Example usage

The following test from the source code demonstrates the behaviour of the `.wait_for_()` method in a variety of situations.

```rust
use std::task::Poll;

use surrealdb::engine::remote::ws::{Client, Ws};
use surrealdb::opt::auth::Root;
use surrealdb::opt::WaitFor::{Connection, Database};
use surrealdb::Surreal;

use futures::poll;
use std::pin::pin;

#[tokio::main]
async fn main() -> surrealdb::Result<()> {

    // Create an unconnected client
    // At this point wait_for should continue to wait for both the connection and database selection.
    let db: Surreal<Client> = Surreal::init();
    assert_eq!(poll!(pin!(db.wait_for(Connection))), Poll::Pending);
    assert_eq!(poll!(pin!(db.wait_for(Database))), Poll::Pending);

    // Connect to the server
    // The connection event should fire and allow wait_for to return immediately when waiting for a connection.
    // When waiting for a database to be selected, it should continue waiting.
    db.connect::<Ws>("127.0.0.1:8000").await.unwrap();
    assert_eq!(poll!(pin!(db.wait_for(Connection))), Poll::Ready(()));
    assert_eq!(poll!(pin!(db.wait_for(Database))), Poll::Pending);

    // Sign into the server
    // At this point the connection has already been established but the database hasn't been selected yet.
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await
    .unwrap();
    assert_eq!(poll!(pin!(db.wait_for(Connection))), Poll::Ready(()));
    assert_eq!(poll!(pin!(db.wait_for(Database))), Poll::Pending);

    // Selecting a namespace shouldn't fire the database selection event.
    db.use_ns("namespace").await.unwrap();
    assert_eq!(poll!(pin!(db.wait_for(Connection))), Poll::Ready(()));
    assert_eq!(poll!(pin!(db.wait_for(Database))), Poll::Pending);

    // Select the database to use
    // Both the connection and database events have fired, wait_for should return immediately for both.
    db.use_db("database").await.unwrap();
    assert_eq!(poll!(pin!(db.wait_for(Connection))), Poll::Ready(()));
    assert_eq!(poll!(pin!(db.wait_for(Database))), Poll::Ready(()));
    Ok(())
}
```

## See also

* [.wait_for() method on Docs.rs](https://docs.rs/surrealdb/latest/surrealdb/struct.Surreal.html#method.wait_for)

---

Source: https://surrealdb.com/docs/reference/swift

# Swift SDK

The official SurrealDB SDK for Swift. Simple and advanced querying of a remote database from Apple-platform and server-side Swift applications.

The SurrealDB SDK for Swift enables you to interact with SurrealDB from native Apple-platform applications (iOS, macOS, tvOS, watchOS and visionOS) as well as server-side Swift, allowing you to integrate SurrealDB into your app or backend and serve dynamic content to your users. You can use the Swift SDK to connect over HTTP or WebSocket, execute queries, manage data with type-safe CRUD via the `@SurrealModel` macro and query DSL, authenticate to the database, build user signup and signin functionality, and subscribe to data changes with live queries delivered as an `AsyncStream`.

> [!IMPORTANT]
> The SDK requires Swift `6.1` or later and works with SurrealDB `v3.0.0` or higher.
> Supported platforms are iOS 17+, macOS 14+, tvOS 17+, watchOS 10+ and visionOS 1+.

> [!NOTE]
> The SDK is distributed via [Swift Package Manager](/docs/reference/swift/installation.md) from the [surrealdb.swift](https://github.com/surrealdb/surrealdb.swift) repository, and will also be available on [Swift Package Index](https://swiftpackageindex.com).

## Getting started

- [Installation](/docs/reference/swift/installation.md) - Install the SDK and add it to your project.

- [Getting started guide](/docs/languages/swift.md) - Connect to SurrealDB and run your first queries.

## Learn

- [Concepts](/docs/reference/swift/concepts/connecting.md) - Guides for connecting, authenticating, querying, and working with data.

- [API Reference](/docs/reference/swift/methods.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.swift](https://github.com/surrealdb/surrealdb.swift) 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.swift)
- [Swift Package Index](https://swiftpackageindex.com)

---

Source: https://surrealdb.com/docs/reference/swift/concepts/authentication

# Authentication

Learn how to sign in, sign up, authenticate and resume sessions with the SurrealDB Swift SDK.

SurrealDB supports a number of methods for authenticating users and securing the database. The Swift SDK exposes these through [`signin`](/docs/reference/swift/methods/signin.md), [`signup`](/docs/reference/swift/methods/signup.md), [`authenticate`](/docs/reference/swift/methods/authenticate.md) and [`invalidate`](/docs/reference/swift/methods/invalidate.md).

## Signing in

The `signin` method accepts a credentials enum describing the level you wish to authenticate at.

### Root user

```swift
let tokens = try await client.signin(.root(username: "root", password: "secret"))
```

### Namespace user

```swift
let tokens = try await client.signin(.namespace(
    namespace: "myapp",
    username: "ns_user",
    password: "secret"
))
```

### Database user

```swift
let tokens = try await client.signin(.database(
    namespace: "myapp",
    database: "mydb",
    username: "db_user",
    password: "secret"
))
```

### Record access with variables

```swift
let tokens = try await client.signin(.accessVariables(
    namespace: "myapp",
    database: "mydb",
    access: "account",
    variables: ["email": .string("user@example.com"), "pass": .string("secret")]
))
```

### Bearer token access

```swift
let tokens = try await client.signin(.accessBearer(
    namespace: "myapp",
    database: "mydb",
    access: "account",
    key: "bearer-token-value"
))
```

## Signing up

New record-access users sign up with `signup`:

```swift
let tokens = try await client.signup(.accessRecord(
    namespace: "myapp",
    database: "mydb",
    access: "account",
    variables: ["email": .string("new@example.com"), "pass": .string("secret")]
))
```

## Resuming a session

You can re-authenticate an existing client with a previously issued access token:

```swift
try await client.authenticate(tokens.access)
```

Alternatively, provide a token when constructing the client through a `SessionContext`:

```swift
let client = try SurrealHTTPClient(
    endpoint: "http://localhost:8000",
    session: SessionContext(
        namespace: "myapp",
        database: "mydb",
        accessToken: "existing-jwt"
    )
)
```

## Invalidating a session

To clear the current authentication state:

```swift
try await client.invalidate()
```

---

Source: https://surrealdb.com/docs/reference/swift/concepts/connecting

# Connecting to SurrealDB

Learn how to connect to SurrealDB over HTTP or WebSocket and configure the client in the Swift SDK.

The Swift SDK provides two transports: an HTTP client and a WebSocket client. Both share the same querying and CRUD API; the WebSocket client additionally supports [live queries](/docs/reference/swift/concepts/live-queries.md).

## HTTP client

`SurrealHTTPClient` connects over HTTP and is the simplest choice for request/response workloads.

```swift
let client = try SurrealHTTPClient(endpoint: "http://localhost:8000")
try await client.connect()
defer { Task { await client.close() } }
```

## WebSocket client

`SurrealWebSocketClient` connects over WebSocket and is required for [live queries](/docs/reference/swift/concepts/live-queries.md). It can also automatically reconnect when a connection is dropped.

```swift
let client = try SurrealWebSocketClient(
    endpoint: "ws://localhost:8000",
    websocketOptions: SurrealWebSocketOptions(
        reconnectEnabled: true,
        maxReconnectAttempts: 8,
        reconnectBaseDelay: 0.5
    )
)
try await client.connect()
defer { Task { await client.close() } }
```

## Selecting a namespace and database

Once connected, select the namespace and database your queries should run against:

```swift
try await client.use(namespace: "myapp", database: "mydb")
```

## Configuration options

### `SurrealClientOptions`

Common options shared by both the HTTP and WebSocket clients:

```swift
SurrealClientOptions(
    requestTimeout: 20,
    pingInterval: 30
)
```

### `SurrealWebSocketOptions`

Reconnection behaviour for the WebSocket client:

```swift
SurrealWebSocketOptions(
    reconnectEnabled: true,
    maxReconnectAttempts: 8,
    reconnectBaseDelay: 0.5
)
```

## Closing the connection

Always close the client when you are done to release the underlying connection:

```swift
await client.close()
```

---

Source: https://surrealdb.com/docs/reference/swift/concepts/live-queries

# Live queries

Subscribe to real-time data changes using live queries and AsyncStream in the SurrealDB Swift SDK.

Live queries let you subscribe to changes on a table and receive events in real time. They require the [WebSocket client](/docs/reference/swift/concepts/connecting.md#websocket-client) and are delivered as an `AsyncStream<LiveEvent<T>>`.

## Starting a live query

```swift
let client = try SurrealWebSocketClient(endpoint: "ws://localhost:8000")
try await client.connect()
_ = try await client.signin(.root(username: "root", password: "secret"))
try await client.use(namespace: "myapp", database: "mydb")

let stream = try await client.live(SurrealDSL.live(Person.self))
```

## Consuming events

Iterate the stream with `for await` and switch on the event's `action`:

```swift
for await event in stream {
    switch event.action {
    case .create:
        print("Created:", event.decoded as Any)
    case .update:
        print("Updated:", event.decoded as Any)
    case .delete:
        print("Deleted record:", event.recordID)
    case .killed:
        print("Live query was killed")
    }
}
```

## Killing a live query

To stop receiving events, kill the live query using its id:

```swift
try await client.kill(liveQueryID: event.queryID)
```

See the [`live`](/docs/reference/swift/methods/live.md) and [`kill`](/docs/reference/swift/methods/kill.md) method references for more detail.

---

Source: https://surrealdb.com/docs/reference/swift/concepts/models

# Models

Define type-safe models for SurrealDB using the @SurrealModel macro or manual conformance in the Swift SDK.

Models describe the shape of your records and give the SDK the type information it needs for type-safe CRUD operations and [predicates](/docs/reference/swift/concepts/predicates.md).

## The `@SurrealModel` macro

The `@SurrealModel` macro is the recommended way to declare a model. It takes the table name and generates everything the SDK needs:

```swift
import SurrealDB

@SurrealModel("person")
struct Person: Codable, Sendable {
    let id: String?
    let name: String
    let age: Int
}
```

The macro generates:

- `static let surrealTable`, the table name passed to the macro.
- `SurrealModel` conformance, so the type can be used directly with [`select`](/docs/reference/swift/methods/select.md), [`create`](/docs/reference/swift/methods/create.md), [`update`](/docs/reference/swift/methods/update.md), [`upsert`](/docs/reference/swift/methods/upsert.md) and [`delete`](/docs/reference/swift/methods/delete.md).
- A `Fields` namespace for building type-safe predicates such as `Person.Fields.age >= 18`.

## Manual conformance

If you prefer not to use the macro, you can conform to `SurrealModel` manually by declaring the `surrealTable` property:

```swift
struct Article: SurrealModel, Codable, Sendable {
    static let surrealTable = "article"
    let id: String?
    let title: String
}
```

Manual conformance does not generate a `Fields` namespace, so you will need to write [raw predicates](/docs/reference/swift/concepts/predicates.md#raw-predicates) for queries that filter on fields.

---

Source: https://surrealdb.com/docs/reference/swift/concepts/predicates

# Predicates

Build type-safe query conditions with the predicate DSL in the SurrealDB Swift SDK.

Predicates are type-safe conditions used to filter records in [`select`](/docs/reference/swift/methods/select.md), [`update`](/docs/reference/swift/methods/update.md), [`upsert`](/docs/reference/swift/methods/upsert.md) and [`delete`](/docs/reference/swift/methods/delete.md). They are built from the `Fields` namespace generated by the [`@SurrealModel` macro](/docs/reference/swift/concepts/models.md).

## Comparison operators

Each field supports the standard comparison operators:

```swift
Person.Fields.name == "Ada"
Person.Fields.name != "Bob"
Person.Fields.age > 18
Person.Fields.age >= 21
Person.Fields.age < 65
Person.Fields.age <= 60
```

## Combining predicates

Combine predicates with the logical operators `&&`, `||` and `!`:

```swift
let combined = Person.Fields.age >= 18 && Person.Fields.published == true
let either = Person.Fields.age < 18 || Person.Fields.name == "Admin"
let negated = !(Person.Fields.published == false)
```

## Raw predicates

When you need an expression that the DSL does not cover, or when using a [manually conformed model](/docs/reference/swift/concepts/models.md#manual-conformance) without a `Fields` namespace, you can supply a raw SurrealQL condition:

```swift
let raw = SurrealPredicate(raw: "age > 18 AND name != 'Bot'")
```

## Using a predicate

Pass a predicate to any method that accepts a `where:` argument:

```swift
let adults = try await client.select(
    Person.self,
    where: Person.Fields.age >= 18,
    limit: 20,
    start: 0
)
```

---

Source: https://surrealdb.com/docs/reference/swift/concepts/query-dsl

# Query DSL

Build queries with compile-time macros or the programmatic SurrealDSL builder in the Swift SDK.

In addition to the high-level CRUD methods, the Swift SDK lets you build queries with a type-safe DSL. You can use either compile-time expression macros or the programmatic `SurrealDSL` builder, then run the result with [`query`](/docs/reference/swift/methods/query.md).

## Query macros

Expression macros resolve to `SurrealDSL` calls at compile time. They are the most concise way to express a query:

```swift
let selectQuery = #select(Person.self, where: Person.Fields.age > 18, limit: 10)
let createQuery = #create(Person.self)
let updateQuery = #update(Person.self, where: Person.Fields.name == "Ada")
let upsertQuery = #upsert(Person.self)
let deleteQuery = #delete(Person.self, where: Person.Fields.age < 18)
let liveQuery = #live(Person.self)
```

Run a query with the [`query`](/docs/reference/swift/methods/query.md) method:

```swift
let people = try await client.query(selectQuery)
```

## Programmatic `SurrealDSL` builder

`SurrealDSL` exposes the same operations as plain functions, which is useful when a query is built dynamically:

```swift
let query = SurrealDSL.select(
    Person.self,
    where: Person.Fields.age >= 21,
    limit: 50,
    start: 0
)
let people = try await client.query(query)
```

When creating records through the DSL, pass the content as a binding:

```swift
let createQuery = SurrealDSL.create(
    Person.self,
    contentBinding: "content",
    bindings: ["content": try .fromEncodable(newPerson)]
)
let created = try await client.query(createQuery)
```

## When to use raw queries instead

For arbitrary SurrealQL that the DSL does not model, use [`queryRaw`](/docs/reference/swift/methods/query-raw.md) with bound parameters.

---

Source: https://surrealdb.com/docs/reference/swift/data-types

# Data types

An overview of the data types used by the SurrealDB Swift SDK, including SurrealValue and SurrealRecordID.

This page describes the core data types used throughout the Swift SDK when working with records and raw queries.

## `SurrealValue`

`SurrealValue` is the universal value type used to represent any data that can be sent to or received from SurrealDB. It is most often used when binding parameters to [raw queries](/docs/reference/swift/methods/query-raw.md) or when working with dynamically typed data.

```swift
let string: SurrealValue = .string("hello")
let int: SurrealValue = .int(42)
let double: SurrealValue = .double(3.14)
let bool: SurrealValue = .bool(true)
let null: SurrealValue = .null
let array: SurrealValue = .array([.string("a"), .int(1)])
let object: SurrealValue = .object(["name": .string("Ada"), "age": .int(30)])
let uuid: SurrealValue = .uuid(UUID())
let datetime: SurrealValue = .datetime(Date())
let record: SurrealValue = .recordID(SurrealRecordID(table: "person", id: .string("ada")))
```

You can convert between `SurrealValue` and your own `Codable` types:

```swift
// Encode a Codable value into a SurrealValue
let value = try SurrealValue.fromEncodable(myStruct)

// Decode a SurrealValue back into a concrete type
let person = try value.decode(Person.self)
```

## `SurrealRecordID`

A `SurrealRecordID` identifies a single record by its table and id. It is used by the record-targeted overloads of [`select`](/docs/reference/swift/methods/select.md), [`create`](/docs/reference/swift/methods/create.md), [`update`](/docs/reference/swift/methods/update.md) and [`delete`](/docs/reference/swift/methods/delete.md).

```swift
let id = SurrealRecordID(table: "person", id: .string("ada"))
let person: Person? = try await client.select(recordID: id, as: Person.self)
```

## `RPCQueryResult`

[`queryRaw`](/docs/reference/swift/methods/query-raw.md) returns an array of `RPCQueryResult`, one per statement in the query. Each result carries a `status` and a `result`.

```swift
let results: [RPCQueryResult] = try await client.queryRaw(
    "SELECT * FROM person WHERE age > $minAge;",
    bindings: ["minAge": .int(18)]
)

for row in results {
    if row.status == .ok {
        print(row.result)
    }
}
```

---

Source: https://surrealdb.com/docs/reference/swift/installation

# Installation

In this section, you will learn how to install the Swift SDK in your project.

Before you can use this SDK in your Swift applications, you need to add it as a dependency and import it into your project. The SDK is distributed via [Swift Package Manager](https://www.swift.org/package-manager/).

> [!NOTE]
> The package is not yet listed on [Swift Package Index](https://swiftpackageindex.com). Until it is, add it using the repository's git URL as shown below. Once published, you will also be able to find it by searching the index.

## Install the SDK

### Using `Package.swift`

Add the package to the `dependencies` array of your `Package.swift`, then add the `SurrealDB` product to the dependencies of your target:

```swift
// swift-tools-version:6.1
import PackageDescription

let package = Package(
    name: "YourApp",
    dependencies: [
        .package(url: "https://github.com/surrealdb/surrealdb.swift.git", from: "0.1.0"),
    ],
    targets: [
        .target(
            name: "YourTarget",
            dependencies: [
                .product(name: "SurrealDB", package: "surrealdb.swift")
            ]
        )
    ]
)
```

### Using Xcode

1. Open your project and choose **File → Add Package Dependencies…**
2. Enter the repository URL `https://github.com/surrealdb/surrealdb.swift.git` in the search field.
3. Select a dependency rule (for example **Up to Next Major Version**) and click **Add Package**.
4. Choose the `SurrealDB` library product and add it to your application target.

## Import the SDK

Once the package is resolved, import it wherever you need it:

```swift
import SurrealDB
```

## Next steps

See the [getting started guide](/docs/languages/swift.md) to connect to a database and run your first query.

---

Source: https://surrealdb.com/docs/reference/swift/methods

# SDK methods

The Swift SDK for SurrealDB enables simple and advanced querying of a remote database.

The Swift SDK exposes its functionality through the `SurrealHTTPClient` and `SurrealWebSocketClient` types, which share the same API. This page lists the methods available on a connected client.

## 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/swift/methods/connect.md"> <code>client.connect()</code></a></td>
            <td scope="row" data-label="Description">Connects the client to the underlying endpoint</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/swift/methods/use.md"> <code>client.use(namespace, database)</code></a></td>
            <td scope="row" data-label="Description">Switch to a specific namespace and 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/swift/methods/signup.md"> <code>client.signup(credentials)</code></a></td>
            <td scope="row" data-label="Description">Signs this connection up to a specific authentication access</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/swift/methods/signin.md"> <code>client.signin(credentials)</code></a></td>
            <td scope="row" data-label="Description">Signs this connection in to a specific authentication level</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/swift/methods/authenticate.md"> <code>client.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/swift/methods/invalidate.md"> <code>client.invalidate()</code></a></td>
            <td scope="row" data-label="Description">Invalidates the authentication for the current connection</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/swift/methods/select.md"> <code>client.select(model)</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/swift/methods/query.md"> <code>client.query(query)</code></a></td>
            <td scope="row" data-label="Description">Runs a query built with the query DSL or macros</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/swift/methods/query-raw.md"> <code>client.queryRaw(sql, bindings)</code></a></td>
            <td scope="row" data-label="Description">Runs a raw SurrealQL query with bound parameters</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/swift/methods/live.md"> <code>client.live(query)</code></a></td>
            <td scope="row" data-label="Description">Subscribes to changes via a live query (WebSocket only)</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/swift/methods/kill.md"> <code>client.kill(liveQueryID)</code></a></td>
            <td scope="row" data-label="Description">Kills a running live query</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/swift/methods/create.md"> <code>client.create(model)</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/swift/methods/update.md"> <code>client.update(model)</code></a></td>
            <td scope="row" data-label="Description">Updates matching records, or a specific record</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/swift/methods/upsert.md"> <code>client.upsert(model)</code></a></td>
            <td scope="row" data-label="Description">Creates or updates matching records</td>
        </tr>
        <tr>
            <td scope="row" data-label="Method"><a href="/docs/reference/swift/methods/delete.md"> <code>client.delete(model)</code></a></td>
            <td scope="row" data-label="Description">Deletes matching records, or a specific record</td>
        </tr>
    </tbody>
</table>

---

Source: https://surrealdb.com/docs/reference/swift/methods/authenticate

# authenticate

The authenticate() method for the SurrealDB Swift SDK authenticates the connection with a JWT token.

Authenticates the current connection with a previously issued JWT access token.

```swift title="Method Syntax"
try await client.authenticate(token)
```

## 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>token</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The JWT access token to authenticate the connection with.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
try await client.authenticate(tokens.access)
```

You can also resume a session by supplying a token through a [`SessionContext`](/docs/reference/swift/concepts/authentication.md#resuming-a-session) when constructing the client.

---

Source: https://surrealdb.com/docs/reference/swift/methods/connect

# connect

The connect() method for the SurrealDB Swift SDK connects the client to the configured endpoint.

Connects the client to the endpoint it was configured with.

```swift title="Method Syntax"
try await client.connect()
```

## Example usage

```swift
let client = try SurrealHTTPClient(endpoint: "http://localhost:8000")
try await client.connect()
defer { Task { await client.close() } }
```

See [Connecting to SurrealDB](/docs/reference/swift/concepts/connecting.md) for the available client types and configuration options.

---

Source: https://surrealdb.com/docs/reference/swift/methods/create

# create

The create() method for the SurrealDB Swift SDK creates a record in the database.

Creates a record in the database, either with an auto-generated id or a specific id.

```swift title="Method Syntax"
try await client.create(content)
```

## 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>content</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The model instance to create. The table is derived from the model.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>recordID</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                A [`SurrealRecordID`](/docs/reference/swift/data-types.md#surrealrecordid) to create the record with a specific id.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
// Create with an auto-generated id
let created: [Person] = try await client.create(Person(id: nil, name: "Ada", age: 30))

// Create with a specific id
let id = SurrealRecordID(table: "person", id: .string("ada"))
let record: Person? = try await client.create(
    recordID: id,
    content: Person(id: nil, name: "Ada", age: 30)
)
```

---

Source: https://surrealdb.com/docs/reference/swift/methods/delete

# delete

The delete() method for the SurrealDB Swift SDK deletes matching records, or a specific record.

Deletes matching records of a model's table, or a single record by id.

```swift title="Method Syntax"
try await client.delete(Model.self, where: predicate)
```

## 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>model</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The model type to delete from, e.g. <code>Person.self</code>.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>where</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                A [predicate](/docs/reference/swift/concepts/predicates.md) selecting which records to delete.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>recordID</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                A [`SurrealRecordID`](/docs/reference/swift/data-types.md#surrealrecordid) to delete a single record.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
// Delete matching records
let deleted = try await client.delete(Person.self, where: Person.Fields.age < 18)

// Delete a specific record
let id = SurrealRecordID(table: "person", id: .string("ada"))
let record: Person? = try await client.delete(recordID: id, as: Person.self)
```

---

Source: https://surrealdb.com/docs/reference/swift/methods/invalidate

# invalidate

The invalidate() method for the SurrealDB Swift SDK clears the connection's authentication state.

Invalidates the authentication for the current connection.

```swift title="Method Syntax"
try await client.invalidate()
```

## Example usage

```swift
try await client.invalidate()
```

---

Source: https://surrealdb.com/docs/reference/swift/methods/kill

# kill

The kill() method for the SurrealDB Swift SDK stops a running live query.

Kills a running [live query](/docs/reference/swift/methods/live.md) by its id, ending the associated `AsyncStream`.

```swift title="Method Syntax"
try await client.kill(liveQueryID: id)
```

## 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>liveQueryID</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The id of the live query to kill, available as <code>event.queryID</code> on received events.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
try await client.kill(liveQueryID: event.queryID)
```

---

Source: https://surrealdb.com/docs/reference/swift/methods/live

# live

The live() method for the SurrealDB Swift SDK subscribes to changes via a live query.

Subscribes to changes on a table and returns an `AsyncStream<LiveEvent<T>>` of events.

> [!IMPORTANT]
> Live queries require the [WebSocket client](/docs/reference/swift/concepts/connecting.md#websocket-client).

```swift title="Method Syntax"
try await client.live(query)
```

## 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">
                A live query built with <code>SurrealDSL.live(_:)</code> or the <code>#live</code> macro.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
let stream = try await client.live(SurrealDSL.live(Person.self))

for await event in stream {
    switch event.action {
    case .create:
        print("Created:", event.decoded as Any)
    case .update:
        print("Updated:", event.decoded as Any)
    case .delete:
        print("Deleted record:", event.recordID)
    case .killed:
        print("Live query was killed")
    }
}
```

See [Live queries](/docs/reference/swift/concepts/live-queries.md) for a full walkthrough, and [`kill`](/docs/reference/swift/methods/kill.md) to stop a subscription.

---

Source: https://surrealdb.com/docs/reference/swift/methods/query

# query

The query() method for the SurrealDB Swift SDK runs a query built with the query DSL or macros.

Runs a query built with the [query DSL](/docs/reference/swift/concepts/query-dsl.md), either a query macro such as `#select` or a `SurrealDSL` builder call.

```swift title="Method Syntax"
try await client.query(query)
```

## 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">
                A query produced by a query macro or a <code>SurrealDSL</code> builder call.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
// Using a query macro
let selectQuery = #select(Person.self, where: Person.Fields.age > 18, limit: 10)
let people = try await client.query(selectQuery)

// Using the SurrealDSL builder
let query = SurrealDSL.select(Person.self, where: Person.Fields.age >= 21, limit: 50)
let result = try await client.query(query)
```

For arbitrary SurrealQL, see [`queryRaw`](/docs/reference/swift/methods/query-raw.md).

---

Source: https://surrealdb.com/docs/reference/swift/methods/query-raw

# queryRaw

The queryRaw() method for the SurrealDB Swift SDK runs a raw SurrealQL query with bound parameters.

Executes a raw SurrealQL string with bound parameters and returns one [`RPCQueryResult`](/docs/reference/swift/data-types.md#rpcqueryresult) per statement.

```swift title="Method Syntax"
try await client.queryRaw(sql, bindings: bindings)
```

## 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">
                The raw SurrealQL query to execute.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>bindings</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                A dictionary of [`SurrealValue`](/docs/reference/swift/data-types.md#surrealvalue) parameters referenced in the query.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
let results: [RPCQueryResult] = try await client.queryRaw(
    "SELECT * FROM person WHERE age > $minAge LIMIT $limit;",
    bindings: [
        "minAge": .int(18),
        "limit": .int(50)
    ]
)

for row in results {
    if row.status == .ok {
        print(row.result)
    }
}
```

---

Source: https://surrealdb.com/docs/reference/swift/methods/select

# select

The select() method for the SurrealDB Swift SDK selects all records in a table, or a specific record.

Selects all records of a model's table, a filtered subset, or a single record by id.

```swift title="Method Syntax"
try await client.select(Model.self, where: predicate, limit: limit,
    start: start)
```

## 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>model</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The model type to select, e.g. <code>Person.self</code>.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>where</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                A [predicate](/docs/reference/swift/concepts/predicates.md) restricting which records are returned.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>limit</code> / <code>start</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Paginate the results by limiting the count and offsetting the start.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>recordID</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                Select a single record by its [`SurrealRecordID`](/docs/reference/swift/data-types.md#surrealrecordid) instead of a table.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
// Select all records
let people = try await client.select(Person.self)

// Select a filtered, paginated subset
let adults = try await client.select(
    Person.self,
    where: Person.Fields.age >= 18,
    limit: 20,
    start: 0
)

// Select a single record by id
let id = SurrealRecordID(table: "person", id: .string("ada"))
let person: Person? = try await client.select(recordID: id,
    as: Person.self)
```

---

Source: https://surrealdb.com/docs/reference/swift/methods/signin

# signin

The signin() method for the SurrealDB Swift SDK signs in to a specific authentication level.

Signs this connection in to a specific authentication level and returns the issued tokens.

```swift title="Method Syntax"
let tokens = try await client.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="row" data-label="Arguments">
                <code>credentials</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The credentials to authenticate with: one of <code>.root</code>, <code>.namespace</code>, <code>.database</code>, <code>.accessVariables</code> or <code>.accessBearer</code>.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
// Root user
let tokens = try await client.signin(.root(username: "root",
    password: "secret"))

// Database user
let tokens = try await client.signin(.database(
    namespace: "myapp",
    database: "mydb",
    username: "db_user",
    password: "secret"
))

// Record access with variables
let tokens = try await client.signin(.accessVariables(
    namespace: "myapp",
    database: "mydb",
    access: "account",
        variables: ["email": .string("user@example.com"),
        "pass": .string("secret")]
))
```

See [Authentication](/docs/reference/swift/concepts/authentication.md) for every supported credentials variant.

---

Source: https://surrealdb.com/docs/reference/swift/methods/signup

# signup

The signup() method for the SurrealDB Swift SDK signs a new record-access user up.

Signs a new record-access user up and returns the issued tokens.

```swift title="Method Syntax"
let tokens = try await client.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">
                The record-access credentials to sign up with, created via <code>.accessRecord</code>.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
let tokens = try await client.signup(.accessRecord(
    namespace: "myapp",
    database: "mydb",
    access: "account",
        variables: ["email": .string("new@example.com"),
        "pass": .string("secret")]
))
```

See [Authentication](/docs/reference/swift/concepts/authentication.md) for more detail.

---

Source: https://surrealdb.com/docs/reference/swift/methods/update

# update

The update() method for the SurrealDB Swift SDK updates matching records, or a specific record.

Updates matching records of a model's table, or a single record by id.

```swift title="Method Syntax"
try await client.update(Model.self, content: content,
    where: predicate)
```

## 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>model</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The model type to update, e.g. <code>Person.self</code>.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>content</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The content to write to matching records.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>where</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                A [predicate](/docs/reference/swift/concepts/predicates.md) selecting which records to update.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>recordID</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                A [`SurrealRecordID`](/docs/reference/swift/data-types.md#surrealrecordid) to update a single record.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
// Update matching records
let updated = try await client.update(
    Person.self,
    content: Person(id: nil, name: "Ada", age: 31),
    where: Person.Fields.name == "Ada"
)

// Update a specific record
let id = SurrealRecordID(table: "person", id: .string("ada"))
let record: Person? = try await client.update(
    recordID: id,
    content: Person(id: nil, name: "Ada", age: 31)
)
```

---

Source: https://surrealdb.com/docs/reference/swift/methods/upsert

# upsert

The upsert() method for the SurrealDB Swift SDK creates or updates matching records.

Creates matching records if they do not exist, or updates them if they do.

```swift title="Method Syntax"
try await client.upsert(Model.self, content: content, where: predicate)
```

## 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>model</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The model type to upsert, e.g. <code>Person.self</code>.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>content</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The content to write to matching records.
            </td>
        </tr>
        <tr>
            <td colspan="2" scope="row" data-label="Arguments">
                <code>where</code>
                <label label="optional" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                A [predicate](/docs/reference/swift/concepts/predicates.md) selecting which records to upsert.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
let upserted = try await client.upsert(
    Person.self,
    content: Person(id: nil, name: "Ada", age: 31),
    where: Person.Fields.name == "Ada"
)
```

---

Source: https://surrealdb.com/docs/reference/swift/methods/use

# use

The use() method for the SurrealDB Swift SDK switches to a specific namespace and database.

Switches the connection to a specific namespace and database.

```swift title="Method Syntax"
try await client.use(namespace: ns, database: 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="row" data-label="Arguments">
                <code>namespace</code>
                <label label="required" />
            </td>
            <td colspan="2" scope="row" data-label="Description">
                The namespace to switch 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 switch to.
            </td>
        </tr>
    </tbody>
</table>

## Example usage

```swift
try await client.use(namespace: "myapp", database: "mydb")
```

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks

# Cookbooks

Opinionated recipes and migration guides.

Practical, end-to-end guides for building trustworthy agent memory with SurrealDB Agent Memory. They assume familiarity with the [eight pillars](/docs/agent-memory/architecture/eight-pillars-and-categories.md) and [unified substrate](/docs/agent-memory/mental-model/two-layer-architecture.md).

## Build

- [Customer support agent](/docs/agent-memory/cookbooks/build/customer-support-agent.md)
- [Personal AI assistant](/docs/agent-memory/cookbooks/build/personal-ai-assistant.md)

## Patterns

- [User memory in chat](/docs/agent-memory/cookbooks/patterns/user-memory-in-chat.md)
- [Knowledge-grounded agents](/docs/agent-memory/cookbooks/patterns/knowledge-grounded-agents.md)
- [Reflection loops](/docs/agent-memory/cookbooks/patterns/reflection-loops.md)
- [Stateful workflows](/docs/agent-memory/cookbooks/patterns/stateful-workflows.md)
- [Adding memory to an existing app](/docs/agent-memory/cookbooks/patterns/adding-memory-to-existing-app.md)
- [Spoiler-safe narrative memory](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md)
- [Event-driven game client](/docs/agent-memory/cookbooks/patterns/event-driven-game-client.md)
- [Historical and archaeological data](/docs/agent-memory/cookbooks/patterns/historical-and-archaeological-data.md)

## Migrate

- [From a vector store](/docs/agent-memory/cookbooks/migrate/from-vector-store.md)
- [From Zep](/docs/agent-memory/cookbooks/migrate/from-zep.md)
- [From LangMem](/docs/agent-memory/cookbooks/migrate/from-langmem.md)

## More recipes

- [Coding agent with project memory](/docs/agent-memory/cookbooks/build/coding-agent-with-project-memory.md) - an assistant that remembers a codebase between sessions
- [Long-running research agent](/docs/agent-memory/cookbooks/build/long-running-research-agent.md) - accumulate findings across many runs
- [Multi-agent shared memory](/docs/agent-memory/cookbooks/build/multi-agent-shared-memory.md) - several agents reading and writing one context
- [Migrate from Mem0](/docs/agent-memory/cookbooks/migrate/from-mem0.md) - move an existing Mem0 store across

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/build/coding-agent-with-project-memory

# Coding agent with project memory

Repository scoped sessions and MCP.

This guide shows how to build a coding assistant - similar to an in-editor AI agent - that uses SurrealDB Agent Memory via MCP to remember coding conventions, past decisions, team preferences, and ongoing work across sessions.

## What the agent remembers

A coding agent working on a repository accumulates a surprising amount of context that is useful across sessions:

- **Coding conventions**: "We use `Result<T, Error>` not exceptions in this codebase"
- **Past decisions**: "We chose Tanstack Query over SWR because of the devtools"
- **Team preferences**: "Alice prefers functional components; Bob owns the auth module"
- **Ongoing work**: "The migration to the analytics database is 80% complete, blocked on the auth service"
- **Documentation**: Product specs, architecture decisions, API contracts

Without persistent memory, every new session starts from scratch. With SurrealDB Agent Memory, the agent can recall relevant context at the start of each conversation and update its memory as new decisions are made.

---

## Installing the MCP server

SurrealDB Agent Memory serves MCP at `/mcp` on your instance (SurrealDB Cloud: your context host from SurrealDB Studio **API keys**; self-hosted: your server's base URL). Install it into your editor with [`install-mcp`](https://github.com/supermemoryai/install-mcp) - the `/mcp` URL is the first argument, auth goes through `--header`, and `--oauth no` skips the OAuth prompt:

```bash
# Install for Cursor
npx install-mcp https://<your-context-host>/mcp \
    --client cursor \
    --header "Authorization: Bearer <your-api-key>" \
    --oauth no
```

This registers the SurrealDB Agent Memory MCP server in your editor's MCP configuration. Memory is scoped per tool call with a `scope` argument (for example `["org/acme/project/my-repo"]`), not at install time.

### Manual configuration

If you prefer to configure the MCP server manually, add it to your editor's MCP config:

```json
{
  "mcpServers": {
    "spectron": {
      "url": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>",
        "X-Spectron-Context": "my-project"
      }
    }
  }
}
```

---

## Scope per repository

Scoping memory to a repository ensures that conventions from one project do not bleed into another. Use the `project` dimension in the scope:

```json
["org/acme/project/my-repo"]
```

With this scope, all memory stored during sessions for `my-repo` is isolated from memory for other projects. Team-wide knowledge (e.g. company-wide conventions) lives at the `org` scope and is readable by all project-scoped sessions.

---

## Session start: loading context

At the beginning of each coding session, the agent should call `context` to load relevant context before it responds to the first user message:

```text
Tool: context
Arguments: {
  "query": "project conventions and active work for my-repo",
  "lens": ["org/acme/project/my-repo"]
}
```

This returns a markdown context block of what SurrealDB Agent Memory knows about this project: ongoing work, decisions, preferences, and conventions. The agent includes this in its system context before the user's first message.

---

## Storing decisions

When a significant decision is made during a session, use `remember` to persist it:

```text
Tool: remember
Arguments: {
  "text": "We decided to use Zod for runtime validation because it integrates with our existing TypeScript types and provides better error messages than Yup.",
  "scope": ["org/acme/project/my-repo"]
}
```

For team preferences and conventions, the agent can store these automatically when it identifies a standing directive in the conversation:

```text
User: \
  "Always use named exports in this project, never default exports."

Tool: remember
Arguments: {
  "text": "Always use named exports, never default exports.",
  "scope": ["org/acme/project/my-repo"]
}
```

---

## Documentation lookup with recall

When the user asks a question that might be answered by project documentation ingested into authoritative knowledge, use `recall`:

```text
Tool: recall
Arguments: {
  "query": "What is the authentication flow for the API?",
  "lens": ["org/acme/project/my-repo"],
  "k": 3
}
```

This searches the authoritative knowledge base for relevant passages from ingested documents (architecture decision records, API specs, README files).

To ingest project documentation into authoritative knowledge:

```bash
# One document at a time
spectron documents upload ./docs/architecture.md --label "topic=architecture"
spectron documents upload ./docs/api-spec.yaml --label "topic=api"

# Or ingest a whole folder recursively
spectron ingest ./docs --label "topic=project-docs"
```

---

## Full session flow

Here is what a typical coding session looks like with SurrealDB Agent Memory MCP integrated:

**Session start**

```text
Tool: context
→ Returns: ongoing work, conventions, decisions, preferences

Tool: recall (query = user's first message)
→ Returns: relevant documentation passages
```

**During the session**

```text
User: "How should I handle errors in the new payment module?"

Tool: recall
Arguments: { "query": "error handling patterns", "lens": ["org/acme/project/my-repo"] }
→ Returns: "Use Result<T, Error> not exceptions in this codebase"

Agent: "Based on this project's conventions, you should use Result<T, Error>…"
```

**When a decision is made**

```text
User: "Let's use Stripe's webhook library directly rather than wrapping it."

Tool: remember
Arguments: {
  "text": "Payment module uses Stripe webhook library directly, no wrapper.",
  "scope": ["org/acme/project/my-repo"]
}
```

**Session end (optional)**

```text
Tool: reflect
Arguments: {
  "query": "What was decided and what needs follow-up?",
  "persist": true
}
→ Synthesises and stores a session summary
```

---

## Multi-developer teams

When multiple developers work on the same project, they share memory at the project scope. Each developer's session contributes to and reads from the same experiential memory.

For individual preferences, use the `user` dimension alongside `project`:

```json
["org/acme/project/my-repo/user/alice"]
```

Alice's personal preferences (code style, preferred approaches) live at the user scope. Project-wide conventions live at the project scope. Both are accessible when Alice's session is active.

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/build/customer-support-agent

# Customer support agent

Ticket-linked memory with authoritative knowledge policies.

This guide walks through building a customer support agent that uses SurrealDB Agent Memory for two distinct purposes: **authoritative knowledge** holds the authoritative product knowledge - FAQs, policies, and the product catalogue - and **experiential memory** holds per-customer memory accumulated over every interaction. The result is an agent that answers product questions correctly and remembers each customer's history without manual context injection.

## What you are building

- A Context configured for extraction, holding customer, product, and ticket records.
- authoritative knowledge nodes for the product catalogue and policy documents.
- Per-customer sessions scoped by `user_id`, so each customer's memory is isolated.
- A conversation loop that retrieves relevant context before each LLM call and extracts new facts after each turn.

## Prerequisites

A Context is created through the management API or the dashboard. The examples below assume you have a `context_id` and a management API key for ingestion, plus an agent API key for the conversation loop.

## Step 1 - Create the Context

Create the Context with LLM extraction enabled, so conversation turns and uploaded documents produce typed entities rather than passages alone.

```python
import os
import httpx

mgmt = httpx.Client(
    base_url="https://spectron.surrealdb.com/api/v1",
    headers={"Authorization": f"Bearer {os.environ['SPECTRON_MGMT_KEY']}"},
)

mgmt.post("/contexts", json={
    "id": "support",
    "display_name": "Customer Support",
    "config": {
        "llm_extraction_enabled": True,
        "models": {"extraction": "openai/gpt-4o-mini"},
    },
})
```

```typescript
const response = await fetch("https://spectron.surrealdb.com/api/v1/contexts", {
    method: "POST",
    headers: {
        "Authorization": "Bearer mgmt_...",
        "Content-Type": "application/json",
    },
    body: JSON.stringify({
        id: "support",
        display_name: "Customer Support",
        config: {
            llm_extraction_enabled: true,
            models: { extraction: "openai/gpt-4o-mini" },
        },
    }),
});
```

> [!NOTE]
> Entity types come from a fixed vocabulary - a customer extracts as `person` or `organisation`, a catalogue item as `product`, a ticket as `event` or `other`. You cannot register `Customer` or `Ticket` as types of their own. Attribute keys and relation labels are free-form and converge on reuse as the graph fills; see [Extraction vocabulary](/docs/agent-memory/tuning/ontology-grounding.md). Where you need guaranteed keys - a ticket's `status`, a plan tier - write them as triples with `infer: "triples"` instead of relying on extraction.

## Step 2 - Ingest the product catalogue into authoritative knowledge

Upload your product catalogue as a document. SurrealDB Agent Memory chunks it, extracts keywords, and creates knowledge nodes that agents can query.

```python
import pathlib

upload = httpx.Client(
    base_url="https://spectron.surrealdb.com/api/v1/support",
    headers={"Authorization": f"Bearer {os.environ['SPECTRON_API_KEY']}"},
)

with open("products.json", "rb") as f:
    upload.post(
        "/documents",
        files={"file": ("products.json", f, "application/json")},
        data={"title": "Product catalogue", "content_type": "product_data"},
    )
```

```typescript
const formData = new FormData();
formData.append("file", new Blob([productJson], { type: "application/json" }), "products.json");
formData.append("title", "Product catalogue");
formData.append("content_type", "product_data");

await fetch("https://spectron.surrealdb.com/api/v1/support/documents", {
    method: "POST",
    headers: { "Authorization": "Bearer mgmt_..." },
    body: formData,
});
```

## Step 3 - Ingest policy documents

Policy documents are ingested the same way. SurrealDB Agent Memory parses them and extracts structured knowledge nodes for policy rules, deadlines, and conditions.

```python
for path in pathlib.Path("policies/").glob("*.md"):
    with open(path, "rb") as f:
        upload.post(
            "/documents",
            files={"file": (path.name, f, "text/markdown")},
            data={"title": path.stem.replace("-", " ").title(), "content_type": "policy"},
        )
```

```typescript
import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path";

const files = await readdir("policies/");
for (const filename of files.filter(f => f.endsWith(".md"))) {
    const content = await readFile(join("policies", filename));
    const formData = new FormData();
    formData.append("file", new Blob([content], { type: "text/markdown" }), filename);
    formData.append("title", filename.replace(/-/g, " ").replace(".md", ""));
    formData.append("content_type", "policy");

    await fetch("https://spectron.surrealdb.com/api/v1/support/documents", {
        method: "POST",
        headers: { "Authorization": "Bearer mgmt_..." },
        body: formData,
    });
}
```

## Step 4 - Handle a customer conversation

Each customer conversation is a session scoped to that customer's `user_id`. The scope ensures that Customer entity attributes (past tickets, preferences, purchase history) are isolated per customer.

### Initialise the client

```python
from surrealdb.memory import Memory

memory = Memory(context="support", api_key="sk-...")
```

```typescript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "support", apiKey: "sk-..." });
```

### Create a session per conversation

```python
session = await memory.sessions.create(
    scopes=[f"org/acme-support/user/{customer_id}"],
)
```

```typescript
const session = await memory.sessions.create({
    scopes: [`org/acme-support/user/${customerId}`],
});
```

### Retrieve context before each agent call

Before generating a response, retrieve relevant memory. The `context()` call performs hybrid retrieval across authoritative knowledge and experiential memory, returning a ranked summary the agent can use.

```python
async def respond(session, user_message: str) -> str:
    # 1. Retrieve relevant context from both layers
    ctx = await session.context(query=user_message)

    # 2. Build the system prompt
    system = f"""You are a customer support agent for Acme Corp.
Use the context below to answer accurately.

{ctx.formatted}"""

    # 3. Call your LLM
    response = your_llm(system=system, user=user_message)

    # 4. Record both turns so Memory extracts memory from the exchange
    await memory.remember(user_message, session_id=session.id, role="user")
    await memory.remember(response, session_id=session.id, role="assistant")

    return response
```

```typescript
async function respond(session: Session, userMessage: string): Promise<string> {
    // 1. Retrieve relevant context from both layers
    const ctx = await session.context({ query: userMessage });

    // 2. Build the system prompt
    const system = `You are a customer support agent for Acme Corp.
Use the context below to answer accurately.

${ctx.formatted}`;

    // 3. Call your LLM
    const response = await yourLlm({ system, user: userMessage });

    // 4. Record both turns
    await memory.remember(userMessage, { sessionId: session.id, role: "user" });
    await memory.remember(response, { sessionId: session.id, role: "assistant" });

    return response;
}
```

## Step 5 - What the agent sees

After a few interactions, the context retrieval for a query like "what is your return policy for AirPods?" will surface:

- **authoritative knowledge**: The return policy knowledge node (authoritative: 30 days, no opened packaging).
- **authoritative knowledge**: The AirPods Pro product node (price, SKU, warranty terms).
- **experiential memory**: The customer entity with attributes - plan tier, previous ticket about a delivery issue, preferred contact channel.

The agent answers the return policy question correctly from authoritative knowledge and can personalise the response ("since you're on the Pro plan, you also have extended phone support") using experiential memory context.

## Step 6 - Querying a customer's memory directly

At any point you can inspect what SurrealDB Agent Memory knows about a specific customer:

```python
# GET /entities filters on type only - the key's read region bounds the rest
entities = await memory.entities.list(type="Customer")
for entity in entities:
    print(entity.attributes)
```

```typescript
const entities = await memory.entities.list({ type: "Customer" });
for (const entity of entities) {
    console.log(entity.attributes);
}
```

This is useful for building agent dashboards, pre-populating ticket forms, or debugging unexpected agent behaviour.

## Authoritative and Experiential interaction

When a customer says "your return policy is actually 60 days", SurrealDB Agent Memory stores their belief under the **Experiential** pillar and surfaces the conflict with the curated policy under the **Authoritative** pillar - the document record is not updated.

This is the core guarantee: **Authoritative** content is protected from conversational drift regardless of how many users assert conflicting information. See [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md) and [Authority when pillars meet](/docs/agent-memory/reasoning/authority-hierarchy.md).

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/build/long-running-research-agent

# Long-running research agent

Sessions across days with reflection.

Research agents accumulate findings across many sessions, often spanning days or weeks. This guide shows how to structure sessions and use reflection to distil growing memory into durable, queryable knowledge.

## The challenge

A research agent that works on a topic over multiple sessions faces a core tension:

- **Too many sessions**: each session starts fresh, losing continuity
- **One long session**: context window fills up, early findings are lost
- **Flat memory**: facts pile up without structure, recall becomes noisy

SurrealDB Agent Memory addresses this through structured entity extraction, temporal attributes, and the reflect operation - which synthesises accumulated session memory into consolidated findings that persist across sessions.

## Architecture

A long-running research agent typically uses:

- **One session per working day** (or per topic block) - sessions remain focused and bounded
- **Scope by project** - all sessions for a research topic share a `project` scope dimension
- **Reflection at session end** - distils findings before the session closes
- **Profile at session start** - loads the consolidated state before the first response

```text
Day 1 session → extract → reflect → consolidated memory
Day 2 session → profile → load context → research → reflect → updated memory
Day 3 session → profile → load context → research → reflect → updated memory
```

## Session structure

### Starting a session

```python
from surrealdb.memory import AsyncMemory

client = AsyncMemory(
    context="acme-prod",
    endpoint="https://spectron.example.com",
    api_key="sk-...",
)

session = await client.sessions.create(scopes=["org/acme/user/researcher-alice"])
profile = await client.profile()
system_prompt = f"""You are a research assistant working on a quantum computing survey.

Current research state:
{profile}

Continue from where we left off."""
# ... conduct research ...
```

### JavaScript equivalent

```javascript
import { AgentMemory } from "@surrealdb/memory";

const client = new AgentMemory({
    endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
    context: "acme-prod",
    apiKey: process.env.AGENT_MEMORY_API_KEY!,
});

const session = await client.sessions.create({
    scopes: ["org/acme/user/researcher-alice/project/quantum-computing-survey"],
});

const profile = await client.profile();
```

## Storing research findings

As the agent discovers new information, store findings as structured memory:

```python
# Store a factual finding
await memory.remember(
    "Surface codes are the leading error correction approach for near-term quantum hardware. IBM and Google both use variants of surface codes in their 2024 roadmaps.",
    memory_category="knowledge",
)

# Store a source reference
await memory.remember(
    "Preskill 2018 coined 'Quantum Volume' as a hardware-independent performance metric. Paper: arXiv:1801.00862",
    memory_category="knowledge",
)

# Store an open question for follow-up. Uncertainties are an extraction
# *output*, not a category you can request - write the text and let the
# extractor flag it; it comes back in `extraction.uncertainties`.
await memory.remember(
    "UNCLEAR: Whether topological qubits (Microsoft) will outpace surface codes before 2030. Need to check latest Microsoft Station Q publications.",
    memory_category="context",
)

# Store a research directive. Standing instructions are likewise detected by
# extraction and returned in `extraction.instructions`.
await memory.remember(
    "Always cross-reference vendor claims with peer-reviewed papers. IBM and Google press releases tend to overstate error rates.",
)
```

## Mid-session recall

When the user asks a question that might connect to earlier findings, use recall before generating an answer:

```python
async def answer_with_context(session, user_question: str) -> str:
    # Retrieve relevant prior findings
    context = await memory.recall(
        user_question,
        k=5,
    )
    
    prompt = f"""Research context from previous sessions:
{context.formatted}

User question: {user_question}

Answer based on prior research, noting any gaps or uncertainties."""
    
    return await llm.complete(prompt)
```

## Reflection at session end

At the end of each session, use `reflect` to distil findings into consolidated summaries:

```python
# Distil findings from this session
await memory.reflect(
    query="What were the key findings, open questions, and next steps from today's research?",
    persist=True,  # Store the synthesis as a new memory
)

# Also distil any conflicts found
await memory.reflect(
    query="Were there any contradictions found between sources? What needs verification?",
    persist=True,
)
```

The `persist=True` flag stores the reflection output as a `knowledge` category memory item, scoped to the project. Future sessions will see these consolidated summaries in their profile.

## Knowledge base ingestion

Research papers and documents should be ingested into authoritative knowledge for high-fidelity retrieval:

```python
# Using the management client to ingest a PDF
management = client.management

await memory.documents.upload(
    "quantum-error-correction.pdf",
    title="Quantum Error Correction: Surface Codes and Beyond",
    content_type="application/pdf",
    scopes=["org/acme/project/quantum-computing-survey"],
    labels=["topic=error-correction", "topic=surface-codes"],
)
```

After ingestion, the agent can retrieve from authoritative knowledge via MCP `recall` or the SDK `recall` helper:

```python
results = await memory.recall(
    query="surface code fidelity thresholds 2024",
    k=3,
    mode="hybrid",
)
```

## Session metadata and continuity

Tag sessions with metadata to make them queryable and to track progress:

```python
session = await client.sessions.create(
    scopes=["org/acme/project/quantum-survey"],
    metadata={
        "day": 4,
        "focus": "topological qubits",
        "sources_reviewed": 0,
        "citations_found": 0,
    },
)

```

Session metadata is set when the session is created and cannot be patched
afterwards - there is no update endpoint. To track counters that change during a
run, write them as attributes instead, where they get a supersession chain:

```python
await memory.remember(
    infer="triples",
    triples=[
        {"entity": {"type": "project", "name": "research-run-14"},
         "key": "sources_reviewed", "value": "12"},
    ],
)
```

## State and diffs between sessions

Use `state()` to inspect the accumulated knowledge at any point:

```python
state = await memory.state()
print(f"Entities: {len(state.knowledge['entities'])}")
print(f"Relations: {len(state.knowledge['relations'])}")
print(f"Open uncertainties: {len(state.unknowns)}")
```

There is no cross-session diff endpoint. Each write returns its own delta, so
accumulate those as the run proceeds:

```python
result = await memory.remember(finding, session_id=session.id)

print(f"New facts: {len(result.extraction.attributes)}")
print(f"Revised facts: {len(result.extraction.corrections)}")
print(f"Open uncertainties: {len(result.extraction.uncertainties)}")
```

## Long-term research patterns

For research spanning weeks or months, consider:

**Weekly reflection**: Run a dedicated reflection pass each week that synthesises all findings into a structured overview. Tag it with the week number for easy retrieval.

**Uncertainty triage**: Regularly recall all `uncertainties` memories and close them out - either confirming them with sources and converting to `knowledge`, or marking them as `forget` once resolved.

**Scope pruning**: When a research sub-topic is complete, use `POST /scopes/forget` with `{"path": "org/acme/project/sub-topic/"}` to prune stale context that would otherwise add noise to future recalls.

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/build/multi-agent-shared-memory

# Multi-agent shared memory

Supervisors, reflection, and shared scopes.

When multiple agents collaborate on a shared task, they need access to the same memory. SurrealDB Agent Memory's scope model makes this natural: agents that share a scope dimension (such as `project`) can all read from and write to the same experiential memory, while retaining individual isolation where needed.

## Patterns

There are two common patterns for multi-agent memory sharing:

| Pattern | When to use |
|---|---|
| Shared scope | All agents contribute to and read from a common project scope |
| Supervisor + workers | A supervisor agent coordinates several worker agents, each with its own user/agent scope |

## Pattern 1: shared project scope

The simplest approach - all agents are given the same scope when creating sessions. Memory written by one agent is immediately visible to others operating in the same scope.

```python
from surrealdb.memory import AsyncMemory

client = AsyncMemory(
    context="acme-prod",
    endpoint="https://spectron.example.com",
    api_key="sk-...",
)

SHARED_SCOPE = ["org/acme/project/market-research-q3"]

# Agent A: data collection agent
await client.remember(
    "Competitor X launched a new pricing tier at $299/mo targeting SMBs.",
    scopes=SHARED_SCOPE,
)

# Agent B: analysis agent - sees Agent A's memory
results = await client.recall(
    "recent competitor pricing changes",
    k=5,
    lens=SHARED_SCOPE,
)
# results.hits includes Agent A's finding
```

### Read/write access control

To prevent worker agents from writing to shared memory (read-only readers), issue separate API keys with restricted write capabilities. A key bound to a principal granted `memory:read` and `memory:write` on `org/acme/project/market-research-q3` can read and write at that scope. For genuinely read-only agents, mint the key against a principal granted `memory:read` but not `memory:write` on that path.

## Pattern 2: supervisor and workers

A supervisor agent orchestrates several workers. Each worker operates in its own user/agent scope, but the supervisor aggregates their findings into a shared project scope.

```python
SUPERVISOR_SCOPE = ["org/acme/project/research-pipeline"]

async def run_worker(topic: str, worker_id: str, client: AsyncMemory) -> str:
    worker_scope = [*SUPERVISOR_SCOPE, f"agent/{worker_id}"]
    async with client.sessions.create(scopes=worker_scope) as session:
        # Worker researches its topic
        context = await client.recall(topic, k=5)
        findings = await llm.research(topic, context["hits"])

        # Worker stores findings in its own scope
        await client.remember(
            findings,
            session_id=session.id,
            memory_category="knowledge",
        )

        # Return a summary for the supervisor
        reflection = await client.reflect(
            query=f"Summarise findings about {topic}",
            persist=False,
        )
        return reflection.reflection

async def supervisor(client: AsyncMemory):
    topics = ["pricing trends", "competitor features", "customer sentiment"]

    # Run workers concurrently
    results = await asyncio.gather(*[
        run_worker(topic, f"worker-{i}", client)
        for i, topic in enumerate(topics)
    ])

    # Supervisor aggregates findings into shared scope
    async with client.sessions.create(scopes=SUPERVISOR_SCOPE) as session:
        for topic, finding in zip(topics, results):
            await client.remember(
                f"[{topic}] {finding}",
                session_id=session.id,
                memory_category="knowledge",
            )

        # Supervisor synthesises and stores a final report
        await client.reflect(
            query="Synthesise all research findings into a coherent executive summary.",
            persist=True,
        )
```

## Scope hierarchy

SurrealDB Agent Memory resolves **scope visibility** from OR-of-AND clauses on each record. For typical single-owner tags, a query at scope `["org/acme"]` retrieves org-wide memory and shared org facts, but not another user’s private record info unless your grant covers them.

```text
{org: "acme"}                    ← visible to all org agents
{org: "acme", project: "alpha"}  ← visible to project alpha agents
{org: "acme", project: "alpha", agent: "planner"}  ← planner only
```

A supervisor querying at `{org: "acme", project: "alpha"}` sees:
- Everything at the project scope (shared findings)
- Everything at more specific scopes (individual worker findings)

A worker reading with a lens of `["org/acme/project/alpha/agent/worker-1"]` sees only its own memory and the shared project scope.

## Preventing memory pollution

When many agents write to a shared scope, unrelated facts from different tasks can accumulate. Use metadata to tag memory items with their provenance:

```python
await memory.remember(
    "Customer segment 'enterprise' values compliance features most.",
    memory_category="knowledge",
    labels=["source=customer-interview-2024-q3", "agent=interview-agent"],
)
```

Labels are `"key=value"` strings stamped on the rows the write produces. They
narrow reads (`labels` on `/query`) but never widen access - the scope predicate
is applied first.

Use the entity type system to keep memory structured. Rather than storing flat facts, extract structured entities so that conflicts are detected and superseded correctly:

```python
# Good: structured entity extraction will happen automatically from this
await memory.remember(
    "The enterprise customer segment prioritises SOC2 compliance over cost.",
)

# The extraction pipeline creates:
# entity: CustomerSegment/enterprise
# attribute: top_priority = "SOC2 compliance"
# relation: enterprise → values → compliance_features
```

## Handling write conflicts

When two agents write conflicting facts to the same scope at roughly the same time, SurrealDB Agent Memory's reconciliation pipeline detects the conflict and creates a supersession chain. The later write wins for attribute values. You can inspect the conflict:

```python
state = await memory.state()

# /state returns only the current view, so a row that carries `supersedes`
# is one that replaced an earlier value.
for attr in state.knowledge["attributes"]:
    if attr["supersedes"]:
        print(f"{attr['entity']}.{attr['key']} is now {attr['value']} (replaced an earlier value)")

# Walk the full chain for one key:
history = await memory.entities.history("person", "alice", "role")
```

## Supervisor reflection

The supervisor pattern works best with a dedicated reflection pass that runs after all workers complete:

```python
async with client.sessions.create(scopes=SUPERVISOR_SCOPE) as supervisor_session:
    # Load all worker contributions
    full_context = await client.profile()

    # Synthesise
    synthesis = await client.reflect(
        query="What are the top-level conclusions across all worker findings? What is still uncertain?",
        persist=True,
    )
    
    print(synthesis.summary)
```

The persisted reflection becomes part of the shared scope's long-term memory and is included in future `profile()` calls.

## JavaScript example

```javascript
import { AgentMemory } from "@surrealdb/memory";

const client = new AgentMemory({
    endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
    context: "acme-prod",
    apiKey: process.env.AGENT_MEMORY_API_KEY!,
});

const sharedScope = ["org/acme/project/market-research-q3"];

await client.remember(
    "Competitor X raised prices by 20% in Q2.",
    { scopes: [...sharedScope, "org/acme/project/market-research-q3/agent/worker-1"] },
);

const results = await client.recall("competitor pricing", { k: 10, lens: sharedScope });
console.log(results.hits);
```

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/build/personal-ai-assistant

# Personal AI assistant

End-user scoped memory with profiles and preferences.

This guide covers building a personal AI assistant that learns from every conversation and retains that knowledge across sessions. The assistant accumulates user preferences, biographical facts, current projects, and behavioural instructions. It injects relevant context automatically at the start of each new session so the experience feels continuous.

## What you are building

- A single-user Context scoped by `user_id`.
- Memory that spans multiple sessions: identity facts, knowledge, context-specific state, and instructions.
- A profile endpoint that produces a ready-to-inject system prompt fragment.
- The ability to forget outdated information when the user's situation changes.

## Step 1 - Create a session per conversation

Each conversation is a new session. The scope ties the session to the user so all extracted facts are associated with them.

```python
from surrealdb.memory import Memory

memory = Memory(context="assistant", api_key="sk-...")

async def start_conversation(user_id: str):
    session = await memory.sessions.create(
        scopes=[f"user/{user_id}"],
    )
    return session
```

```typescript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "assistant", apiKey: "sk-..." });

async function startConversation(userId: string) {
    const session = await memory.sessions.create({
        scopes: [`user/${userId}`],
    });
    return session;
}
```

## Step 2 - Inject memory at the start of each session

The profile endpoint returns a structured summary of everything SurrealDB Agent Memory knows about the user - identity attributes, active projects, preferences, and instructions - formatted as a system prompt fragment.

```python
async def build_system_prompt(user_id: str) -> str:
    profile = await memory.profile()

    base = "You are a personal AI assistant. Be concise, direct, and helpful."

    profile_block = format_profile(profile)   # see Profiles: injecting into prompts
    if profile_block:
        return f"{base}\n\n## What you know about this user\n\n{profile_block}"

    return base
```

```typescript
async function buildSystemPrompt(userId: string): Promise<string> {
    const profile = await memory.profile();

        const base = "You are a personal AI assistant. Be concise,
        direct, and helpful.";

    const profileBlock = formatProfile(profile);   // see Profiles: injecting into prompts
    if (profileBlock) {
        return `${base}\n\n## What you know about this user\n\n${profileBlock}`;
    }

    return base;
}
```

A formatted profile looks something like this after a few conversations:

```text
The user is Alice Chen, Head of Platform at Acme Corp. They prefer \
  TypeScript over JavaScript. They live in London. They prefer \
  bullet-point responses and dislike filler phrases. They are \
  currently leading a migration from CommonJS to ESM.
```

This summary is synthesised from the five memory categories: Identity (name, role, location), Knowledge (technical stack), Context (current project), and Instructions (response style preferences).

## Step 3 - Run the conversation loop

The simplest integration records each exchange as a pair of turns. SurrealDB Agent Memory extracts facts asynchronously and they are available for the next session.

```python
async def chat(session, user_id: str, user_message: str) -> str:
    # Build context-aware system prompt
    system = await build_system_prompt(user_id)

    # Retrieve session-specific relevant context
    ctx = await session.context(query=user_message)
    if ctx.items:
        system += f"\n\n## Relevant context\n\n{ctx.formatted}"

    # Call your LLM
    response = your_llm(system=system, user=user_message)

    # Record both turns
    await memory.remember(user_message, session_id=session.id, role="user")
    await memory.remember(response, session_id=session.id, role="assistant")

    return response
```

```typescript
async function chat(session: Session, userId: string,
    userMessage: string): Promise<string> {
    // Build context-aware system prompt
    let system = await buildSystemPrompt(userId);

    // Retrieve session-specific relevant context
        const ctx = await session.context({ query: userMessage });
    if (ctx.items.length > 0) {
        system += `\n\n## Relevant context\n\n${ctx.formatted}`;
    }

    // Call your LLM
    const response = await yourLlm({ system, user: userMessage });

    // Record both turns
    await memory.remember(userMessage, { sessionId: session.id, role: "user" });
    await memory.remember(response, { sessionId: session.id, role: "assistant" });

    return response;
}
```

## Step 4 - How memory builds up

After the user says "I prefer bullet-point responses", the extraction pipeline creates an `Instruction` record:

```json
{
  "category": "instructions",
  "key": "response_format",
  "value": "Use bullet points. Avoid filler phrases.",
  "source_turn": "turn:01jt4m...",
  "valid_from": "2024-11-15T10:23:00Z"
}
```

After the user says "I just moved from Berlin to London", the extraction pipeline updates the `location` attribute on the user's `Person` entity and creates a supersession chain:

```json
{
  "entity": "entity:[\"Person\", \"alice\"]",
  "category": "identity",
  "key": "location",
  "value": "London",
  "previous_value": "Berlin",
  "action": "updated",
  "valid_from": "2024-11-15T10:25:00Z"
}
```

The old value is retained in the supersession chain for auditability but no longer appears in the active profile.

## Step 5 - Using `chat()` instead of `remember()`

If you want SurrealDB Agent Memory to manage the agent call rather than just recording turns, use the `chat()` endpoint. It will retrieve context, call the Context's configured synthesis model, persist both turns, and run extraction with no callback to supply. Override the model for a single call with `model` if you need to.

```python
session = await memory.sessions.create(scopes=[f"user/{user_id}"])

result = await memory.chat("What are my current projects?", session_id=session.id)
print(result["reply"])
print(result["citations"])       # one entry per [S1] marker in the reply
print(result["memoryUpdates"])   # extraction diff from the user turn
```

```typescript
const session = await memory.sessions.create({ scopes: [`user/${userId}`] });

const result = await memory.chat("What are my current projects?", { sessionId: session.id });
console.log(result.reply);
console.log(result.citations);
console.log(result.memoryUpdates);
```

The `chat()` call returns `reply`, `citations`, `memoryUpdates`, `sessionId`, and `traceId`. Because the model is configured on the Context rather than passed in, use the `remember()` + your-own-LLM shape when you need custom prompting, tool use, or streaming to your UI.

## Step 6 - Forgetting outdated information

When a user's situation changes significantly - they change jobs, finish a project, move city - you can explicitly forget stale facts rather than waiting for the supersession chain to handle it via new turns.

`forget` is **query-driven**, not field-driven: you describe what to forget in
natural language and SurrealDB Agent Memory matches the facts within the caller's
`memory:forget` region.

```python
# Preview first - dry_run returns the would-be count without writing
preview = await memory.forget("Alice's employer", dry_run=True)
print(preview["deleted"])

# Then apply
await memory.forget("Alice's employer")
```

```typescript
const preview = await memory.forget("Alice's employer", { dryRun: true });
console.log(preview.deleted);

await memory.forget("Alice's employer");
```

To erase an entire branch rather than matched facts, use the scope-level route
**`POST /scopes/forget`** with the subtree path (`user/alice/`), or delete a single
entity with **`DELETE /entities/{type}/{name}`**.

`forget` soft-deletes the matched facts (sets `valid_until` to now) and removes
them from future retrievals, keeping prior rows for audit. Pass `purge=True` to
also remove the supersession history - that is the right-to-be-forgotten path and
is irreversible.

## Memory categories in a personal assistant

| Category | Examples |
|---|---|
| Identity | Name, location, occupation, family |
| Knowledge | Domain expertise, tools, languages, opinions |
| Context | Current projects, recent events, open tasks |
| Instructions | Response style, formatting preferences, topics to avoid |
| Unknowns | Contradictory or uncertain statements flagged for review |

The profile endpoint surfaces all five categories in a single call, prioritising high-confidence, recently validated facts.

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/migrate/from-langmem

# Migrate from LangMem

Moving LangChain memory to SurrealDB Agent Memory.

LangMem is LangChain's in-process memory library, typically backed by a local vector store or an in-memory store. This guide covers the concept mapping and migration path to SurrealDB Agent Memory.

## Concept mapping

| LangMem concept | SurrealDB Agent Memory equivalent | Notes |
|---|---|---|
| Namespace | Scope + Context | SurrealDB Agent Memory uses **scope tags** within a named Context |
| Memory (document) | Entities + attributes | SurrealDB Agent Memory extracts structure; LangMem stores flat text |
| `put_memories()` | `remember()` | Write path is similar; extraction differs |
| `search_memory()` | `recall()` | SurrealDB Agent Memory adds graph-density reranking |
| `get_memories()` | `profile()` | Returns structured snapshot |
| Memory type (`semantic`, `episodic`, `procedural`) | Memory category (`knowledge`, `context`, `instructions`) | Categories have different volatility and expiry |
| `delete_memories()` | `forget()` | Query-driven; `POST /scopes/forget` erases a whole subtree |
| InMemoryStore | Embedded SurrealDB Agent Memory (in-process SurrealDB) | See the embedded deployment guide |

## Migration example

### LangMem (Python)

```python
from langgraph.store.memory import InMemoryStore
from langmem import create_memory_store_manager

store = InMemoryStore(
    index={"dims": 1536, "embed": embeddings}
)
memory = create_memory_store_manager(
    "openai/gpt-4o",
    namespace=("user", "alice"),
    store=store,
)

await memory.aput(
    [{"content": "Alice prefers concise, technical answers."}]
)
results = await memory.asearch("communication style")
```

### SurrealDB Agent Memory equivalent

```python
from surrealdb.memory import AsyncMemory

client = AsyncMemory(
    context="dev",
    endpoint="https://spectron.example.com",
    api_key="sk-...",
)

await client.remember(
    "Alice prefers concise, technical answers.",
    scopes=["user/alice"],
)
results = await client.recall("communication style", k=5, lens=["user/alice"])
for hit in results.hits:
    print(hit.text)
```

## Key differences

**Persistence**: LangMem with `InMemoryStore` loses all memory when the process restarts. SurrealDB Agent Memory is durable by default - all memory lives in SurrealDB and survives restarts, deployments, and crashes.

**Structured extraction**: LangMem stores memories as text documents. SurrealDB Agent Memory extracts structured entities, attributes, and relations. "Alice prefers concise answers" becomes an entity `Person/alice` with attribute `communication_style = "concise, technical"` - queryable and updatable as structured data.

**Conflict handling**: LangMem stores all memories and relies on the retrieval layer to resolve conflicts via recency ranking. SurrealDB Agent Memory detects contradictions and supersedes old attribute values, maintaining a correct, single current value with a history chain.

**Namespace vs scope**: LangMem uses a tuple namespace `("user", "alice")`. SurrealDB Agent Memory uses hierarchical slash paths like `["user/alice"]`. For single-clause scopes, org-wide queries surface org-tagged memory while hiding user-specific records - see [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md).

**Categorisation**: SurrealDB Agent Memory's categories map loosely to LangMem memory types:
- LangMem `semantic` → `knowledge` (facts, preferences)
- LangMem `episodic` → `context` (recent, auto-expiring events)
- LangMem `procedural` → `instructions` (behavioural directives)

## LangChain integration

SurrealDB Agent Memory ships a LangChain memory adapter (planned). Until it is released, use the SDK directly and inject the formatted context into your chain or graph:

```python
from langchain_core.messages import SystemMessage
from surrealdb.memory import AsyncMemory

client = AsyncMemory(context="dev", endpoint="...", api_key="...")

class SpectronMemory:
    def __init__(self, client: AsyncMemory, scope: list[str]):
        self.client = client
        self.scope = scope

    async def load_context(self, query: str) -> str:
        results = await self.client.recall(query, k=5, lens=self.scope)
        return "\n".join(hit.text for hit in results.hits)

    async def save_turn(self, role: str, content: str):
        await self.client.remember(f"{role}: {content}", scopes=self.scope)

# Usage in a LangGraph node
async def agent_node(state, memory: SpectronMemory):
    context = await memory.load_context(state["messages"][-1].content)
    messages = [
        SystemMessage(content=f"Memory:\n{context}"),
        *state["messages"],
    ]
    response = await llm.ainvoke(messages)
    await memory.save_turn("assistant", response.content)
    return {"messages": [response]}
```

## Migrating from LangChain ConversationBufferMemory

If you are using the older LangChain `ConversationBufferMemory` or similar in-context memory, migration is straightforward: replace the buffer with SurrealDB Agent Memory sessions. Instead of passing the full conversation history as context (which grows without bound), pass a recalled summary from SurrealDB Agent Memory.

```python
# Before: buffer-based
memory = ConversationBufferMemory()
chain = ConversationChain(llm=llm, memory=memory)

# After: Memory-based
async with client.sessions.create(scopes=[f"user/{user_id}"]) as session:
    # At turn start, recall relevant context
    context = await client.recall(user_message, k=5)

    # Pass context as part of the system prompt instead of full history
    response = await llm.ainvoke([
        SystemMessage(content=f"Relevant context:\n{context['hits']}"),
        HumanMessage(content=user_message),
    ])

    # Store the turn
    await client.remember(user_message, session_id=session.id, role="user")
    await client.remember(response.content, session_id=session.id, role="assistant")
```

This approach scales indefinitely - context window size is bounded by `k` on recall, not by conversation length.

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/migrate/from-mem0

# Migrate from Mem0

Mapping concepts and incremental migration.

This guide maps Mem0 concepts to their SurrealDB Agent Memory equivalents, then an incremental migration that can run both systems in parallel.

## Concept mapping

| Mem0 concept | SurrealDB Agent Memory equivalent | Notes |
|---|---|---|
| User ID | Scope dimension `user` | SurrealDB Agent Memory scopes are multi-dimensional; `user` is one axis |
| Agent ID | Scope dimension `agent` | Combine with `user` for agent-per-user isolation |
| Run ID | Session | SurrealDB Agent Memory sessions are first-class records with richer metadata |
| Memory (flat string) | Entities + attributes + relations | SurrealDB Agent Memory extracts structured triples from text |
| `add()` | `remember()` | SurrealDB Agent Memory also deduplicates and reconciles on write |
| `search()` | `recall()` | SurrealDB Agent Memory adds graph-density reranking |
| `get_all()` | `profile()` | Returns structured snapshot, not a flat list |
| `delete()` | `forget()` | Query-driven; `POST /scopes/forget` erases a whole subtree |
| History | Session turns + temporal attributes | SurrealDB Agent Memory tracks `valid_from`/`valid_until` on attributes |

## SDK migration

### Python

**Mem0:**
```python
from mem0 import Memory

m = Memory()
m.add("I prefer vegetarian food.", user_id="alice")
results = m.search("What are Alice's food preferences?", user_id="alice")
```

**SurrealDB Agent Memory:**
```python
from surrealdb.memory import AsyncMemory

client = AsyncMemory(
    context="dev",
    endpoint="http://localhost:9090",
    api_key="sk-...",
)

await client.remember("I prefer vegetarian food.", scopes=["user/alice"])
results = await client.recall("What are Alice's food preferences?", k=5, lens=["user/alice"])
for hit in results.hits:
    print(hit.text)
```

### JavaScript

**Mem0:**
```javascript
import { Memory } from "mem0ai";

const m = new Memory();
await m.add("I prefer vegetarian food.", { user_id: "alice" });
const results = await m.search("food preferences", { user_id: "alice" });
```

**SurrealDB Agent Memory:**
```javascript
import { AgentMemory } from "@surrealdb/memory";

const client = new AgentMemory({
  endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
  context: "dev",
  apiKey: process.env.AGENT_MEMORY_API_KEY!,
});

await client.remember("I prefer vegetarian food.", { scopes: ["user/alice"] });
const results = await client.recall("What are Alice's food preferences?", { k: 5, lens: ["user/alice"] });
console.log(results.hits);
```

## Key differences

**Structured extraction**: Mem0 stores memories as flat text strings. SurrealDB Agent Memory extracts structured entities, attributes, and relations. When you write "Alice prefers vegetarian food", it creates an entity `Person/alice` with attribute `food_preference = "vegetarian"`. Future writes that contradict this (e.g. "Alice now eats fish") update the attribute with a supersession chain, so you can see the history.

**Conflict resolution**: Mem0 stores new memories alongside old ones. SurrealDB Agent Memory detects when a new memory contradicts a stored attribute and automatically supersedes the old value. The old value is not deleted - it is marked as superseded with a timestamp.

**Scoped multi-tenancy**: Mem0 uses separate `user_id` and `agent_id` parameters. SurrealDB Agent Memory uses **slash-path scope** (for example `["org/acme"]`). A query at `org/acme` can retrieve org-wide memory across users.

**Authoritative versus experiential**: Mem0 treats all memory uniformly. SurrealDB Agent Memory models **eight pillars** of agent memory; among them, **Authoritative** curated content (ingested documents, knowledge nodes) is distinct from **Experiential** conversational memory (turns and the **six memory categories**). Reconciliation gives **Authoritative** precedence when they conflict. See [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md).

## Incremental migration strategy

### Step 1: run both systems in parallel

Wrap your memory calls in a thin adapter that writes to both Mem0 and SurrealDB Agent Memory:

```python
class MemoryAdapter:
    def __init__(self, mem0_client, spectron_client):
        self.mem0 = mem0_client
        self.spectron = spectron_client
    
    async def add(self, content: str, user_id: str):
        # Write to both
        self.mem0.add(content, user_id=user_id)
        async with self.spectron.sessions.create(scopes=[f"user/{user_id}"]) as s:
            await s.remember(content)
    
    async def search(self, query: str, user_id: str, use_spectron: bool = False):
        if use_spectron:
            async with self.spectron.sessions.create(scopes=[f"user/{user_id}"]) as s:
                return await s.recall(query, k=5)
        return self.mem0.search(query, user_id=user_id)
```

### Step 2: validate recall quality

Compare recall results between the two systems for a sample of production queries. Use the `use_spectron=True` flag on a percentage of traffic while monitoring for quality regressions.

### Step 3: migrate existing memories

Export existing Mem0 memories and replay them into SurrealDB Agent Memory:

```python
existing_memories = m.get_all(user_id="alice")

async with client.sessions.create(scopes=["user/alice"]) as session:
    for item in existing_memories:
        await client.remember(item["memory"], session_id=session.id)
```

SurrealDB Agent Memory's reconciliation pipeline deduplicates on write, so replaying memories that contain the same facts will produce correct structured state rather than duplicates.

### Step 4: cut over

Once recall quality is satisfactory, remove the dual-write and switch reads to SurrealDB Agent Memory only.

## Session management difference

Mem0's `add()` takes a `user_id` directly - there is no concept of a session. SurrealDB Agent Memory requires a session as context for each turn. The nearest equivalent to Mem0's `add()` is:

```python
# One-shot add without a long-lived session
async with client.sessions.create(scopes=[f"user/{user_id}"]) as session:
    await client.remember(content, session_id=session.id, role="user")
# Session closes automatically; memory is extracted and persisted
```

If your application does not have natural session boundaries (e.g. it stores individual facts rather than conversations), create a short-lived session for each batch of writes and close it immediately.

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/migrate/from-vector-store

# Migrate from a vector store

From chunk-only search to structured memory.

Vector stores are a common first step for adding memory to AI agents. They work well for simple document retrieval but fall short when you need structured extraction, provenance tracking, temporal validity, or authority-based conflict resolution. This guide covers what you gain by migrating to SurrealDB Agent Memory and how to execute the migration.

## What you gain

| Capability | Vector store | SurrealDB Agent Memory |
|---|---|---|
| Document retrieval | Semantic similarity search | Hybrid (semantic + BM25 + graph traversal) |
| Structured extraction | None - raw chunks | Entities, attributes, and relations |
| Provenance | None - chunk origin only | Every fact traces back to the turn that produced it |
| Correction tracking | None | Supersession chains with full history |
| Temporal validity | None | `valid_from` / `valid_until` on every attribute |
| Authoritative precedence | None | **Authoritative** pillar wins over **Experiential** assertions when they conflict |
| Scope-based isolation | Manual metadata filtering | Dimensional scope with floor matching |
| State inspection | None | Query memory state directly |

Some typical reasons to migrate are: contradictions accumulating with no way to tell which fact is current, no way to correct or expire a fact, no queryable view of what the agent knows, or metadata filtering that no longer scales.

## Migration steps

### 1. Export your existing documents

Export the source documents from your vector store. For most systems, this means exporting the original text rather than the embeddings - SurrealDB Agent Memory re-indexes everything through its own pipeline.

```python
# Example: export from Pinecone
import pinecone

index = pinecone.Index("my-index")
# Fetch all vectors with metadata (Pinecone requires pagination)
results = index.query(
    vector=[0.0] * 1536,
    top_k=10000,
    include_metadata=True,
)
documents = [
    {"text": r.metadata["text"], "source": r.metadata.get("source", "")}
    for r in results.matches
]
```

```typescript
// Example: export from a generic vector store
const documents = await vectorStore.fetchAll({ includeMetadata: true });
const exportedDocs = documents.map(doc => ({
    text: doc.metadata.text,
    source: doc.metadata.source ?? "",
}));
```

### 2. Upload documents to authoritative knowledge

Ingest your exported documents into SurrealDB Agent Memory's authoritative knowledge layer. Each document is processed through the ingestion pipeline: chunked, keyword-extracted, and linked to knowledge nodes.

```python
import os
import httpx

client = httpx.Client(
    base_url="https://spectron.surrealdb.com/api/v1/my-context",
    headers={"Authorization": f"Bearer {os.environ['AGENT_MEMORY_API_KEY']}"},
)

for doc in documents:
    client.post(
        "/documents",
        files={"file": (
            "document.txt",
            doc["text"].encode(),
            "text/plain",
        )},
        data={
            "title": doc.get("source", "Imported document"),
            "source_url": doc.get("source", ""),
        },
    )
```

```typescript
for (const doc of exportedDocs) {
    const formData = new FormData();
    formData.append("file", new Blob([doc.text], { type: "text/plain" }), "document.txt");
    formData.append("title", doc.source || "Imported document");
    formData.append("source_url", doc.source || "");

    await fetch("https://spectron.surrealdb.com/api/v1/my-context/documents", {
        method: "POST",
        headers: { "Authorization": "Bearer mgmt_..." },
        body: formData,
    });
}
```

SurrealDB Agent Memory's ingestion pipeline handles chunking internally. You do not need to replicate the chunking strategy from your vector store.

### 3. Handle the metadata gap

Vector stores often have minimal, inconsistent, or missing metadata. SurrealDB Agent Memory's pipeline extracts structure from content, so missing metadata is less critical - but it is worth enriching documents before ingestion if you have source information available.

If your vector store metadata includes document type, date, or author information, include it in the document upload:

```python
client.post(
    "/documents",
    files={"file": ("policy.md", content, "text/markdown")},
    data={
        "title": "Return policy",
        "content_type": "policy",
        "authored_at": "2024-01-15T00:00:00Z",
    },
)
```

### 4. Migrate conversational memory

If your vector store also held per-user conversational memory (previous chat turns or extracted facts stored as vectors), re-ingest them as SurrealDB Agent Memory turns:

```python
# For each user, create a session and ingest their history
for user_id, history in user_histories.items():
    session = await memory.sessions.create(
        scopes=[f"org/my-org/user/{user_id}"],
    )
    for message in history:
        await memory.remember(message["content"], session_id=session.id, role=message["role"])
```

```typescript
for (const [userId, history] of Object.entries(userHistories)) {
    const session = await memory.sessions.create({
        scopes: [`org/my-org/user/${userId}`],
    });
    for (const message of history) {
        await memory.remember(message.content, { sessionId: session.id, role: message.role });
    }
}
```

The extraction pipeline runs on each turn and re-derives structured entities and attributes from the conversation history. You do not need to manually map old vector metadata to SurrealDB Agent Memory's schema.

## Coexistence strategy

You do not need to cut over immediately. Run SurrealDB Agent Memory and your vector store in parallel for a period:

1. **Write to both** - record turns in SurrealDB Agent Memory and continue writing to the vector store.
2. **Read from SurrealDB Agent Memory first** - use its context retrieval as primary; fall back to the vector store if it returns nothing.
3. **Validate** - compare the quality of responses with SurrealDB Agent Memory context versus vector store context over a sample of real queries.
4. **Cut over** - once satisfied, remove the vector store read path.

```python
async def retrieve_context(user_id: str, query: str) -> str:
    # Try Memory first
    ctx = await session.context(query=query)
    if ctx.items:
        return ctx.formatted

    # Fall back to vector store during transition
    chunks = vector_store.search(query=query, user_id=user_id, top_k=6)
    return "\n\n".join(c["text"] for c in chunks)
```

## Timeline

| Week | Activity |
|---|---|
| 1 | Export documents; begin authoritative knowledge ingestion |
| 2 | Begin recording new conversations as SurrealDB Agent Memory turns |
| 3-4 | Coexistence: SurrealDB Agent Memory primary, vector store fallback |
| 5 | Validate response quality; remove fallback |
| 6+ | Re-ingest historical conversational memory if needed |

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/migrate/from-zep

# Migrate from Zep

Mapping Zep concepts to SurrealDB Agent Memory's unified graph.

Zep and SurrealDB Agent Memory both target **durable agent memory**, but SurrealDB Agent Memory uses a **single SurrealDB graph**, explicit reconciliation, and a focused HTTP surface (`/facts`, `/documents`, `/query`, `/chat`).

## Concept mapping

| Zep concept | SurrealDB Agent Memory equivalent | Notes |
| --- | --- | --- |
| User | Scope path `user=…` | Scopes are hierarchical path strings |
| Session | Session + optional `session_id` on `/facts/batch` | Transcript introspection; batch ingest preferred |
| Memory context | `POST .../context` | Formatted string for prompts |
| Facts | Entities + attributes + relations | Extracted and reconciled |
| Zep Graph / knowledge | **Documents** + extracted graph | One unified graph |
| Summary | `profile`, `reflect`, consolidation | Reflection is explicit `POST .../reflect` |

## API mapping

| Zep-style action | SurrealDB Agent Memory |
| --- | --- |
| Add message to session | `POST /api/v1/{ctx}/facts/batch` with `messages[]` |
| Search memory | `POST /api/v1/{ctx}/query` |
| Get memory context string | `POST /api/v1/{ctx}/context` |
| Add business data | `POST /api/v1/{ctx}/documents` or `/facts` with `infer: "triples"` |

Auth header: **`Authorization: Bearer`**. Base path: **`/api/v1/{context_id}/`**.

## SDK sketch

```python
await client.remember_many(
    messages=[{"role": "user", "content": "I was promoted to Head of Platform."}],
    scopes=["org/acme/user/alice"],
)

hits = await client.recall(
    "What is Alice's role?",
    k=10,
    lens=["org/acme/user/alice"],
)
```

## Behavioural differences

1. **Reconciliation** - conflicting sources emit `uncertainty` instead of silent overwrite.
2. **Authoritative vs experiential** - `source.kind` distinguishes document vs turn provenance.
3. **Unified recall** - one `/query` router over facts and passages.

## Further reading

- [REST API](/docs/agent-memory/reference/rest-api.md)
- [Eight pillars](/docs/agent-memory/architecture/eight-pillars-and-categories.md)
- [Unified substrate](/docs/agent-memory/mental-model/two-layer-architecture.md)

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/patterns/adding-memory-to-existing-app

# Adding memory to an existing app

Introduce turns without replacing your LLM client.

Most teams do not rebuild their application to add memory. This guide covers the minimal integration path: intercepting existing LLM calls to extract memory, injecting context before those calls, and gradually expanding the integration without disrupting what already works.

## The minimal integration

SurrealDB Agent Memory's minimum viable integration is two operations around your existing LLM call:

1. **Before the call** - retrieve relevant context and prepend it to the system prompt.
2. **After the call** - record the user message and assistant response as turns.

No sessions are required for the first pass. You keep your existing data model and LLM client; SurrealDB Agent Memory sits beside them as the memory layer.

```python
from surrealdb.memory import Memory

memory = Memory(context="my-app", api_key="sk-...")

CONTEXT_ID = "my-app"
DEFAULT_SCOPE = ["org/my-org"]  # Start with a single scope

async def call_llm_with_memory(user_message: str,
    session_id: str | None = None) -> str:
    # 1. Create a session on first call; afterwards carry the id forward.
    #    There is no "open" call - a session is addressed by its id.
    if session_id is None:
        session = await memory.sessions.create(scopes=DEFAULT_SCOPE)
        session_id = session.id

    ctx = await memory.sessions.context(session_id, query=user_message)

    # 2. Inject into your existing system prompt
    system = your_existing_system_prompt()
    if ctx.formatted:
        system = f"{system}\n\n## Relevant context\n{ctx.formatted}"

    # 3. Your existing LLM call - unchanged
    response = your_existing_llm_call(system=system, user=user_message)

    # 4. Record the exchange
    await memory.remember(user_message, session_id=session_id, role="user")
    await memory.remember(response, session_id=session_id, role="assistant")

    return response
```

```typescript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "my-app", apiKey: "sk-..." });

const DEFAULT_SCOPE = ["org/my-org"];

async function callLlmWithMemory(
    userMessage: string,
    sessionId?: string,
): Promise<{ response: string; sessionId: string }> {
    const id = sessionId
        ?? (await memory.sessions.create({ scopes: DEFAULT_SCOPE })).id;

    const ctx = await memory.sessions.context(id, { query: userMessage });

    let system = yourExistingSystemPrompt();
    if (ctx.formatted) {
        system = `${system}\n\n## Relevant context\n${ctx.formatted}`;
    }

    const response = await yourExistingLlmCall({ system, user: userMessage });

    await memory.remember(userMessage, { sessionId: id, role: "user" });
    await memory.remember(response, { sessionId: id, role: "assistant" });

    return { response, sessionId: id };
}
```

The key principle: **do not change what your LLM receives if there is no relevant memory**. The `if ctx.formatted` guard ensures that when SurrealDB Agent Memory has nothing useful to add, the call is identical to the original.

## Intercepting existing LLM calls

If your application already has a wrapper around LLM calls, add memory extraction at that layer. This avoids scattering SurrealDB Agent Memory calls throughout your codebase.

```python
# Before: a simple LLM wrapper
async def llm(system: str, user: str) -> str:
    return await openai_client.chat(system=system, user=user)

# After: the same wrapper with memory
async def llm(system: str, user: str,
    session_id: str | None = None) -> str:
    if session_id:
        ctx = await memory.sessions.context(session_id, query=user)
        if ctx.formatted:
            system = f"{system}\n\n{ctx.formatted}"

    response = await openai_client.chat(system=system, user=user)

    if session_id:
        await memory.remember(user, session_id=session_id, role="user")
        await memory.remember(response, session_id=session_id, role="assistant")

    return response
```

```typescript
// Before
async function llm(system: string, user: string): Promise<string> {
    return openaiClient.chat({ system, user });
}

// After
async function llm(system: string, user: string,
    sessionId?: string): Promise<string> {
    let enrichedSystem = system;

    if (sessionId) {
        const ctx = await memory.sessions.context(sessionId, { query: user });
        if (ctx.formatted) enrichedSystem = `${system}\n\n${ctx.formatted}`;
    }

    const response = await openaiClient.chat({ system: enrichedSystem, user });

    if (sessionId) {
        await memory.remember(user, { sessionId, role: "user" });
        await memory.remember(response, { sessionId, role: "assistant" });
    }

    return response;
}
```

Making `session_id` optional means the change is backwards-compatible - all existing call sites continue to work without passing a session.

## Starting with a single scope

Do not attempt multi-user scoping on day one. Start with a single organisational scope and confirm the extraction pipeline is working correctly before splitting by user.

```python
# Phase 1: single scope, all conversations share it
DEFAULT_SCOPE = ["org/my-app"]

# Phase 2 (later): add user dimension
def scope_for_user(user_id: str) -> dict:
    return [f"org/my-app/user/{user_id}"]
```

The profile and context endpoints are scope-matched: a scope of `["org/my-app/user/alice"]` matches memory stored at that path and also memory stored under the broader `["org/my-app"]` path (hierarchical visibility). Starting broad and narrowing later does not require rewriting stored records.

## Expanding to multi-user

Once single-scope extraction is verified, add the user dimension. The only change is in how you create sessions:

```python
# Before
session = await memory.sessions.create(scopes=["org/my-app"])

# After
session = await memory.sessions.create(scopes=[f"org/my-app/user/{user_id}"])
```

Existing memory under the org-only scope remains readable, since the org path is an ancestor of the user path. New memory is stored under the user scope and is only visible to that user's context retrievals.

## Profile injection at session start

Once you have per-user memory accumulating, add profile injection at the start of each new conversation:

```python
async def start_session(user_id: str) -> tuple[str, str]:
    session = await memory.sessions.create(
        scopes=[f"org/my-app/user/{user_id}"],
    )

    profile = await memory.profile()
    system = your_existing_system_prompt()
    profile_block = format_profile(profile)   # see Profiles: injecting into prompts
    if profile_block:
        system = f"{system}\n\n## About this user\n{profile_block}"

    return session.id, system
```

```typescript
async function startSession(userId: string): Promise<{ sessionId: string; system: string }> {
    const [session, profile] = await Promise.all([
        memory.sessions.create({ scopes: [`org/my-app/user/${userId}`] }),
        memory.profile(),
    ]);

    let system = yourExistingSystemPrompt();
    const profileBlock = formatProfile(profile);   // see Profiles: injecting into prompts
    if (profileBlock) {
        system = `${system}\n\n## About this user\n${profileBlock}`;
    }

    return { sessionId: session.id, system };
}
```

## Migration path summary

| Phase | What you add | What changes |
|---|---|---|
| 1 - Extraction only | Record turns after each LLM call | Memory accumulates but is not used |
| 2 - Context injection | Retrieve context and inject before each call | Responses become memory-aware |
| 3 - Profile injection | Inject profile at session start | New sessions start with full user context |
| 4 - Per-user scoping | Add `user` scope dimension | Memory is isolated per user |
| 5 - authoritative knowledge | Ingest authoritative documents | Agents answer from authoritative knowledge |

Each phase is independently deployable and backwards-compatible with the previous one.

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/patterns/event-driven-game-client

# Event-driven game client

Pipe RPG or simulation events into SurrealDB Agent Memory with epistemic labels and careful extraction.

This page details how SurrealDB Agent Memory can sit behind a **listener**: a small companion process that receives events from a game engine, simulator, or other non-chat source, then calls SurrealDB Agent Memory to remember and (optionally) think aloud. This allows the player or game testers to receive extra information about the current situation in a role-playing game or to simulate the thoughts of the main character during a quest.

The advice is generalised for RPG-style and simulation clients - which events to send, how to label them, and how to keep extraction from inventing a denser world than the player has actually inhabited. **Ultima VII** (via Exult and a SurrealDB Agent Memory sidecar) is the game that formed the basis for this cookbook: the patterns below are what that integration validated, not Ultima-only mechanics.

You do not replace the game. The engine stays the source of truth for physics and UI. SurrealDB Agent Memory holds what the hero (or operator) has learned, heard, read, and done, and can answer in an inner-voice or coach mode via `/chat`.

## Architecture

```text
Game / engine
    │  event hooks (talk, travel, read, combat, …)
    ▼
Listener (HTTP or IPC)
    ├── chronicle / local UI (optional)
    ├── POST /facts - remember with labels + infer mode
    ├── POST /documents - seed lore, long texts
    └── POST /chat - short reflections, greetings, musings
```

Keep the listener thin:

1. Translate engine payloads into a small, stable event schema.
2. Decide whether each event should reach SurrealDB Agent Memory (many clicks are noise).
3. Wrap writes with epistemic and authority labels so extraction does not treat every noun as lived truth.
4. Call `/chat` only when you want prose in a known voice, not for every tick.

## Design the event surface first

The best way to generate meaningful output from SurrealDB Agent Memory is to pipe it events that most closely mimic the experience of the character in a game in the way it would be experienced in real life. For example, while a game engine might emit exact tile location of a character at every step, an actual character in such a setting would only be aware of vague relative movement. The following shows the types of events that are a good fit to pass on to SurrealDB Agent Memory:

| Event family | Typical meaning | Usually remember? | Usually `/chat`? |
| --- | --- | --- | --- |
| Conversation open / line / choice / close | Spoken testimony | Yes (transcript snapshot) | Greeting / farewell |
| Travel / situation snapshots | Glance-first place + optional net move | Yes (situation, not every step) | Soft location muse |
| Relocate / teleport | Sudden party move (gates, cutscenes) | Yes (arrival + reset travel) | Optional arrival thought |
| Examine / look | Label only | Often no (local chronicle only) | Rarely |
| Container open (glimpse) | Seen, not taken | Yes, as **observed** | Rarely |
| Book / scroll / sign | Written glance | Yes, as **read** | Optional thought |
| Combat / sleep / death | State change | Yes, short fact | Death / wake thought |
| Party join / leave | Companions | Yes | Short reaction |
| Map / sextant reading | Geography reading | Yes, as written | Optional |
| Item take / drop / bark | Theft, protest, chase | Yes when distinct from glance | Strong reaction once |

It is preferable to only transmit events that take place via active player interaction. For example, examine events (“a tree”, “a crate”) can flood a live session if they are transmitted simply through running through a map, but can be good to pass on if they require a specific action such as a mouse click. Opening a chest without taking anything is different: one short “looked inside, did not take” fact is often worth remembering later.

Assign every actor a **stable id** from the engine (`npc_id`, character key) and keep appearance labels (“a guard”, “shopkeeper”) separate until dialogue reveals a personal name. Many game engines have specific names for NPCs that can leak identity the hero has not earned yet, or even nicknames that are used as inside jokes for the developers when building the game.

## Epistemic framing: lived, spoken, written, read

Extraction with `infer=full` will build an entity graph from whatever prose you send. That helps for armour tables and goes wrong on parody legalese or flavour text. Stamp each write so you can tell how the hero knows.

Suggested `labels` (key=value tags on `/facts` and document metadata):

| Label | Use when |
| --- | --- |
| `source=game` | Came from the engine listener |
| `kind=conversation\|book\|sign\|travel\|combat\|container\|…` | Event family |
| `authority=lived\|spoken\|written\|seen` | Deed vs hearsay vs document vs glance |
| `epistemic=read\|heard\|observed\|done` | How knowledge arrived |
| `era=…` | Optional chapter / prior-campaign pack |

Wrap the remembered body so the extractor cannot miss the frame:

```http
POST /api/v1/{context_id}/facts
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "text": "Epistemic frame: the hero has just READ this document in the game.\nThis is a glance, not a deed. Do not invent participation in activities named only on the page.\nDo not explode parody legalese into a dense entity graph; prefer one short summary of what the paper claims.\n\nTitle: \"Bill of Underwater Scavenging…\"\nFull text:\n…",
  "infer": "full",
  "memory_category": "knowledge",
  "labels": [
    "source=game",
    "kind=book",
    "authority=written",
    "epistemic=read"
  ]
}
```

Contrast with a deed:

```json
{
  "text": "The hero entered combat outdoors at dawn under clear weather.",
  "infer": "full",
  "labels": ["source=game", "kind=combat", "authority=lived", "epistemic=done"]
}
```

Or spoken testimony after a conversation closes:

```json
{
  "text": "Conversation with npc_id=21 (appeared as \"shopkeeper\").\nTranscript:\n…\nUse a personal name only if the transcript shows it was revealed.",
  "infer": "full",
  "memory_category": "context",
  "labels": ["source=game", "kind=conversation", "authority=spoken", "epistemic=heard"]
}
```

Container glimpse (opened, not looted):

```json
{
  "text": "Epistemic frame: the hero OPENED a container and looked inside.\nThis is a glimpse, not ownership. Do not invent that the hero took, stole, or now owns these items.\n\nContainer: \"oak chest\". Contents seen: a dagger, three gold coins.\nPrefer third person: the hero looked inside the oak chest and saw …",
  "infer": "full",
  "labels": [
    "source=game",
    "kind=container",
    "authority=seen",
    "epistemic=observed"
  ]
}
```

Note that while labels guide filtering and intent, they are not a substitute for clear prose. Putting `epistemic=read` on a fact whose body says “the hero engages in underwater scavenging” still invites the wrong graph. State the frame in plain language and on labels.

## Relation direction vs spoken wording

NPC dialogue often puts the organisation in the subject seat even when the durable fact runs the other way.

Example line:

> “The guild is the philosophical society devoted to the teachings of a truly great man named Batlin.”

`infer=full` can emit `the_guild->is_devoted_to->batlin` because that follows English syntax. Canonically you usually want **Batlin founded / leads the guild**, and devotion (if kept) to point at **teachings** or a philosophy node, not at collapsing “teachings of Batlin” into the person.

When faction lore matters:

- Prefer a short normalisation in the remembered body (“Batlin founded the guild. Members follow his triad.”) so extraction sees the leadership edge.
- Or `infer=none` for a hand-written summary of key lore talks.
- Avoid expecting soft predicates like `is_devoted_to` to stand in for `founded_by` / `led_by` / `member_of`.

## High-value spoken facts

Passwords, personal names revealed in dialogue, and “who runs this place” lines are easy for players to need hours later.

In the remember body for a closed conversation:

- Call out discovery lines in plain language after the transcript (“The clerk revealed the gate password is Nightjar.”).
- Prefer third person for the durable store: “The shopkeeper said the blacksmith’s son is called Rowan.”
- Do not rely on `/chat` alone to cement those facts; chat may hedge or under-enumerate until the operator escalates.

Guest registers and other written lists retrieve well as *names on a page*. Spoken “I run the inn” often fails to join to that place unless both land in memory with an explicit place link in prose.

### Name and job slots

Many RPGs expose a small talk menu (Name, Job, Bye, and a few topics). Track those slots in the listener and restate them on talk end so SurrealDB Agent Memory can answer “people whose names I never asked” from **absence**, not only from fuzzy role words:

```text
Conversation with (appeared as "a paladin").
Name: not asked.
Job: asked - said he keeps villains out and that you need need a password to leave.
Transcript:
…
```

Keep engine **actor ids** on SurrealDB Agent Memory **labels** only (`npc_id=12`). Never put `npc_id=…` in Avatar-facing `/facts` or `/chat` prompts - synthesis will echo the identifier in first person. Appearance strings (“Spark”, “a shopkeeper”) stay in prose; durable ids stay in labels for filtering and briefing.

## Choose `infer` deliberately

| Mode | When to use for game clients |
| --- | --- |
| `full` (default) | Short facts, dialogue summaries, combat/sleep, travel notes where structure helps |
| `none` | You already wrote the prose you want embedded; skip graph extraction (good fallback for long seed text) |
| Document upload | Multi-kilobyte background lore, quest packs, manuals - not a single synchronous `/facts?infer=full` paste |

Long prior-campaign notes and world books should go through [Documents (upload)](/docs/agent-memory/reference/rest-api.md#documents-upload).

## Quantise noisy streams

Tile steps, mouse-overs, and ambient weather can generate thousands of events per hour. SurrealDB Agent Memory does not need each one.

- Prefer a **situation** snapshot over pedometry: glance names nearby (buildings, signs, people), optional net move as a footnote, optional map/sextant reading - flushed on a cadence, on talk boundaries, on death, and on teleports. Headings and “net tiles” alone make the mind sound like it walked blind.
- Emit a first-class **relocate** (or equivalent) when the engine teleports the party (gates, cutscenes, jail moves). Flush the open walk *before* the jump, remember arrival glances, and hard-reset travel so pre/post never blend into one continuous hike.
- Batch ordinary travel into those situation facts; do not send every step, and avoid gait or stride words that extraction turns into creature attributes.
- Attach a light ambient snapshot (time of day, indoor/outdoor, weather) to meaningful events only.
- Deduplicate repeating sign or book opens if the engine fires twice for one click.
- Prefer place names, signs, and glance labels for “where am I?” over repeating tile telemetry in `/chat` prompts.

That keeps the graph about journeys and places, not pathfinding samples.

### Lived vs spoken geography

Spoken testimony about a place is not presence there. Dialogue that aspires to travel (“I want passage to X”) must not become “I visited X” in chat. Stamp conversation as `authority=spoken` / `epistemic=heard`, and keep relocate / walk / situation facts as the lived trail. Named-place queries (“what happened at the inn?”) retrieve well; open geography prompts (“where have I been?”) still prefer place-bearing glances and talks over travel mush - design the write path accordingly.

## Chat for voice, facts for truth

`/chat` is for short reflections in a product voice (inner monologue, coach, companion). `/facts` and documents are for what should persist.

Typical split in an RPG listener:

| Moment | Persist (`/facts` or documents) | Reflect (`/chat`) |
| --- | --- | --- |
| Talk starts | Optional ambient | Briefing: what I already know about this npc_id |
| Mid-talk quiz (manual/map) | Optional after answer is known | Short thought; prefer an injected answer over hoping retrieval finds book lore |
| Talk ends | Transcript + discovery note | Farewell thought |
| Sign / book | Written / read fact | Optional one-line “where might I be?” |
| Container glimpse | Seen-not-taken fact | Usually nothing |
| Death / dungeon | Lived fact | Strong emotion once |
| Every examine | Usually nothing | Nothing |

## Seed memory once

Before the live loop, upload a small prior-memory pack (previous campaigns, faction primers, geography the hero already lived) as documents labelled `kind=prior_memory`, `authority=lived`, `era=…`. Prefer document upload over many `infer=full` facts. Optionally write a single identity fact that matches the **SurrealDB Studio** surface you will use, and keep the mind-chat speaker rule in the listener prompt, so the two UIs do not fight over who “I” is.

Do not put prior-campaign titles into the mind’s standing voice string if those campaigns were never uploaded. Chat consolidation and pretrained lore can invent “I finished those quests” without a document trail.

## Reliability notes from the field

Listener clients are often fire-and-forget from the engine. A few lessons that are easy to miss:

1. **Finish the HTTP exchange.** On some platforms, closing the socket immediately after `send` without reading the response can drop the body before SurrealDB Agent Memory accepts the write. Wait for the status line, or use an HTTP client that does.
2. **Context id ≠ display name.** API paths need the opaque Context id from SurrealDB Studio or the management API, not the friendly name you typed in the UI.
3. **Grants.** A key that can `/chat` but not write memory produces a mind that talks and never learns. Mint keys with the write capabilities your loop needs.
4. **Show write errors.** Surface status and response body in the listener UI; “N failed” without the message wastes debugging time. Opaque `{"message":"unknown error"}` bodies are common on hard failures; log request time and payload type so server logs can be matched later.
5. **Graph vs chat.** Entities marked observed (animals, props) may exist in the graph while `/chat` denies them until the prompt or retrieval path treats them as nearby or recently seen. Prefer third-person observe facts with a place name, not only soft `current_situation` edges.
6. **Clear state supersession works; gestalt is weak.** Party join / leave / rejoin and similar boolean state tend to answer correctly as “current vs prior.” Assembling many related fragments (currency amounts, map tiles, map readings, situation glances) into one concept or layout often does not - `/query` returns the pieces while `/chat` hedges or under-enumerates. Design prompts and evals around named threads; do not expect anonymous tile dumps to synthesise a settlement map.
7. **Playground and mind-chat pollute retrieval.** Operator questions and `/chat` answers become hits. Prefer a **fresh Context** for regression batteries, or filter operator turns. Do not leak the answer in survey ids, labels, or prompt wording when testing synthesis (“walled_town_…” in a probe id teaches the model the label).
8. **Ping a new Context before a long ingest.** After create, verify one small `/facts` write succeeds before flooding hundreds of events - opaque `500` responses can appear briefly on brand-new Contexts.

## Minimal remember helper

```python
import os
import httpx

SPECTRON = os.environ["SPECTRON_BASE_URL"].rstrip("/")
CTX = os.environ["SPECTRON_CONTEXT_ID"]
KEY = os.environ["SPECTRON_API_KEY"]

def remember(
    text: str,
    *,
    labels: list[str],
    infer: str = "full",
    memory_category: str | None = "context",
) -> None:
    body = {
        "text": text,
        "infer": infer,
        "labels": labels,
    }
    if memory_category:
        body["memory_category"] = memory_category
    r = httpx.post(
        f"{SPECTRON}/api/v1/{CTX}/facts",
        headers={
            "Authorization": f"Bearer {KEY}",
            "api-version": "1",
            "Content-Type": "application/json",
        },
        json=body,
        timeout=60.0,
    )
    r.raise_for_status()

# Example: book glance
remember(
    "Epistemic frame: the hero has just READ this scroll.\n"
    "Summarise what it claims; do not invent that the hero took part.\n\n"
    + scroll_text,
    labels=[
        "source=game",
        "kind=book",
        "authority=written",
        "epistemic=read",
    ],
    memory_category="knowledge",
)
```

## Checklist

- [ ] Event enum is small; noise stays local
- [ ] Stable actor ids on **labels** only; appearance / Name slots in prose
- [ ] Every write carries `kind` + `authority` + `epistemic` (or equivalent)
- [ ] High-value spoken lines and Name/Job slots are restated after the transcript
- [ ] Long lore uses documents; short moments use `/facts`
- [ ] Prior memory is third person on documents; chat speaker is per surface
- [ ] Travel uses situation snapshots (glances + optional move); teleports emit relocate
- [ ] Spoken place names are not treated as visited places
- [ ] `/chat` prompts declare the speaker for that UI; strip citation markers if needed
- [ ] HTTP client waits for accept; errors are visible
- [ ] Eval batteries use a clean Context; probe ids do not leak the expected answer

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/patterns/historical-and-archaeological-data

# Historical and archaeological data

Ingest fragmentary primary sources, ask questions the corpus can answer, and prove the answers came from the corpus.

![Three kinds of primary source side by side: an engraving of bound medieval survey volumes, a Roman dedicatory inscription cut into a limestone slab at Pompeii, and a Neolithic clay figurine of the Vinča culture.](~/assets/img/spectron/historical-corpora.webp)

*Left to right: the Domesday volumes, engraved for William Andrews, "Historic Byways and Highways of Old England" (1900). The amphitheatre dedication of Quinctius Valgus and Marcius Porcius, Pompeii, photographed by Gary Todd. A figurine of the Vinča culture, Cleveland Museum of Art 2000.202.*

## Overview

SurrealDB Agent Memory can be used to answer questions that no single historical record answers on its own.

Land surveys, inscriptions, census returns, excavation catalogues, and parish registers share a shape, but tend to be short and fragmentary and may lack a stable identifier. The useful answers in this case come from **stitching**, in which SurrealDB Agent Memory composes many scattered records into one claim.

This page describes the general workflow:

- Prepare a corpus and keep it checkable.
- Stamp historical time rather than ingest time.
- Choose questions the material can answer.
- Widen retrieval enough for totals.
- Prove that an answer came from your corpus, not from a model's training data.

Three case studies inform the advice. The first is a 1086 Domesday survey of one [English hundred](https://en.wikipedia.org/wiki/Hundred_(county_division)), at 52 records. The second is 127 Roman electoral notices from the walls of Pompeii, painted before the [disaster in AD 79](https://en.wikipedia.org/wiki/Eruption_of_Mount_Vesuvius_in_79_AD). The third is 91 site reports built from 1,822 radiocarbon measurements from the Serbian Danube (largely from the [Vinča culture](https://en.wikipedia.org/wiki/Vin%C4%8Da_culture)), in which one excavation appears under as many as six spellings.

## What makes these corpora distinct

Historical and archaeological records are painstakingly put together and are not frequently updated. This leads to a number of interesting properties that must be taken into account:

| Property | Consequence |
| --- | --- |
| No stable identifiers | Entity resolution is the work, not a preparation step |
| Records share vocabulary | Relevance scores band tightly, so rank order carries little signal |
| Answers span many records | Totals depend on retrieval breadth, not on reasoning quality |
| Dates are ranges, or one record covers two eras | Ingest time is meaningless. Supply the historical time |
| Names vary in spelling and inflection | A count by name string splits one person into several |
| One record covers several places or people | Attribution to a single subject is unreliable |
| Some records carry a laboratory or catalogue number | Resolution becomes checkable rather than a matter of opinion |
| The source is well known | A correct answer alone does not prove retrieval |

Some tips for working with such data are:

## Ingest prose, not a graph

Do not resolve entities in your own pipeline. If you upload `holding → held_by → william_of_warenne`, you have already done the identity work, and SurrealDB Agent Memory cannot help with the hard part.

Write each record the way the source reports it, with no identifiers. Extraction then works out that the William of Warenne in one place and the one in another are the same man.

A prose record from a land survey looks like this:

```text
Scottow, in the hundred of South Erpingham in Norfolk.

Before the Conquest, in King Edward's time:
one free man held land assessed at 0.10 carucates for geld.

In 1086: Ribald brother of Count Alan holds it of Count Alan,
who holds of the King, worth £1 4s.

The survey records a mill for grinding corn,
6 acres of meadow and woodland for 20 pigs here.

Domesday Book, Norfolk 4,36.
```

Three rules hold for every corpus type:

- **One record per source unit.** Use the unit a historian cites: one survey entry, one inscription, one burial. Citations then match the scholarly reference, so a reader can check any claim.
- **Keep the source text.** Where the original is in another language or notation, put it in the record beside the gloss. The record stays checkable, and keyword retrieval gets the original terms.
- **End with the citation.** Retrieval returns the citation with the passage, so every answer carries provenance.

For an inscription corpus, one record carries both layers:

```text
A campaign notice painted on a street wall at Pompeii.

The muleteers ask the voters to elect Gaius Iulius Polybius as duumvir.

The Latin reads: caium iulium polybium iivirum muliones rogant.

CIL 04, 00113.
```

Upload records as documents rather than as many synchronous facts. See [Documents (upload)](/docs/agent-memory/reference/rest-api.md#documents-upload).

## Normalise surface forms, not identities

Normalise the spelling of a name. Do not merge two people by hand.

- **Put inflected names into one citation form.** Latin `Marcum Holconium Priscum` becomes `Marcus Holconius Priscus`. The same man then reads identically wherever he appears. Do the same for declined place names.
- **Expand editorial conventions into running prose.** Print editions mark supplied letters (`M(arcum)`) and lost text (`[3]`). Strip the brackets for readable text. Keep a marker where the source is illegible.
- **Leave similar names alone.** `Holconius`, `Marcus Holconius`, and `Marcus Holconius Priscus` may be one person or three. Let resolution and [reconciliation](/docs/agent-memory/reasoning/reconciliation-and-supersession.md) decide. A manual merge destroys the evidence for the decision.
- **Keep the "nothing recorded" sentinel distinct from zero.** Sources use `-`, `n/a`, or a blank to mean *not surveyed*. Write "the survey records no mill here", or omit the sentence. Never write "0 mills".
- **Say what the blank applies to.** "No coordinates are recorded under this spelling" is a better sentence than "no coordinates". The first tells a reader, and the retrieval layer, that another spelling may hold the value. The second reads as a fact about the place.

> [!NOTE]
> Primary sources measure things in conventions that do not convert cleanly. Land measures, shared fractions of a mill, and resources counted by the stock they support ("woodland for 27 pigs") all carry meaning that a numeric conversion loses. Convert in one documented place. State the unit in the record.

## Keep the join key

Some corpora carry an identifier that is not a name: a laboratory sample number, a museum accession number, a catalogue reference, a DOI. Where one exists, keep it in the record text and keep it verbatim. Two records that cite the same identifier describe the same subject, whatever their names say. Resolution can then be scored instead of argued.

Identifiers settle identity questions that names cannot. Several spellings that read as variants of one name may resolve on shared sample numbers into two separate subjects, hundreds of kilometres apart. Spellings that look unrelated may resolve into one. String similarity gets both cases wrong, and an identifier gets both right.

An identifier also gives you a second, independent check on an answer. Where a claim rests on two records being about one subject, you can confirm the join yourself.

> [!NOTE]
> Encoding damage is not always dirt. Text that has been through a broken decode (incorrect `Vinàa-Belo Brdo` instead of the proper spelling `Vinča-Belo Brdo`) reads as corruption, but for a resolution test it is the material. Clean it and you have solved the problem you meant to measure. If you do repair it, keep the damaged form in the record beside the repair, and say which is which.

## Stamp historical time, not ingest time

By default, derived facts date to the moment you upload them. For historical material that date is wrong, and it breaks every temporal question. Set `observedAt` on upload to the known time of the record.

```http
POST /api/v1/{context_id}/documents
Content-Type: multipart/form-data

file=<binary>
metadata={"title":"scottow-4-36","observedAt":"1086-01-01T00:00:00Z","labels":["corpus=domesday","place=scottow"]}
```

Reads then accept `asOf`. You can ask what the corpus attests for one moment rather than for all time. See [Temporal validity](/docs/agent-memory/reasoning/temporal-validity.md) and [spoiler-safe narrative memory](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md) for more on this subject.

Decide three cases before you ingest:

- **A record that describes two eras.** Survey entries often report a holding before and after a conquest. Split the record in two, with one `observedAt` each. Or keep one record and give each era its own paragraph, with the date stated in prose. Do not interleave the eras in one sentence.
- **A date that is a range.** Stamp `observedAt` with the earliest plausible instant. State the range in prose ("between AD 62 and AD 79"). Synthesis quotes the prose. Ordering uses the timestamp.

Some corpora must hide later material from the reader. An excavation published in phases works this way, as does a narrative released in instalments. The same mechanism gives spoiler-safe playback.

## Label provenance and certainty

Labels are `key=value` tags for filtering. On a historical corpus they carry the distinctions a historian needs.

| Label | Use for |
| --- | --- |
| `corpus=…` | The body of material the record belongs to |
| `unit=…` | The citation unit (`entry`, `inscription`, `burial`, `folio`) |
| `place=…` | Findspot or subject place, in one normalised spelling |
| `era=…` | Period name where a timestamp is unavailable or unhelpful |
| `certainty=attested\|reconstructed\|inferred` | Whether the artefact says this, an editor supplied it, or you deduced it |
| `language=…` | Original language of the source text |
| `derived=true` | The record is your own synthesis, not a source |

`certainty` matters most. A gap an editor filled is not evidence, nor is your own inference. State the distinction on the label and in the prose, because synthesis reads the prose.

## Choose questions the corpus can answer

Question shape matters more than prompt wording. It decides which surface to ask, and whether you drive retrieval yourself.

| Shape | Example | How to ask it |
| --- | --- | --- |
| Lookup | "Who held Scottow in 1086?" | `/chat` |
| Identity | "Are these two spellings the same site?" | `/chat`, after `/consolidate` |
| Relational | "Who was allied with whom?" | `/chat`, after `/elaborate` |
| Structural | "How did lordship change across the Conquest?" | `/reflect` |
| Aggregate | "Who was wealthiest?", "How many mills in total?" | Fan-out `/query` at raised `k`, then count in your own code |
| Set | "Do these two entries share any samples?" | Enumerate both sides, then intersect in your own code |

## Set breadth to match the question

`k` on `/query` defaults to **10** and deployments cap it at **50**. The internal retrieval pool is set separately from `k`. See [Answer size vs search breadth](/docs/agent-memory/retrieve/hybrid-search.md#answer-size-vs-search-breadth).

A count needs a window wide enough to cover every record that bears on it. Where the candidate set is larger than the window, the answer describes what the window saw rather than what the corpus holds. Raising `k` to the size of the candidate set completes it. Nothing is missing from the index in the meantime, only unseen.

Records in one corpus share vocabulary, so relevance scores tend to band tightly. Rank order inside a narrow band carries little information. What a count sees therefore depends on the width of the window rather than on the ranking.

Counts are worth driving yourself, with `k` set from what you already know about the corpus:

```python
import os
import httpx

SPECTRON = os.environ["SPECTRON_BASE_URL"].rstrip("/")
CTX = os.environ["SPECTRON_CONTEXT_ID"]
KEY = os.environ["SPECTRON_API_KEY"]

client = httpx.Client(
    base_url=f"{SPECTRON}/api/v1/{CTX}",
    headers={"Authorization": f"Bearer {KEY}", "api-version": "1"},
    timeout=60.0,
)

def hits(query: str, k: int = 50) -> list[dict]:
    r = client.post("/query", json={"query": query, "k": k})
    r.raise_for_status()
    return r.json()["hits"]

# Fan out one query per subject instead of asking for a ranking in one call.
places = ["Scottow", "Aylsham", "Wickmere", "Calthorpe"]  # from your own index
mills = {place: hits(f"mill for grinding corn at {place}") for place in places}

for place, found in mills.items():
    print(place, len(found))
```

Three practices follow:

1. **Enumerate subjects from your own index.** You know which places or people exist. Ask once per subject, then combine the results yourself.
2. **Report coverage with every total.** State how many records contributed. "£51 from 11 of 52 records" is usable. "£51" misleads.
3. **Check the margin before you report a ranking.** Where the leader wins by a wide margin, the order holds up on partial coverage. Where the field is close, the order depends on having seen every contributor. Widen `k` until the coverage line stops moving, then name a winner.

### Enumerate both sides of a set operation

Coverage acts on a total and on a set operation differently, and the difference is arithmetic rather than anything to do with reasoning.

A total built from part of the corpus comes out low, in proportion to what it saw. A coverage line makes the shortfall visible. An intersection built from part of the corpus is not a partial intersection: it is the intersection of two samples, which is a different quantity, and its size gives no clue to the true one.

The gap can be wide. Suppose two entries hold a hundred sample numbers each, and the evidence names three of them. An intersection over that evidence can report no overlap at all between entries that share dozens. Every identifier in the evidence may be genuine, and every step over it may follow. Set operations do not survive sampling.

So treat intersections, differences, and "do these two overlap" questions the way you treat totals. Enumerate both sides with `/query` at raised `k`, then compute the set operation in your own code.

```python
import re

# Whatever shape the identifier takes in your corpus: OxA-13613, Bln-873, AA-57774.
LAB_NUMBER = re.compile(r"\b[A-Z][A-Za-z]{1,3}-\d{3,5}\b")

def samples(entry: str) -> set[str]:
    found = set()
    for hit in hits(f"laboratory samples listed for {entry}", k=50):
        found.update(LAB_NUMBER.findall(hit["text"]))
    return found

shared = samples("Vlasac") & samples("Lepenski Vir")
print(f"{len(shared)} shared, from {len(samples('Vlasac'))} and {len(samples('Lepenski Vir'))}")
```

## Bridge period vocabulary and modern questions

Sources use the vocabulary of their time and readers ask in theirs. "Where can I get bread?" and *a mill for grinding corn* have no words in common. A modern question can therefore miss the records that answer it, or be read as a question about the modern world. Retrieval may return the right records while the wording at both ends keeps them apart. The distance to close sits in the wording, not in the index.

Close it from either end. Four ways, cheapest first:

- **Ask in period terms.** Re-framed as "the year is 1086; a traveller wants corn ground into flour", the same corpus answered well.
- **Add one bridging sentence per record.** A short modern gloss ("A mill grinds corn into flour for bread.") gives retrieval and synthesis the hop. Keep it separate from the source text.
- **Upload a glossary.** Put period terms and their modern equivalents in one document. The vocabulary then sits in the corpus, not in your prompts.
- **Turn on query expansion for document retrieval.** `/documents/query` accepts `useHyde` and `decomposeQuery`. Both help when query wording and corpus wording diverge. See [Hybrid search](/docs/agent-memory/retrieve/hybrid-search.md#advanced-options).

Where the corpus names things plainly, the gap does not appear. A corpus that says "the bakers" outright needs no bridge for the bread question.

## Handle records that cover several subjects

One survey entry can cover ten places. One inscription can name several candidates. These records retrieve well, and what a reader can do with them depends on how you write them. A leading list of ten names followed by figures for the group supports a group answer, because that is the only claim the record makes. There is nothing in it to attach to any single place. A record written that way yields group answers, for that reason.

Write each subject so it carries its own claim. Two options:

- **Repeat each subject in its own sentence** inside the record. Each name then appears with its own resources, not only in a leading list.
- **Emit one record per subject.** Each carries the shared citation and a `group=<citation>` label, plus a sentence that states the source entry covers several subjects and shares the figures.

The second option costs duplication and buys attribution. Prefer it when per-subject questions matter.

## Apply synthesis

Synthesis turns a fragmentary corpus into an answer. The claims worth the operation are the ones no single record makes:

- **A change in structure across a period.** One form of tenure or allegiance gives way to another. The claim comes from the shape of the graph at each end, not from any one entry.
- **Two subjects acting together.** A shared ticket, partnership, or workshop, read from co-occurrence across separate records.
- **Rivalry.** Subjects contending for the same office, market, or territory in the same season, which no record states outright.
- **A value recovered under another name.** The record you asked about holds the subject but not the value, and a variant spelling holds the value.

The last is the clearest case for the pattern. The value sits in the corpus under a name nobody would think to search for, and no single record could produce the answer. Ask for the source spelling alongside the value, so the recovery stays checkable.

Use the operations in this order:

1. **`/elaborate`** before relational questions, so links are pre-formed rather than inferred inside one answer window.
2. **`/consolidate`** to merge name variants into single entities. Run it before any question that depends on counting people.
3. **`/reflect`** for interpretive claims across many records. Reflection always calls a model. It returns synthesised text with its evidence. See [Reflection](/docs/agent-memory/operations/reflect.md).

```python
essay = client.post(
    "/reflect",
    json={
        "query": (
            "Across these records, how did the pattern of lordship change "
            "between 1066 and 1086? Cite the records for each claim."
        ),
        "persist": False,
    },
).json()
```

> [!IMPORTANT]
> Keep synthesis out of the source corpus. Answers, operator questions, and persisted reflections all become retrievable hits. Beside the sources, they let a later question cite your own conclusion as evidence. Persist derived facts with `derived=true` and a distinct scope, or write them to a separate Context. Keep regression tests on a Context that holds sources only.

Ask for reasoning to be shown. A structural claim with its supporting records attached is checkable. The same claim alone is not.

## Prove the answer came from the corpus

Most well-known historical sources sit in model training data, so a correct answer does not prove that retrieval worked. Here are some tips to work with sources that may already form part of general knowledge known by LLM tools:

| Control | Method | Pass condition |
| --- | --- | --- |
| Empty-context baseline | Ask every probe **before** you ingest | `hits: []` on all probes |
| Perturbation | Replace one record with a version that carries an invented name and value | The answer reports the invention |
| Held-out real subject | Exclude a genuine place or person, then ask about it | The answer declines |
| Invented subject | Ask about something that never existed, as though real | The answer declines |
| Provenance | Read the `citations` array on every answer | Every claim resolves to a record |

Perturbation is the strongest of the four, because it separates retrieval from prior knowledge directly. Rewrite one record with an invented holder and value. Delete the original. Ingest the forgery, then ask. If the answer names the invented holder and never the real one, the answer came from your corpus.

```bash
# 1. remove the genuine record
curl -X DELETE "$SPECTRON_BASE_URL/api/v1/$SPECTRON_CONTEXT_ID/documents/$DOC_ID" \
  -H "Authorization: Bearer $SPECTRON_API_KEY"

# 2. ingest the altered record in its place
curl -X POST "$SPECTRON_BASE_URL/api/v1/$SPECTRON_CONTEXT_ID/documents" \
  -H "Authorization: Bearer $SPECTRON_API_KEY" \
  -F "file=@perturbed/cawston.txt" \
  -F 'metadata={"title":"cawston-perturbed","labels":["control=perturbation"]}'
```

Run the held-out control with a **real** subject from a neighbouring region, not an invented one. A model that knows the source may recognise a genuine name, so a refusal to answer about it is the stronger result. Keep the invented-subject probe as well. It catches confabulation rather than leakage.

Restore the perturbed record when the control finishes. Note in your results that the corpus was altered.

## Build ground truth that can adjudicate

Score answers rather than read them. Compute the expected totals, graphs, and counts from the same source data that generated the records. Store them beside the corpus.

Two failures are common.

- **A count by name string fragments people.** One man spread across five spellings can score 3 where the merged figure is 9. An answer that reasons from rank or office rather than raw frequency is then marked wrong while being defensible. Merge name variants before a count-based figure adjudicates "most prominent" or "wealthiest".
- **A join can lose records silently.** Where a citation key maps many-to-many in both directions, an inner join drops rows without an error. Check row counts at each join. Verify the total against a figure published with the source.

Record which source records produced each ground-truth figure. When an answer disagrees, you can then find out whether the answer or the ground truth is wrong.

### Include pairs that must not merge

Ground truth that holds only true merges cannot detect over-merging. A system that joins everything on string similarity scores full marks against it.

Add negative controls: subjects whose names invite a merge that the evidence does not support. Most corpora hold good cases already. Look for two names that differ by a letter or two and sit in the same region. Keep the pair if they share no identifiers, and if the evidence puts them a thousand years or a few hundred kilometres apart. For example, `Banja` and `Banjica`, or `Hajdučka Vodenica` and `Hadučka Vodenica`.

A passing distinct-pair control is what makes the merges elsewhere worth reporting. Record the expected verdict for each pair, not only the expected clusters.

Build three kinds of case into the same file:

| Case | Expected verdict |
| --- | --- |
| Merge | These labels are one subject |
| Split | This label covers more than one subject |
| Distinct | These labels look alike and are separate subjects |

## Notes for bulk ingest

- **Verify one write first.** Post a single small document to a new Context. Confirm it becomes retrievable before you send hundreds.
- **Confirm retrievability, not only status.** Document processing is asynchronous. A record can answer queries while its reported stage still lags. Gate your run on a successful `/query` for a known string.
- **Page correctly when you list documents.** Follow `page.nextCursor` from `GET /documents?limit=100` until it is absent, rather than stopping on the first short page. See [Pagination](/docs/agent-memory/reference/rest-api.md#pagination).
- **Poll gently during ingest.** Listing endpoints stay under load while the pipeline drains. Back off and retry rather than poll tightly.
- **Use a fresh Context per experiment.** Operator questions and prior answers become hits, so a reused Context contaminates the next run. See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md).
- **Keep the answer out of metadata.** A document title, label, or probe id that names the expected result teaches the model the answer.
- **Check the count out against the count in.** Any normalisation in your own pipeline can perform the resolution you are trying to measure. A filename function that maps every non-ASCII character to `_` collapses `č` and a mojibake variant onto one name. Documents then overwrite each other before the upload starts, and part of the resolution arrives pre-solved. The run still looks like a clean pass. Compare the number of subjects in your source data with the number of files, documents, and dictionary keys at every stage.

## Checklist

- [ ] Records are prose with no pre-resolved identifiers
- [ ] One record per citation unit, ending with its citation
- [ ] Original-language text sits beside the gloss
- [ ] Laboratory, accession, or catalogue numbers kept verbatim in the record
- [ ] Name spellings normalised; identities left unmerged
- [ ] "Nothing recorded" stays distinct from zero, and names the spelling it applies to
- [ ] `observedAt` set to historical time; two-era records split or dated in prose
- [ ] `certainty` stated on labels and in prose
- [ ] Aggregate and set questions use fan-out `/query` at raised `k`
- [ ] Set operations computed in your own code, not read out of an answer
- [ ] Every total reports how many records contributed
- [ ] Period vocabulary bridged by a gloss line or a glossary document
- [ ] Multi-subject records either repeat subjects or split with a shared citation
- [ ] `/consolidate` run before any count-based question
- [ ] Synthesis persisted with `derived=true` or to a separate Context
- [ ] Empty-context, perturbation, held-out, and invented-subject controls all run
- [ ] Ground truth holds merge, split, and distinct cases, and records its contributing records
- [ ] Document count out matches subject count in, at every pipeline stage

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/patterns/knowledge-grounded-agents

# Knowledge-grounded agents

Combine authoritative knowledge retrieval with experiential memory.

A knowledge-grounded agent answers from authoritative sources before consulting conversational memory. This pattern uses SurrealDB Agent Memory's authoritative knowledge to store canonical knowledge - product data, policies, technical documentation - and relies on the authority hierarchy to prevent conversational drift from corrupting those facts.

## Why ground agents in authoritative knowledge

Without a knowledge layer, agents hallucinate or rely on stale training data for domain-specific questions. The common workaround - embedding documents in a vector store and retrieving chunks - improves recall but loses structure, provenance, and the ability to enforce authority.

Authoritative knowledge gives you:

- **Structured knowledge nodes** with typed attributes, not just text chunks.
- **Authority enforcement** - authoritative knowledge wins when a user asserts something conflicting.
- **`resolves_to` links** - conversational references to products or policies resolve to authoritative nodes.
- **Content addressing** - uploading the same document twice is idempotent.

## Step 1 - Load authoritative knowledge

Documents are ingested through the knowledge API. SurrealDB Agent Memory processes them into knowledge nodes (typed entities with attributes) and keyword-indexed chunks.

```python
import os
import httpx

client = httpx.Client(
    base_url="https://spectron.surrealdb.com/api/v1/my-context",
    headers={"Authorization": f"Bearer {os.environ['AGENT_MEMORY_API_KEY']}"},
)

# Upload a product specification as a JSON document
with open("product-specs.json", "rb") as f:
    client.post(
        "/documents",
        files={"file": ("product-specs.json", f, "application/json")},
        data={"title": "Product specifications"},
    )

# Upload a policy document as Markdown
with open("returns-policy.md", "rb") as f:
    client.post(
        "/documents",
        files={"file": ("returns-policy.md", f, "text/markdown")},
        data={"title": "Returns policy"},
    )
```

```typescript
const formData = new FormData();
formData.append(
    "file",
    new Blob([productSpecsJson], { type: "application/json" }),
    "product-specs.json",
);
formData.append("title", "Product specifications");
formData.append("content_type", "product_data");

await fetch("https://spectron.surrealdb.com/api/v1/my-context/documents", {
    method: "POST",
    headers: { "Authorization": `Bearer ${process.env.AGENT_MEMORY_API_KEY}` },
    body: formData,
});
```

## Step 2 - Query authoritative knowledge in the agent loop

Before generating a response, search authoritative knowledge for relevant passages. Use `documents.query` for natural-language search over document chunks, or an entity read for exact lookups.

```python
from surrealdb.memory import Memory

memory = Memory(context="my-context", api_key="sk-...")

async def grounded_response(session, user_message: str) -> str:
    # Search authoritative knowledge for relevant authoritative knowledge
    knowledge = await memory.documents.query(
        query=user_message,
        mode="hybrid",
        k=4,
    )

    # Retrieve experiential memory user context for personalisation
    ctx = await session.context(query=user_message)

    # Assemble the prompt with authoritative facts taking precedence
    system = "You are a product assistant. Answer from the knowledge provided."

    # documents.query returns {results, queryMs}; each hit is {chunk, document, score}
    if knowledge["results"]:
        system += "\n\n## Authoritative knowledge\n"
        for hit in knowledge["results"]:
            system += f"\n{hit['chunk']['text']}  (source: {hit['document']['title']})"

    if ctx.items:
        system += f"\n\n## User context\n{ctx.formatted}"

    response = your_llm(system=system, user=user_message)

    await memory.remember(user_message, session_id=session.id, role="user")
    await memory.remember(response, session_id=session.id, role="assistant")

    return response
```

```typescript
async function groundedResponse(session: Session, userMessage: string): Promise<string> {
    const [knowledge, ctx] = await Promise.all([
        memory.documents.query({ query: userMessage, mode: "hybrid", k: 4 }),
        session.context({ query: userMessage }),
    ]);

    let system = "You are a product assistant. Answer from the knowledge provided.";

    if (knowledge.results.length > 0) {
        system += "\n\n## Authoritative knowledge\n";
        for (const hit of knowledge.results) {
            system += `\n${hit.chunk.text}  (source: ${hit.document.title})`;
        }
    }

    if (ctx.items.length > 0) {
        system += `\n\n## User context\n${ctx.formatted}`;
    }

    const response = await yourLlm({ system, user: userMessage });

    await memory.remember(userMessage, { sessionId: session.id, role: "user" });
    await memory.remember(response, { sessionId: session.id, role: "assistant" });

    return response;
}
```

## Step 3 - The `resolves_to` mechanism

When a user mentions a product or policy, SurrealDB Agent Memory creates an experiential memory entity and automatically creates a `resolves_to` relation pointing to the matching authoritative knowledge node. The context retrieval traverses this relation so the agent sees both layers together.

```json
// User says "I bought the AirPods Pro" - experiential memory entity created
{
  "id": "entity:[\"Product\", \"airpods_pro\"]",
  "layer": 1,
  "scope": ["org/acme/user/alice"]
}

// authoritative knowledge node - loaded from product catalogue
{
  "id": "knowledge:[\"Product\", \"airpods_pro\"]",
  "layer": 0,
  "name": "AirPods Pro (2nd generation)",
  "price": 279,
  "return_window_days": 30
}

// resolves_to - Spectron creates this automatically
{
  "in": "entity:[\"Product\", \"airpods_pro\"]",
  "out": "knowledge:[\"Product\", \"airpods_pro\"]"
}
```

At retrieval time, a query about the user's AirPods returns both the experiential-memory entity (the user owns them) and the authoritative knowledge node (authoritative specs). The agent receives a complete picture in a single context call.

## Step 4 - Handling authoritative and experiential streams conflicts

When a user asserts something that contradicts authoritative knowledge, SurrealDB Agent Memory records the conflict rather than silently overwriting the authoritative fact.

Example: the return policy is 30 days (authoritative knowledge), and a user says "I thought it was 60 days".

The extraction pipeline:

1. Creates an experiential memory attribute: `return_window_days: 60` on the policy entity.
2. Detects the conflict with the authoritative knowledge node (which says 30).
3. Surfaces the clash in **`uncertainties`** and state/profile responses without modifying the curated record.

The agent is informed via the context retrieval:

```json
{
  "type": "conflict",
  "l0_fact": { "key": "return_window_days", "value": 30 },
  "l1_belief": { "key": "return_window_days", "value": 60, "source": "user assertion" },
  "recommendation": "Inform the user of the authoritative value."
}
```

The agent can then politely correct the user without any custom conflict-detection code.

## Exact entity retrieval

For lookups where you know the entity type and name (from a structured UI, a product SKU field, etc.), read the entity directly instead of searching. This hits **`GET /entities/{entity_type}/{entity_name}`**, not the document store - `documents.get` takes a document id.

```python
entity = await memory.entities.get("product", "airpods_pro")
```

```typescript
const entity = await memory.entities.get("product", "airpods_pro");
```

This is faster than semantic search and appropriate when the reference is unambiguous.

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/patterns/reflection-loops

# Reflection loops

Scheduled or triggered reflect jobs.

Reflection is SurrealDB Agent Memory's mechanism for synthesising higher-order insights from accumulated memory. Unlike retrieval - which surfaces facts that already exist - reflection runs an LLM reasoning pass over a scope's memory and produces new insights that can be persisted back as experiential memory attributes. It is how SurrealDB Agent Memory moves from storing individual facts to producing understanding.

## What reflection does

A reflection request takes a query, a scope, and optional parameters, then:

1. Retrieves relevant memory items across the scope.
2. Sends them to a reasoning model alongside your query.
3. Returns a synthesised response.
4. If `persist: true`, stores the synthesised insights as new attributes on the relevant entities.

This is different from `context()`, which retrieves and ranks existing facts. Reflection reasons across facts to produce conclusions that are not explicitly stored anywhere.

## Basic reflection call

```python
from surrealdb.memory import Memory

memory = Memory(context="support", api_key="sk-...")

result = await memory.reflect(
    query="What are Alice's most frequently reported frustrations?",
    persist=False)

print(result.reflection)
```

```typescript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "support", apiKey: "sk-..." });

const result = await memory.reflect({
    query: "What are Alice's most frequently reported frustrations?",
    persist: false });

console.log(result.reflection);
```

The `reflection` field is a free-form text response from the reasoning model. When `persist: false`, nothing is written back to memory - useful for exploratory analysis or generating one-off summaries.

## Persisting synthesised insights

Set `persist: true` to write the insights back as experiential memory attributes. The reasoning model produces structured attribute suggestions which are reconciled against the existing memory state before being committed.

```python
result = await memory.reflect(
    query="Summarise this customer's product preferences and risk of churn.",
    persist=True)
```

```typescript
const result = await memory.reflect({
    query: "Summarise this customer's product preferences and risk of churn.",
    persist: true });
```

`persist` is the only routing control: reflect writes back against the entities its
own evidence names, and returns them in `persistedAttributes`. There is no
`target_entity_type` / `target_attribute_key` - you cannot pin the write to a
chosen attribute. The persisted attributes appear in future `profile()` and
`context()` calls, enriching responses with synthesised understanding rather than
just raw facts.

## Cross-user reflection with supervisor keys

Supervisor API keys have broader scope access - they can reflect across multiple users or the entire organisation scope. This enables pattern analysis across your user base.

```python
supervisor_memory = Memory(
    context="support",
    api_key="supervisor_sk_...",
)

result = await supervisor_memory.reflect(  # No user - reflects across all users in the org
    query="What are the most common product complaints this week?",
    persist=True)
```

```typescript
const supervisorMemory = new AgentMemory({
    context: "support",
    apiKey: "supervisor_sk_...",
});

const result = await supervisorMemory.reflect({
    // no user segment - reflects across everything the key can read
    query: "What are the most common product complaints this week?",
    persist: true,
});
```

## When to run reflections

Reflection is a compute-intensive operation. Appropriate trigger points:

- **End of session** - after a conversation closes, reflect to produce a session summary and update the user's churn risk or sentiment attributes.
- **Daily batch** - run a nightly reflection across all active users to update aggregate attributes.
- **Event-triggered** - run a targeted reflection when a specific event occurs (a complaint, a high-value purchase, an escalation).
- **Weekly insights** - broader organisational reflections that surface cross-user patterns.

### Scheduling as a background job

```python
import asyncio
from datetime import datetime, timezone

async def nightly_reflection(memory, org_id: str):
    """Run nightly reflection for all users in an org."""
    # GET /entities filters on type only - the key's read region bounds the rest
    entities = await memory.entities.list(type="Customer")

    for customer in entities:
        await memory.reflect(
            query="Update this customer's satisfaction score and churn risk based on recent interactions.",
            persist=True)
        # Respect rate limits between requests
        await asyncio.sleep(0.5)

    print(f"[{datetime.now(timezone.utc).isoformat()}] Nightly reflection complete for {len(entities)} customers.")
```

```typescript
async function nightlyReflection(memory: Memory, orgId: string): Promise<void> {
    // GET /entities filters on type only - the key's read region bounds the rest
    const entities = await memory.entities.list({ type: "Customer" });

    for (const customer of entities) {
        await memory.reflect({
            query: "Update this customer's satisfaction score and churn risk based on recent interactions.",
            persist: true });

        await new Promise(r => setTimeout(r, 500));
    }

    console.log(`Nightly reflection complete for ${entities.length} customers.`);
}
```

## Example reflection queries

| Use case | Query |
|---|---|
| Session summary | "Summarise this conversation and any commitments the agent made." |
| Customer health | "Rate this customer's satisfaction and likelihood to renew (1-10)." |
| Project risk | "What risks or blockers have been mentioned about this project?" |
| Complaint patterns | "What product issues have been raised most frequently this month?" |
| Learning trajectory | "What topics has this learner mastered and what gaps remain?" |

## Reflection versus retrieval

| | `context()` / `recall()` | `reflect()` |
|---|---|---|
| What it does | Retrieves existing facts | Reasons across facts to produce new conclusions |
| Output | Ranked memory items | Free-form synthesis (+ optional persisted attributes) |
| Cost | Low (retrieval only) | Higher (LLM reasoning pass) |
| When to use | Before every LLM call | Periodically or on specific events |

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory

# Spoiler-safe narrative memory

Ingest full canon; answer only as far as the user has read or watched.

SurrealDB Agent Memory can hold an entire story - novel, series, franchise, training curriculum released in modules - while an agent answers **only from what the user has reached so far**. The pattern combines three ideas you already have elsewhere in the docs:

1. **`observed_at` / `observedAt` on ingest** - stamp each chapter, page, or episode with a synthetic **known time** on the narrative axis (not wall-clock import time).
2. **`asOf` on recall** - the user's current position ("I'm on chapter 8", "I've finished episode V") selects which stamped facts and **relations** are visible.
3. **`labels` and `lens`** (optional) - navigate by `chapter=3` / `page=5`, or narrow a query to a scope region without changing permissions.

See [Temporal validity](/docs/agent-memory/reasoning/temporal-validity.md#narrative-playback-and-spoiler-safety) for the model; this page is the recipe.

## When to use it

| Scenario | Why bulk ingest breaks | What you gate |
| --- | --- | --- |
| Novel with a late reveal | Hyde/Jekyll-style identity twist | Relation edges and attributes stamped at the reveal |
| TV or film franchise | Viewing order ≠ story chronology | Per-episode stamps in **the order the user chose** |
| Long book series | Reader is on book 3 of 14 | `asOf` at book 3's end - later books ingested but hidden |
| Policy / curriculum modules | User certified on module 2 only | Module-scoped stamps + labels |

## Worked example: *Dr Jekyll and Mr Hyde*

*The Strange Case of Dr Jekyll and Mr Hyde* is a useful mental model: the whole plot is a single-entity reveal. Ingest each chapter (or page) with its own synthetic `observedAt`, monotonically increasing through the book. Stamp the Hyde↔Jekyll `same_as` relation only when you ingest the reveal chapter.

- Query with **`asOf`** at “chapter 8” → Hyde and Jekyll stay separate in the graph; no spoiler link.
- Query with **`asOf`** at “book finished” → the `same_as` edge is visible.

Same reader, same permissions - only the **`asOf`** instant changes.

## Franchise and non-chronological orders

Release order and story order are different problems with the same mechanism: **stamps follow discovery order, not calendar dates**.

**Star Wars (release order IV → V → VI, then I → II → III)**

Ingest each film with `observedAt` (or per-scene triples with `observed_at`) on a timeline that matches **when a release-order viewer learns each fact**. "Darth Vader is Luke's father" gets a stamp after *Empire* - not after *A New Hope*. A separate reader profile watching I → II → III first would use a different stamp sequence on the same ingested records (or separate scope paths per viewing track), because for this viewer there is no spoiler in Episode V that Anakin Skywalker and Darth Vader are the same person. However, a viewer following this path does have a reveal in Episode III that Anakin is no longer a hero, a fact that a user watching in release order would already be aware of when viewing the prequels.

**Wheel of Time (reader on book 3)**

Ingest all books if you want one substrate, but stamp facts extracted from book *N* with monotonically increasing known times per book (or per chapter). Recall with `asOf` set to "end of book 3" so prophecies, deaths, and alliances from later books stay out of answers.

```http
POST /api/v1/{context_id}/facts
Content-Type: application/json

{
  "text": "… excerpt from The Dragon Reborn …",
  "infer": "full",
  "scopes": [["org/my-app/reader/alice"]],
  "observed_at": "2000-10-15T00:00:00Z",
  "labels": ["book=3", "chapter=12"]
}
```

```http
POST /api/v1/{context_id}/query
Content-Type: application/json

{
  "query": "Who is Rand al'Thor allied with?",
  "k": 10,
  "asOf": "2000-10-15T00:00:00Z",
  "lens": [["org/my-app/reader/alice"]],
  "labels": ["book=3"]
}
```

Adjust the synthetic timeline to your ordering scheme; the important part is that **later books never share an earlier stamp**.

## Document ingest (page-by-page)

For PDFs or markdown split into pages:

```bash
spectron documents upload ./chapters/ch08.txt \
  --scope org/my-app/reader/alice \
  --label chapter=8 --label page=5 \
  --as-of 1886-03-01T00:00:00Z
```

Metadata JSON equivalent:

```json
{
  "title": "Dr Jekyll and Mr Hyde - ch. 8",
  "scopes": [["org/my-app/reader/alice"]],
  "labels": ["chapter=8", "page=5"],
  "observedAt": "1886-03-01T00:00:00Z"
}
```

Then query with matching `asOf` and optional `labels` for passage-only navigation.

## Tracking prominence over the timeline

While SurrealDB Agent Memory reconciles repeated mentions into a single state, it does not count them. Re-asserting the same fact does not increment a tally you can read back, and **`importance`** is a retrieval weight, not a mention count.

When you need a per-position measure - how prominent a character is by chapter *N* - compute it during ingest and store it as an ordinary attribute stamped with that chapter's known time. Each chapter's write supersedes the previous value, so the attribute becomes one supersession chain per character, and **`asOf`** returns the count as it stood at that point in the story.

The following example shows how this would be done with characters from the first few chapters of the fantasy series The Wheel of Time in order to track the importance of characters as the book progresses.

Write one batch per chapter. Use **`infer: "triples"`** to store the values verbatim, with no LLM extraction:

```http
POST /api/v1/{context_id}/facts
Content-Type: application/json

{
  "infer": "triples",
  "observed_at": "2000-01-08T00:00:00Z",
  "triples": [
    { "entity": { "type": "person", "name": "Moiraine" }, "key": "mention_count", "value": "40" },
    { "entity": { "type": "person", "name": "Lan" }, "key": "mention_count", "value": "20" }
  ]
}
```

Read the value at any reader position:

```bash
curl "$SPECTRON_HOST/api/v1/$CONTEXT_ID/entities/person/moiraine?asOf=2000-01-08T00:00:00Z" \
  -H "authorization: Bearer $SPECTRON_API_KEY"
```

The character Moiraine shows up in the book after the first few chapters, with a `mention_count` that quickly accelerates once she is first introduced:

| Reader position | `asOf` | `mention_count` |
| --- | --- | --- |
| Chapter 5 | `2000-01-06T00:00:00Z` | `28` |
| Chapter 7 | `2000-01-08T00:00:00Z` | `40` |
| Chapter 8 | `2000-01-09T00:00:00Z` | `85` |

Decide two things before you start:

- **Cumulative or per unit.** Cumulative totals only grow, and read as “prominence so far”. Per-chapter counts rise and fall, and read as “prominence in this chapter”. Store both under different keys if you need both - each key gets its own chain.
- **Counting is yours.** SurrealDB Agent Memory stores the number you supply. Aliases, epithets, and pronouns are a counting problem to solve before the write.

Batch every character for one chapter into a single request. One request per character multiplies the write count for no benefit.

> [!WARNING]
> **The entity type is half the identity, not a label.** An entity's record id is the composite **`[type, normalised_name]`**, and an unrecognised type falls back to **`other`** instead of failing. A counter written as `{"type": "creature", "name": "Trolloc"}` lands on `other/trolloc`, while the same character extracted from a document as a `person` is a **separate entity with a separate chain**. Use a type from the closed vocabulary - `person`, `organisation`, `project`, `location`, `topic`, `product`, `policy`, `concept`, `event`, `agent`, `service`, `other` - and use the same one every time you write the counter.

> [!NOTE]
> One write per chapter per character adds one row to that character's chain per chapter. That is the intended shape - history is kept, not overwritten - but keep content volume in mind as well: a 700-chapter series produces a 700-row chain for every character you track.

## Name variants are separate entities

Entity identity is a composite **`[type, normalised_name]`**, and matching on the write path is an exact record-id lookup. There is no fuzzy, phonetic, or embedding-based matching, no alias field, and no merge operation.

A narrative works against this. Characters are introduced in full and then referred to informally, so extraction mints each surface form it meets as its own entity: `Mat` and `Matrim Cauthon` become two people, as do `Egwene` and `Egwene al'Vere`. A misspelling in the source text becomes a third.

`same_as` does not fix this. It is an ordinary relation label - it records the claim that two entities are the same, and nothing reconciles them into one row.

Some ways to work with name variants:

**Detect it.** A hygiene sweep reports near-identical entities above a cosine threshold:

```http
POST /api/v1/{context_id}/fsck
Content-Type: application/json

{ "check": "duplicates", "duplicateThreshold": 0.95, "maxResults": 100 }
```

This reports, but does not merge.

**Prevent it.** Extraction is shown the entities the context already holds and told to reuse their exact names, so writing your canonical cast before a bulk ingest makes later mentions attach to the rows you chose. Seed the names you care about first, in the type you intend to keep.

## Reading a whole timeline back

**`asOf`** answers "what was true at this point". Rebuilding an entire timeline - every character at every chapter, to drive an export or a visualisation - by calling **`asOf`** once per character per position costs one request per cell. Three hundred characters across seven hundred chapters is 210,000 requests for a single attribute.

Ask for the chain instead. A single call returns the full supersession chain for one attribute key:

```bash
curl "$SPECTRON_HOST/api/v1/$CONTEXT_ID/entities/person/moiraine/history/mention_count" \
  -H "authorization: Bearer $SPECTRON_API_KEY"
```

Every row carries its `value`, its `createdAt`, and `supersedes` / `supersededBy` links. Because **`observed_at`** on ingest sets `created_at` as the row's known time - the same field **`asOf`** walks when it resolves a chain - `createdAt` here is the chapter's narrative stamp, not the wall-clock moment you uploaded it. The chain is therefore already the per-position series, in order.

That turns one request per character per position into one request per character per key. The same three hundred characters and one key: three hundred requests.

To enumerate the cast, list entities and page through them - the listing is unpaginated by default and capped at 500, so pass `limit` and `offset` for anything larger:

```bash
curl "$SPECTRON_HOST/api/v1/$CONTEXT_ID/entities?type=person&limit=500&offset=0" \
  -H "authorization: Bearer $SPECTRON_API_KEY"
```

## Composing narrowings

On a single full-access reader you can combine three independent slices:

| Narrowing | Question | Mechanism |
| --- | --- | --- |
| Time | How far have I read? | `asOf` - gates facts and relations by known time |
| Scope lens | Limit this query to chapters 1-8 | `lens: [["chapter/1"], …]` - involvement filter within grant |
| Labels | What's on page 3? | `labels: ["page=3"]` - row filter; use with `include: ["passages"]` for text |

Use time for **spoiler boundaries on the graph**; use labels for **locating** content within what time already allows.

## Limits to know

- **Passages** are not reading-time-gated by `asOf` alone - they retain upload-time metadata. Rely on the structured graph for spoiler-safe *answers*, or filter passages with **`labels`**.
- **`asOf` on `/query`** walks known-time on attributes and relations; pair with [Temporal validity](/docs/agent-memory/reasoning/temporal-validity.md) for `valid_from` / `valid_until` on in-world dates.
- Response caching is bypassed when temporal filters are present - correct for narrative playback, slightly higher latency.

## Related reading

- [Storing memories](/docs/agent-memory/ingest/experiential/remember.md) - `observed_at` and `--as-of` on the CLI
- [Uploading documents](/docs/agent-memory/ingest/authoritative/uploading-documents.md) - `observedAt` in upload metadata
- [Recalling memories](/docs/agent-memory/retrieve/recall.md) - `asOf` and `occurredAt` on hits
- [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md) - `lens` and labels

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/patterns/stateful-workflows

# Stateful workflows

Diff-friendly state for workflow UIs.

SurrealDB Agent Memory is not limited to conversational memory. Its entity-attribute model and temporal validity system make it a natural fit for tracking the state of long-running, multi-step agent workflows - processes that span hours or days, survive restarts, and need to avoid repeating completed steps.

## Why use SurrealDB Agent Memory for workflow state

Traditional approaches to workflow state - Redis keys, database records, task queue metadata - require purpose-built state management. SurrealDB Agent Memory adds:

- **Scoped isolation** - workflow state for project A cannot interfere with project B.
- **Temporal validity** - step results can expire, triggering re-execution.
- **Provenance** - every state change traces back to the turn or operation that caused it.
- **State diff** - query what changed since a checkpoint without building your own diffing logic.
- **Observability** - inspect the full state of any workflow at any point in time.

## Step 1 - Design your state model

Map your workflow steps to entity types and attributes. A research workflow might look like:

| Entity type | Example entities | Key attributes |
|---|---|---|
| `ResearchTask` | `market_analysis_q1` | `status`, `assigned_model`, `deadline` |
| `SearchResult` | `result_001` | `query`, `url`, `summary`, `relevance_score` |
| `Report` | `market_report_draft` | `status`, `word_count`, `sections_complete` |

## Step 2 - Scope workflow state to a project identifier

Use a `project` or `task` scope dimension to isolate each workflow's state:

```python
from surrealdb.memory import Memory

memory = Memory(context="workflows", api_key="sk-...")

# Each workflow run gets its own scope
workflow_scope = ["org/acme/project/market-analysis-q1-2025"]

session = await memory.sessions.create(scopes=workflow_scope)
```

```typescript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "workflows", apiKey: "sk-..." });

const workflowScope = ["org/acme/project/market-analysis-q1-2025"];
const session = await memory.sessions.create({ scopes: workflowScope });
```

## Step 3 - Record step completions as turns

Use turns to record what each step did. The extraction pipeline stores the results as Context-category attributes on the relevant entities.

```python
async def run_search_step(session, query: str) -> list[dict]:
    results = await your_search_api(query)

    # Record the step as an agent turn - extraction captures the results
    await memory.remember(
        f"Completed search for '{query}'. Found {len(results)} results. "
        f"Top result: {results[0]['url']} - {results[0]['summary'][:200]}",
        session_id=session.id,
        role="assistant",
    )

    return results
```

```typescript
async function runSearchStep(session: Session, query: string): Promise<SearchResult[]> {
    const results = await yourSearchApi(query);

    await memory.remember(
        `Completed search for '${query}'. Found ${results.length} results. `
            + `Top result: ${results[0].url} - ${results[0].summary.slice(0, 200)}`,
        { sessionId: session.id, role: "assistant" },
    );

    return results;
}
```

## Step 4 - Check state before each step

Before running a step, check whether it has already been completed. This makes the workflow idempotent - safe to restart without duplicating work.

```python
async def should_run_step(memory, lens: list[str], step_name: str) -> bool:
    ctx = await memory.context(
        lens=lens,
        query=f"Has the {step_name} step been completed?",
        k=3,
    )

    # Check if there is an existing completion record for this step
    for item in ctx.items:
        if item.entity_type == "WorkflowStep" and item.attributes.get("name") == step_name:
            if item.attributes.get("status") == "completed":
                return False

    return True
```

```typescript
async function shouldRunStep(
    memory: Memory,
    lens: string[],
    stepName: string,
): Promise<boolean> {
    const ctx = await memory.context({
        lens,
        query: `Has the ${stepName} step been completed?`,
        k: 3,
    });

    for (const item of ctx.items) {
        if (
            item.entityType === "WorkflowStep"
            && item.attributes.name === stepName
            && item.attributes.status === "completed"
        ) {
            return false;
        }
    }

    return true;
}
```

## Step 5 - Using temporal validity for step expiry

Some workflow steps have results that go stale - a price lookup, a news summary, a resource availability check. Set `valid_until` on the turn to give the extracted attributes a time-to-live:

```http
POST /api/v1/{context_id}/sessions/{session_id}/turns
Content-Type: application/json

{
  "role": "assistant",
  "content": "Fetched current gold price: $2,340/oz",
  "metadata": {
    "valid_until": "2025-11-16T00:00:00Z"
  }
}
```

When the validity period expires, the attribute no longer appears in context retrievals and `should_run_step` returns `true` again, triggering a re-fetch.

## Step 6 - Tracking progress

Each step's write returns its own delta, so accumulate those rather than polling
state. `/state` returns the whole Context, and a workflow's Context only grows -
reading it twice per step gets slower as the run goes on.

```python
progress = {"completed": 0, "revised": 0, "blocked": 0}

for step_name, step_fn in steps:
    result = await step_fn()                     # each step calls memory.remember(...)
    progress["completed"] += len(result.extraction.attributes)
    progress["revised"] += len(result.extraction.corrections)
    progress["blocked"] += len(result.extraction.uncertainties)

print(progress)
```

```typescript
const progress = { completed: 0, revised: 0, blocked: 0 };

for (const { name, fn } of steps) {
    const result = await fn();                   // each step calls memory.remember(...)
    progress.completed += result.extraction.attributes.length;
    progress.revised += result.extraction.corrections.length;
    progress.blocked += result.extraction.uncertainties.length;
}

console.log(progress);
```

The diff is useful for:

- **Progress bars** - count completed steps vs total expected.
- **Stall detection** - alert if no state changes have occurred in N minutes.
- **Audit trails** - record what changed during a workflow run for compliance.

## Putting it together

```python
async def run_research_workflow(user_id: str, topic: str):
    scope = [f"org/acme/project/research-{topic.replace(' ', '-')}"]
    session = await memory.sessions.create(scopes=scope)

    steps = [
        ("web_search", lambda: run_search_step(session, topic)),
        ("summarise", lambda: run_summarise_step(session, topic)),
        ("draft_report", lambda: run_draft_step(session, topic)),
    ]

    for step_name, step_fn in steps:
        if await should_run_step(memory, scope, step_name):
            await step_fn()
        else:
            print(f"Skipping {step_name} - already completed.")

    # Final state summary
    state = await memory.state()
    return state
```

```typescript
async function runResearchWorkflow(userId: string, topic: string) {
    const scope = [`org/acme/project/research-${topic.replace(/ /g, "-")}`];
    const session = await memory.sessions.create({ scope });

    const steps = [
        { name: "web_search", fn: () => runSearchStep(session, topic) },
        { name: "summarise", fn: () => runSummariseStep(session, topic) },
        { name: "draft_report", fn: () => runDraftStep(session, topic) },
    ];

    for (const { name, fn } of steps) {
        if (await shouldRunStep(memory, scope, name)) {
            await fn();
        } else {
            console.log(`Skipping ${name} - already completed.`);
        }
    }

    const state = await memory.state();
    return state;
}
```

---

Source: https://surrealdb.com/docs/agent-memory/cookbooks/patterns/user-memory-in-chat

# User memory in chat

Per-user scopes and profile injection.

Adding per-user persistent memory to a chat application is the most common SurrealDB Agent Memory integration. This guide covers the two integration shapes - one driven by SurrealDB Agent Memory, one driven by the caller - and shows how to inject memory into system prompts and how memory accumulates across multiple sessions.

## The pattern

The core pattern is:

1. **One session per conversation** - scoped to the user's identifier.
2. **Profile injection** - retrieve the user's accumulated memory and prepend it to the system prompt before each LLM call.
3. **Turn recording** - after each exchange, record both the user and assistant turns so the extraction pipeline can update memory.

Memory builds up across sessions automatically. The second time a user starts a conversation, the profile already contains facts from previous sessions.

## Integration shape 1 - SurrealDB Agent Memory drives the loop

Use this shape when you want the simplest possible integration and are comfortable letting SurrealDB Agent Memory manage the LLM calls. SurrealDB Agent Memory retrieves context, calls the Context's configured **synthesis** model, persists the exchange, and runs extraction.

There is no caller-supplied callback. `/chat` uses the model configured on the
Context (`models.synthesis`), which you can override per call with `model`. The
profile is folded into the prompt server-side - you do not assemble it yourself in
this shape. If you need your own model, prompt, or tool loop, use integration
shape 2 below.

```python
from surrealdb.memory import Memory

memory = Memory(context="chat", api_key="sk-...")

# Per conversation
session = await memory.sessions.create(scopes=[f"user/{user_id}"])

# Each user message
result = await memory.chat(user_message, session_id=session.id)
reply = result["reply"]
print(result["memoryUpdates"])   # extraction diff from the user turn
```

```typescript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "chat", apiKey: "sk-..." });

const session = await memory.sessions.create({ scopes: [`user/${userId}`] });

const result = await memory.chat(userMessage, { sessionId: session.id });
const reply = result.reply;
console.log(result.memoryUpdates);
```

## Integration shape 2 - Caller drives the loop

Use this shape when you already manage the conversation loop and want to inject SurrealDB Agent Memory into your existing flow without restructuring it.

```python
from surrealdb.memory import Memory

memory = Memory(context="chat", api_key="sk-...")

async def handle_message(user_id: str, session_id: str | None,
    user_message: str) -> str:
    # A session is addressed by its id - create one on first message,
    # then carry the id forward. There is no re-open call.
    if session_id is None:
        session = await memory.sessions.create(scopes=[f"user/{user_id}"])
        session_id = session.id

    # Retrieve memory-enriched context
    profile = await memory.profile()
    ctx = await memory.sessions.context(session_id, query=user_message)

    # Build system prompt
    system = "You are a helpful assistant."
    profile_block = format_profile(profile)   # see Profiles: injecting into prompts
    if profile_block:
        system += f"\n\n{profile_block}"
    if ctx.formatted:
        system += f"\n\n## Relevant memory\n{ctx.formatted}"

    # Your LLM call
    response = your_llm(system=system, user=user_message)

    # Record the exchange
    await memory.remember(user_message, session_id=session_id, role="user")
    await memory.remember(response, session_id=session_id, role="assistant")

    return response
```

```typescript
async function handleMessage(
    userId: string,
    sessionId: string | null,
    userMessage: string,
): Promise<string> {
    const id = sessionId
        ?? (await memory.sessions.create({ scopes: [`user/${userId}`] })).id;

    const [profile, ctx] = await Promise.all([
        memory.profile(),
        memory.sessions.context(id, { query: userMessage }),
    ]);

    let system = "You are a helpful assistant.";
    const profileBlock = formatProfile(profile);   // see Profiles: injecting into prompts
    if (profileBlock) system += `\n\n${profileBlock}`;
    if (ctx.formatted) system += `\n\n## Relevant memory\n${ctx.formatted}`;

    const response = await yourLlm({ system, user: userMessage });

    await memory.remember(userMessage, { sessionId: id, role: "user" });
    await memory.remember(response, { sessionId: id, role: "assistant" });

    return response;
}
```

The key difference between the two shapes is ownership of the LLM call. The caller-driven shape is usually the right choice for existing applications because it requires no changes to the core call path - you add memory injection before and recording after.

## Injecting profile into the system prompt

The profile endpoint is designed for system prompt injection, but it returns
**sections rather than a prose summary**: `static`, `dynamic`, `preferences`, and
`selfFacts` are lists of `{key, value}`, and `instructions` is a list of
`{id, label, description}`. Format the sections you want into the prompt yourself
- see [Profiles](/docs/agent-memory/operations/profiles.md#injecting-profiles-into-system-prompts)
for a reusable `format_profile` helper.

```python
profile = await memory.profile()

# Whole profile, formatted
system = f"You are a helpful assistant.\n\n{format_profile(profile)}"

# Fine-grained: pick individual sections
identity = profile.static
instructions = [i["description"] for i in profile.instructions]
```

```typescript
const profile = await memory.profile();

// Whole profile, formatted
const system = `You are a helpful assistant.\n\n${formatProfile(profile)}`;

// Fine-grained: pick individual sections
const identity = profile.static;
const instructions = profile.instructions.map(i => i.description);
```

## How memory accumulates across sessions

Memory is not session-scoped - it is user-scoped. Every session with the same `user` scope dimension feeds into the same pool of entities and attributes.

Consider a user who has three conversations over a week:

- **Session 1**: "I work at Acme Corp as a backend engineer." → extracts `employer: Acme Corp`, `role: backend engineer`.
- **Session 2**: "I prefer concise answers." → extracts instruction `response_style: concise`.
- **Session 3**: "I just moved to the platform team." → updates `role: platform engineer` with a supersession chain.

By session 3, the profile contains all three facts. The role update from session 3 supersedes session 1, but the old value is preserved in the supersession chain for auditability.

## Querying accumulated memory

To see what SurrealDB Agent Memory currently knows about a user:

```python
entities = await memory.entities.list()
for entity in entities:
    print(f"{entity.type}/{entity.name}")
    for attr in entity.attributes:
        print(f"  {attr.key}: {attr.value}")
```

```typescript
const entities = await memory.entities.list();
for (const entity of entities) {
    console.log(`${entity.type}/${entity.name}`);
    for (const attr of entity.attributes) {
        console.log(`  ${attr.key}: ${attr.value}`);
    }
}
```

This is the same view the agent gets via the profile endpoint, but structured for programmatic inspection rather than prompt injection.

---

Source: https://surrealdb.com/docs/agent-memory/integrations

# Agent Memory integrations

SDKs, the MCP server, AI SDKs and agent frameworks. Connecting SurrealDB Agent Memory to your stack, plus voice tools and automation.

SurrealDB Agent Memory connects to agents through HTTP, MCP, **official SDKs**, and harness adapters that mirror conversation turns into `POST /api/v1/{context_id}/facts/batch`, using the same provenance and trust model as the server.

The REST surface is described by an OpenAPI specification. The Python, TypeScript, Swift, and Kotlin clients track that spec so request and response shapes stay aligned with the server.

## MCP server (coding assistants)

Native MCP at `/mcp` on the api port, with the same **`Authorization: Bearer`** auth as REST. Prefer this when the client already speaks MCP. Point the client at your instance's `/mcp` endpoint, or install it with [`install-mcp`](https://github.com/supermemoryai/install-mcp):

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client cursor \
  --header "Authorization: Bearer <your-api-key>" --oauth no
```

→ [MCP server install](/docs/agent-memory/integrations/mcp-server/install.md) · Per-client guides: [Claude](/docs/agent-memory/integrations/mcp-server/coding-assistants/claude-desktop-and-code.md) · [Cursor](/docs/agent-memory/integrations/mcp-server/coding-assistants/cursor.md) · [VS Code](/docs/agent-memory/integrations/mcp-server/coding-assistants/vscode.md) · [JetBrains](/docs/agent-memory/integrations/mcp-server/coding-assistants/jetbrains.md) · [JetBrains Air](/docs/agent-memory/integrations/mcp-server/coding-assistants/jetbrains-air.md) · [Zed](/docs/agent-memory/integrations/mcp-server/coding-assistants/zed.md) · [Windsurf](/docs/agent-memory/integrations/mcp-server/coding-assistants/windsurf.md) · [Codex](/docs/agent-memory/integrations/mcp-server/coding-assistants/codex.md) · [Antigravity](/docs/agent-memory/integrations/mcp-server/coding-assistants/antigravity.md) · [OpenCode](/docs/agent-memory/integrations/mcp-server/coding-assistants/opencode.md)

## SDKs

Call SurrealDB Agent Memory directly from application code.

| Language | Package |
| --- | --- |
| Dart | `surrealdb` (import `spectron.dart`) |
| Elixir | `:surrealdb` (`SurrealDB.Memory`) |
| Go | `spectron` package in `surrealdb.go` |
| Haskell | `surrealdb-spectron` |
| JavaScript / TypeScript | `@surrealdb/memory` |
| Kotlin | bundled in `com.surrealdb:kotlin` |
| Python | `surrealdb` (SurrealDB Agent Memory ships inside it) |
| Swift | `AgentMemory` product in `surrealdb.swift` |

→ [Dart](/docs/agent-memory/integrations/sdks/dart.md) · [Elixir](/docs/agent-memory/integrations/sdks/elixir.md) · [Go](/docs/agent-memory/integrations/sdks/go.md) · [Haskell](/docs/agent-memory/integrations/sdks/haskell.md) · [JavaScript & TypeScript](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md) · [Kotlin](/docs/agent-memory/integrations/sdks/kotlin.md) · [Python](/docs/agent-memory/integrations/sdks/python.md) · [Swift](/docs/agent-memory/integrations/sdks/swift.md)

## AI SDKs

Drop-in memory for the popular TypeScript AI SDKs, with recall and storage wrapped around your model calls.

- **Cloudflare Workers AI**: the client inside a Worker, alongside Workers AI models.
- **TanStack AI**: the `@surrealdb/memory` client in TanStack Start server routes.
- **Vercel AI SDK**: `@surrealdb/spectron-vercel-ai`, via `wrapLanguageModel` middleware and a tool set.

→ [Cloudflare Workers AI](/docs/agent-memory/integrations/ai-sdks/cloudflare-workers-ai.md) · [TanStack AI](/docs/agent-memory/integrations/ai-sdks/tanstack-ai.md) · [Vercel AI SDK](/docs/agent-memory/integrations/ai-sdks/vercel-ai-sdk.md)

## Agent frameworks

Harness adapters expose SurrealDB Agent Memory as agent tools and add automatic per-turn memory, without changing your prompts.

| Framework | Package | Language |
| --- | --- | --- |
| CrewAI | `spectron-crew-ai` | Python |
| Eve | `@surrealdb/spectron-eve` | TypeScript |
| Google ADK | `spectron-google-adk` | Python |
| Hermes Agent | `spectron-hermes` | Python |
| LangChain / LangGraph | `@surrealdb/langchain`, `@surrealdb/langgraph` | TypeScript |
| Mastra | `@surrealdb/mastra-ai` | TypeScript |
| OpenAI Agents SDK | `spectron-openai-agents-sdk` | Python |
| OpenClaw | `@surrealdb/spectron-openclaw` | TypeScript |
| Pydantic AI | `spectron-pydantic-ai` | Python |
| Strands Agents | `spectron-strands-agents` | Python |

→ [CrewAI](/docs/agent-memory/integrations/frameworks/crewai.md) · [Eve](/docs/agent-memory/integrations/frameworks/eve.md) · [Google ADK](/docs/agent-memory/integrations/frameworks/google-adk.md) · [Hermes Agent](/docs/agent-memory/integrations/frameworks/hermes.md) · [LangChain](/docs/agent-memory/integrations/frameworks/langchain.md) · [Mastra](/docs/agent-memory/integrations/frameworks/mastra.md) · [OpenAI Agents SDK](/docs/agent-memory/integrations/frameworks/openai-agents.md) · [OpenClaw](/docs/agent-memory/integrations/frameworks/openclaw.md) · [Pydantic AI](/docs/agent-memory/integrations/frameworks/pydantic-ai.md) · [Strands Agents](/docs/agent-memory/integrations/frameworks/strands-agents.md)

Frameworks without a dedicated package integrate directly through the SDK.

→ [Agno](/docs/agent-memory/integrations/frameworks/agno.md) · [AutoGen](/docs/agent-memory/integrations/frameworks/autogen.md) · [Camel AI](/docs/agent-memory/integrations/frameworks/camel-ai.md) · [LlamaIndex](/docs/agent-memory/integrations/frameworks/llamaindex.md)

## Voice & realtime

Give voice agents memory that persists across calls.

→ [ElevenLabs](/docs/agent-memory/integrations/voice/elevenlabs.md) · [LiveKit](/docs/agent-memory/integrations/voice/livekit.md) · [Gradium](/docs/agent-memory/integrations/voice/gradium.md)

## Automation

- **n8n**: the `@surrealdb/n8n-nodes-surrealdb` community node, plus SurrealDB Agent Memory over the REST API.
- **Zo Computer**: a Zo skill that calls the SurrealDB Agent Memory SDK for recall and storage.

→ [n8n](/docs/agent-memory/integrations/automation/n8n.md) · [Zo Computer](/docs/agent-memory/integrations/automation/zo-computer.md)

## Observability

Run SurrealDB Agent Memory for memory while an observability platform traces and evaluates the agent.

→ [AgentOps](/docs/agent-memory/integrations/observability/agentops.md) · [Respan](/docs/agent-memory/integrations/observability/respan.md)

## REST API

Direct HTTP from any language. End-user routes: `/api/v1/{context_id}/…`. Management: `/api/v1/contexts/…`.

→ [REST integration guide](/docs/agent-memory/integrations/surfaces/rest.md) · [Full reference](/docs/agent-memory/reference/rest-api.md)

## Not shipped yet

- **Embedded in-process library**: use REST or an SDK against a deployed instance ([Embedded quickstart](/docs/agent-memory/quickstarts/embedded.md)).

## Surfaces

- [Filesystem view](/docs/agent-memory/integrations/surfaces/filesystem-view.md) - browse a context as though it were a filesystem

## MCP tools

- [MCP tools reference](/docs/agent-memory/integrations/mcp-server/tools-reference.md) - every tool the MCP server publishes, and what each takes

---

Source: https://surrealdb.com/docs/agent-memory/integrations/ai-sdks/cloudflare-workers-ai

# Cloudflare Workers AI

Using SurrealDB Agent Memory from a Cloudflare Worker alongside Workers AI models.

A Cloudflare Worker can run a model with [Workers AI](https://developers.cloudflare.com/workers-ai/) and back it with SurrealDB Agent Memory in the same request. The [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md) (`@surrealdb/memory`) uses platform `fetch` and ships no runtime dependencies, so it runs on the Workers runtime unchanged.

> [!NOTE]
> [!NOTE]
> This is an integration guide. There is no first-party Cloudflare package; the code wires the SurrealDB Agent Memory SDK into a Worker. It applies equally to the [Cloudflare Agents SDK](https://developers.cloudflare.com/agents/): construct the client the same way inside your agent.

## Installation

```bash
npm install @surrealdb/memory
```

Store the SurrealDB Agent Memory API key as a secret rather than in `wrangler.toml`:

```bash
npx wrangler secret put AGENT_MEMORY_API_KEY
```

Bind Workers AI in `wrangler.toml`:

```toml
[ai]
binding = "AI"

[vars]
AGENT_MEMORY_ENDPOINT = "https://api.spectron.example"
AGENT_MEMORY_CONTEXT = "acme-prod"
```

## Worker with memory

Recall context, run a Workers AI model with that context, then store the exchange:

```typescript
import { AgentMemory } from "@surrealdb/memory";

interface Env {
    AI: Ai;
    AGENT_MEMORY_ENDPOINT: string;
    AGENT_MEMORY_CONTEXT: string;
    AGENT_MEMORY_API_KEY: string;
}

export default {
    async fetch(request: Request, env: Env): Promise<Response> {
        const { userId, message } = await request.json();
        const scope = [`org/acme/user/${userId}`];

        const spectron = new AgentMemory({
            endpoint: env.AGENT_MEMORY_ENDPOINT,
            context: env.AGENT_MEMORY_CONTEXT,
            apiKey: env.AGENT_MEMORY_API_KEY,
        });

        // 1. Recall relevant memory as a context block.
        const memory = await spectron.context(message, { scope, k: 8 });

        // 2. Run a Workers AI model with the context injected.
        const result = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
            messages: [
                { role: "system", content: `You are a helpful assistant.\n\n## Memory\n${memory}` },
                { role: "user", content: message },
            ],
        });

        // 3. Store the exchange for next time.
        await spectron.rememberMany(
            [
                { role: "user", content: message },
                { role: "assistant", content: result.response },
            ],
            { scope },
        );

        return Response.json({ text: result.response });
    },
};
```

## Latency and subrequests

Each SurrealDB Agent Memory call is an outbound `fetch`, which counts against the Worker's subrequest limit. Two calls per turn (`context` then `rememberMany`) is typical. To keep the response fast, move the write off the critical path with `ctx.waitUntil`:

```typescript
ctx.waitUntil(spectron.rememberMany(turns, { scope }));
```

## Scope per user or session

Pass a `scope` on every call to isolate memory. A scope is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Register paths with `spectron scopes create` before first use.

## Next steps

- [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md): the full client surface
- [REST API](/docs/agent-memory/integrations/surfaces/rest.md): calling SurrealDB Agent Memory over HTTP without the SDK

---

Source: https://surrealdb.com/docs/agent-memory/integrations/ai-sdks/tanstack-ai

# TanStack AI

Adding SurrealDB Agent Memory to a TanStack AI application.

[`@tanstack/ai`](https://tanstack.com/ai) is a type-safe, provider-agnostic AI SDK for streaming chat, tool calling, and agents. SurrealDB Agent Memory adds long-term memory to a `chat()` call: recall relevant context before a generation, then store the exchange afterwards. There is no dedicated adapter. The [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md) (`@surrealdb/memory`) runs in any server handler that calls `chat()`.

> [!NOTE]
> [!NOTE]
> This is an integration guide. There is no first-party TanStack package; the code below wires the SurrealDB Agent Memory SDK into a `@tanstack/ai` handler, and you can adapt it to your server shape.

## Installation

```bash
npm install @surrealdb/memory @tanstack/ai @tanstack/ai-openai
```

SurrealDB Agent Memory holds an API key, so construct the client only on the server, never in a component or loader that ships to the browser.

## A memory-aware server handler

Recall context, prepend it to the system prompt, generate, then store the turn:

```typescript
// src/routes/api/chat.ts
import { AgentMemory } from "@surrealdb/memory";
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";

const spectron = new AgentMemory({
    endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
    context: "acme-prod",
    apiKey: process.env.AGENT_MEMORY_API_KEY!,
});

export async function POST(request: Request) {
    const { userId, message } = await request.json();
    const scope = [`org/acme/user/${userId}`];

    // 1. Recall relevant memory as a ready-to-inject context block.
    const memory = await spectron.context(message, { scope, k: 8 });

    // 2. Generate with the context block in the system prompt.
    const stream = chat({
        adapter: openaiText("gpt-4o"),
        messages: [
            { role: "system", content: `You are a helpful assistant.\n\n## Memory\n${memory}` },
            { role: "user", content: message },
        ],
    });

    // 3. Store the exchange so it is available next time.
    //    (streamToText collects the reply; stream once to the client instead
    //    if you prefer, and store from a tee.)
    return toServerSentEventsResponse(stream);
}
```

To store the turn, collect the reply with `streamToText` before returning it:

```typescript
import { chat, streamToText } from "@tanstack/ai";

const text = await streamToText(chat({
    adapter: openaiText("gpt-4o"),
    messages: [
        { role: "system", content: `You are a helpful assistant.\n\n## Memory\n${memory}` },
        { role: "user", content: message },
    ],
}));

await spectron.rememberMany(
    [
        { role: "user", content: message },
        { role: "assistant", content: text },
    ],
    { scope },
);
```

## Recall as a tool

To let the model decide when to reach for memory, expose recall as a `@tanstack/ai` tool. Build it with `toolDefinition(...).server(...)` and pass it to `chat`:

```typescript
import { chat, toolDefinition } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { z } from "zod";

const recall = toolDefinition({
    name: "recall",
    description: "Search long-term memory for context relevant to a query.",
    inputSchema: z.object({ query: z.string() }),
    outputSchema: z.object({ context: z.string() }),
}).server(async ({ query }) => {
    const context = await spectron.context(query, { scope, k: 8 });
    return { context };
});

const stream = chat({
    adapter: openaiText("gpt-4o"),
    messages: [{ role: "user", content: message }],
    tools: [recall],
});
```

The agent loop invokes the tool automatically when the model asks for it.

## Scope per user or session

Pass a `scope` on every call to isolate memory. A scope is a slash path or an array of paths, for example `["org/acme/user/alice"]` for one user, or a session-specific path such as `["org/acme/session/abc123"]`. Register paths with `spectron scopes create` before first use.

## Next steps

- [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md): the full client surface
- [REST API](/docs/agent-memory/integrations/surfaces/rest.md): if you would rather call SurrealDB Agent Memory over HTTP directly

---

Source: https://surrealdb.com/docs/agent-memory/integrations/ai-sdks/vercel-ai-sdk

# Vercel AI SDK

SurrealDB Agent Memory for the Vercel AI SDK, via language-model middleware and a tool set.

`@surrealdb/spectron-vercel-ai` integrates SurrealDB Agent Memory with the [Vercel AI SDK](https://ai-sdk.dev). Keep using your own model provider (`@ai-sdk/openai`, `@ai-sdk/anthropic`, and so on) with `generateText` / `streamText`, and let SurrealDB Agent Memory transparently:

- **inject** relevant long-term memory (and the user's profile) into the prompt before generation, and
- **store** each user and assistant exchange afterwards,

plus an optional **tool set** so the model can query memory on demand mid-generation.

The API is `createSpectron()` → `.middleware()` / `.tools()`.

## Installation

```bash
npm i @surrealdb/spectron-vercel-ai ai @surrealdb/memory
# plus your model provider, e.g.
npm i @ai-sdk/openai
```

`ai` (v7) is a peer dependency; you bring your own model provider.

## Setup

`createSpectron()` reads credentials from the environment by default:

| Variable | Description |
| --- | --- |
| `SPECTRON_ENDPOINT` | API endpoint origin |
| `SPECTRON_API_KEY` | Bearer API key |
| `SPECTRON_CONTEXT` | Context id |

```typescript
import { createSpectron } from "@surrealdb/spectron-vercel-ai";

// From the environment, bound to one user by default.
const spectron = createSpectron({ defaultScopes: "user/tobie" });

// Or pass config / a preconstructed client explicitly:
import { AgentMemory } from "@surrealdb/spectron-vercel-ai";
const spectron = createSpectron({
    client: new AgentMemory({ endpoint, apiKey, context }),
});
```

## Middleware

Wrap your model with `wrapLanguageModel`. The middleware fetches memory for the latest user message, injects it as a system message, then stores the exchange after generation:

```typescript
import { openai } from "@ai-sdk/openai";
import { generateText, wrapLanguageModel } from "ai";
import { createSpectron } from "@surrealdb/spectron-vercel-ai";

const spectron = createSpectron({ defaultScopes: "user/tobie" });

const model = wrapLanguageModel({
    model: openai("gpt-4o"),
    middleware: spectron.middleware({ sessionId: "session-123" }),
});

const { text } = await generateText({
    model,
    prompt: "What should I focus on today?",
});
```

`streamText` works identically. The middleware wraps the stream, accumulates the reply, and stores it once the stream finishes.

### Middleware options

| Option | Default | Description |
| --- | --- | --- |
| `scopes` | `defaultScopes` | DNF scope selector for reads and writes, for example `"user/tobie"`. |
| `sessionId` | n/a | Session to attach retrieved context and stored turns to. |
| `injectHistory` | `true` | Inject retrieved memory before generation. |
| `store` | `true` | Store the user and assistant exchange after generation. |
| `retrieval` | `"context"` | `"context"` (server-formatted), `"recall"` (raw hits), or `false`. |
| `k` | `8` | Max hits / context breadth to retrieve. |
| `includeProfile` | `true` | Inject the user's profile. |
| `onError` | no-op | Called on memory errors; generation still proceeds. |

> [!NOTE]
> Memory operations are fail-open: if SurrealDB Agent Memory is unreachable, the middleware falls back to a plain LLM call rather than throwing.

When you already pass a full `messages` array, turn `store` off (or set `retrieval: false` / `injectHistory: false`) to avoid duplicating history.

## Tools

`spectron.tools()` returns a Vercel AI SDK `ToolSet` the model can call during generation, bound to the same scope and session you pass:

```typescript
import { generateText, stepCountIs } from "ai";

const { text } = await generateText({
    model,
    tools: spectron.tools({ sessionId: "session-123" }),
    stopWhen: stepCountIs(3),
    prompt: "Based on our past conversations, what do I care about most?",
});
```

| Tool | What it does |
| --- | --- |
| `spectron_recall` | Semantic recall of facts and passages for a query. |
| `spectron_context` | Server-formatted context text for a query. |
| `spectron_reflect` | Synthesise over memory, optionally persisting the conclusion. |
| `spectron_remember` | Persist a fact or observation for future recall. |
| `spectron_forget` | Forget memories matching a query. |
| `spectron_profile` | The user's attributes, preferences, and instructions. |
| `spectron_inspect` | Resolve an entity, attribute, relation, or trace reference. |

## Scopes

Scopes bind reads and writes to a region of memory (a DNF selector). A bare string is a single path:

```typescript
spectron.middleware({ scopes: "user/tobie" });           // one user
spectron.middleware({ scopes: ["team/eng", "user/x"] }); // OR of two
```

## Direct client access

`spectron.client` is the underlying `@surrealdb/memory` client for anything not wrapped here: documents, sessions, entities, `chat`, and so on. See the [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md) for the full surface.

## Next steps

- [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md): using SurrealDB Agent Memory directly with the TypeScript SDK
- [Chat sessions](/docs/agent-memory/sessions/chat-sessions.md): how SurrealDB Agent Memory manages conversation sessions

---

Source: https://surrealdb.com/docs/agent-memory/integrations/automation/n8n

# n8n

Using SurrealDB and SurrealDB Agent Memory from n8n workflows.

[n8n](https://n8n.io) is a workflow-automation tool. The official community node **`@surrealdb/n8n-nodes-surrealdb`** connects a workflow to SurrealDB for full CRUD and custom SurrealQL, and works as both an action node and an [AI agent tool node](https://docs.n8n.io/advanced-ai/). To give an n8n AI Agent long-term memory with SurrealDB Agent Memory, call the [REST API](/docs/agent-memory/integrations/surfaces/rest.md) from an **HTTP Request** node.

## Install the SurrealDB node

The node runs on **self-hosted** n8n (v0.214.0+):

1. Open **Settings → Community Nodes**.
2. Click **Install** and enter `@surrealdb/n8n-nodes-surrealdb`.
3. Restart n8n if prompted.

It requires SurrealDB v3.0.0+ and connects over HTTP/HTTPS (WebSocket is not supported). It offers record, table, field, index, and relationship operations, a visual `SELECT` builder, and raw SurrealQL execution.

## Give an AI agent memory with SurrealDB Agent Memory

SurrealDB Agent Memory is a hosted service reached over HTTP with a Bearer key, so an **HTTP Request** node is the direct path. Store the endpoint and API key in n8n **Credentials** (Header Auth), not in the node.

**Recall before the agent runs** with an HTTP Request node calling `context`:

```text
POST https://api.spectron.example/api/v1/acme-prod/context
Authorization: Bearer <your-api-key>
Content-Type: application/json

{
  "query": "{{ $json.chatInput }}",
  "scope": ["org/acme/user/{{ $json.userId }}"],
  "k": 8
}
```

Feed the returned context block into the AI Agent node's system prompt.

**Store the turn afterwards** with an HTTP Request node calling `facts/batch`:

```text
POST https://api.spectron.example/api/v1/acme-prod/facts/batch
Authorization: Bearer <your-api-key>
Content-Type: application/json

{
  "turns": [
    { "role": "user", "content": "{{ $json.chatInput }}" },
    { "role": "assistant", "content": "{{ $json.output }}" }
  ],
  "scope": ["org/acme/user/{{ $json.userId }}"]
}
```

## Expose Agent Memory as an agent tool

Wrap either request in an **HTTP Request Tool** node and attach it to the AI Agent. The agent then calls recall or remember on its own during a run, the same way the framework adapters expose SurrealDB Agent Memory tools.

## Scope per user or workflow

Set the `scope` on each call to isolate memory. A scope is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Derive it from workflow data such as a chat user id. Register paths with `spectron scopes create` before first use.

## Next steps

- [REST API](/docs/agent-memory/integrations/surfaces/rest.md): the endpoints the HTTP Request nodes call
- [MCP server](/docs/agent-memory/integrations/mcp-server/install.md): if your n8n host speaks MCP instead

---

Source: https://surrealdb.com/docs/agent-memory/integrations/automation/zo-computer

# Zo Computer

Adding SurrealDB Agent Memory to a Zo Computer skill.

[Zo Computer](https://zo.computer/) is a cloud AI platform where users build reusable workflows called **skills**. SurrealDB Agent Memory gives those skills persistent memory across conversations. This is a skill-based integration. A Zo skill calls SurrealDB Agent Memory through the [Python SDK](/docs/agent-memory/integrations/sdks/python.md) (`surrealdb`), not through MCP.

> [!NOTE]
> This is an integration guide. It shows three memory helpers a Zo skill can call, backed by SurrealDB Agent Memory; adapt them to your skill's structure.

## Installation

Add the dependency to your skill and set the connection details as environment variables in Zo:

```bash
pip install --pre surrealdb
```

```bash
AGENT_MEMORY_ENDPOINT=https://api.spectron.example
AGENT_MEMORY_CONTEXT=acme-prod
AGENT_MEMORY_API_KEY=sk-spec-...
```

## Mapping Zo concepts to SurrealDB Agent Memory

| Zo concept | SurrealDB Agent Memory |
| --- | --- |
| Account | Context |
| User | Scope path (for example `user/alice`) |
| Conversation | Session |

## Memory helpers for a skill

Expose three functions a Zo workflow can call: store a turn, ask a question of memory, and fetch a context block for a prompt:

```python
import os
from surrealdb.memory import Memory

memory = Memory(
    endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
    context=os.environ["AGENT_MEMORY_CONTEXT"],
    api_key=os.environ["AGENT_MEMORY_API_KEY"],
)

def save_memory(user_id: str, user_message: str, assistant_message: str) -> None:
    """Persist a conversation turn."""
    memory.remember_many(
        [
            {"role": "user", "content": user_message},
            {"role": "assistant", "content": assistant_message},
        ],
        scopes=[f"user/{user_id}"],
    )

def query_memory(user_id: str, question: str) -> str:
    """Answer a natural-language question from stored memory."""
    return memory.chat(question, scopes=[f"user/{user_id}"]).reply

def get_context(user_id: str, query: str) -> str:
    """Return a formatted context block for an LLM prompt."""
    return memory.query_context(query, k=8, lens=[f"user/{user_id}"])
```

A skill recalls context with `get_context()` before it answers, then records the exchange with `save_memory()` so the next run of the skill has it.

## Scope per user

Each helper scopes to the Zo user with a slash path such as `user/alice`. Register paths with `spectron scopes create` before first use. On **SurrealDB Cloud**, use your context host from SurrealDB Studio **API keys** as the `AGENT_MEMORY_ENDPOINT`.

## Next steps

- [Python SDK](/docs/agent-memory/integrations/sdks/python.md): the full client surface
- [REST API](/docs/agent-memory/integrations/surfaces/rest.md): if your skill runtime is not Python

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/agno

# Agno

Adding persistent memory to Agno agents with the SurrealDB Agent Memory SDK.

[Agno](https://www.agno.com/) is a high-performance Python framework for building agents. SurrealDB Agent Memory adds long-term memory that persists across sessions and agents. There is no dedicated adapter. The [Python SDK](/docs/agent-memory/integrations/sdks/python.md) (`surrealdb`) exposes the memory operations you attach as Agno tools.

> [!NOTE]
> This is an integration guide. It wires the SurrealDB Agent Memory SDK into Agno's tool interface; adapt to your installed Agno version.

## Installation

```bash
pip install agno openai
pip install --pre surrealdb
```

**Bash**

```bash
export AGENT_MEMORY_ENDPOINT="https://api.spectron.example"
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_API_KEY="sk-spec-..."
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_ENDPOINT = "https://api.spectron.example"
$env:AGENT_MEMORY_CONTEXT = "acme-prod"
$env:AGENT_MEMORY_API_KEY = "sk-spec-..."
```

## Memory as tools

Agno agents take a `tools` list. Pass functions that call the SurrealDB Agent Memory client, and Agno exposes them to the model:

```python
import os
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from surrealdb.memory import Memory

memory = Memory(
    endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
    context=os.environ["AGENT_MEMORY_CONTEXT"],
    api_key=os.environ["AGENT_MEMORY_API_KEY"],
)
scope = ["org/acme/user/alice"]

def remember(text: str) -> str:
    """Store a durable fact for later recall."""
    memory.remember(text, scopes=scope)
    return "stored"

def recall(query: str) -> str:
    """Retrieve relevant memory for a query."""
    return memory.query_context(query, k=8, lens=scope)

agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    tools=[remember, recall],
    instructions="Use recall before answering and remember anything worth keeping.",
    markdown=True,
)

agent.print_response("What do you know about Alice's role?")
```

## Recall around a run

Recall first, add the context to the agent's instructions, then store the exchange after the response:

```python
block = memory.query_context(user_message, k=8, lens=scope)

agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    instructions=f"You are a helpful assistant.\n\n## Memory\n{block}",
)
response = agent.run(user_message)

memory.remember_many(
    [{"role": "user", "content": user_message}, {"role": "assistant", "content": response.content}],
    scopes=scope,
)
```

> [!NOTE]
> Agno ships its own session storage and user-memory primitives (`enable_user_memories=True`). Use SurrealDB Agent Memory when you want a shared, provenance-first memory that several agents or services read and write, with server-side extraction and hybrid recall.

## Scope per user

Pass a `scope` on every call to isolate memory. A scope is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Register paths with `spectron scopes create` before first use.

## Next steps

- [Python SDK](/docs/agent-memory/integrations/sdks/python.md): the full client surface
- [MCP server](/docs/agent-memory/integrations/mcp-server/install.md): if your host speaks MCP instead

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/autogen

# AutoGen

Adding persistent memory to Microsoft AutoGen agents with the SurrealDB Agent Memory SDK.

[AutoGen](https://microsoft.github.io/autogen/) builds conversational and multi-agent systems in Python. SurrealDB Agent Memory gives those agents long-term memory: recall relevant facts before a turn and store new ones after. There is no dedicated adapter. The [Python SDK](/docs/agent-memory/integrations/sdks/python.md) (`surrealdb`) exposes the memory operations you register as agent tools.

> [!NOTE]
> This is an integration guide. It wires the SurrealDB Agent Memory SDK into AutoGen's tool interface; adapt the function-registration calls to your installed AutoGen version.

## Installation

```bash
pip install autogen-agentchat 'autogen-ext[openai]'
pip install --pre surrealdb
```

**Bash**

```bash
export AGENT_MEMORY_ENDPOINT="https://api.spectron.example"
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_API_KEY="sk-spec-..."
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_ENDPOINT = "https://api.spectron.example"
$env:AGENT_MEMORY_CONTEXT = "acme-prod"
$env:AGENT_MEMORY_API_KEY = "sk-spec-..."
```

## Memory as agent tools

Wrap the SurrealDB Agent Memory client in plain functions and pass them to the agent. AutoGen calls them like any other tool:

```python
import os
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient
from surrealdb.memory import Memory

memory = Memory(
    endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
    context=os.environ["AGENT_MEMORY_CONTEXT"],
    api_key=os.environ["AGENT_MEMORY_API_KEY"],
)
scope = ["org/acme/user/alice"]

def remember(text: str) -> str:
    """Store a durable fact for later recall."""
    memory.remember(text, scopes=scope)
    return "stored"

def recall(query: str) -> str:
    """Retrieve relevant memory for a query."""
    return memory.query_context(query, k=8, lens=scope)

agent = AssistantAgent(
    name="assistant",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    tools=[remember, recall],
    system_message="Use recall before answering and remember anything worth keeping.",
)
```

## Recall around a run

To keep memory out of the agent's tool list, recall before the run and inject the context into the system message, then store the exchange yourself:

```python
block = memory.query_context(user_message, k=8, lens=scope)
agent = AssistantAgent(
    name="assistant",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    system_message=f"You are a helpful assistant.\n\n## Memory\n{block}",
)
# after the run
memory.remember_many(
    [{"role": "user", "content": user_message}, {"role": "assistant", "content": reply}],
    scopes=scope,
)
```

## Scope per user

Pass a `scope` on every call to isolate memory. A scope is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Register paths with `spectron scopes create` before first use.

## Next steps

- [Python SDK](/docs/agent-memory/integrations/sdks/python.md): the full client surface
- [MCP server](/docs/agent-memory/integrations/mcp-server/install.md): if your host speaks MCP instead

---

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

# Camel AI

Adding persistent memory to Camel AI agents with the SurrealDB Agent Memory SDK.

[Camel AI](https://www.camel-ai.org/) is a multi-agent framework for Python. SurrealDB Agent Memory gives its `ChatAgent`s shared long-term memory across runs and agents. There is no dedicated adapter. The [Python SDK](/docs/agent-memory/integrations/sdks/python.md) (`surrealdb`) supplies the memory operations, either as tools or wrapped around each step.

> [!NOTE]
> This is an integration guide. It wires the SurrealDB Agent Memory SDK into Camel AI; adapt to your installed Camel AI version.

## Installation

```bash
pip install camel-ai
pip install --pre surrealdb
```

**Bash**

```bash
export AGENT_MEMORY_ENDPOINT="https://api.spectron.example"
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_API_KEY="sk-spec-..."
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_ENDPOINT = "https://api.spectron.example"
$env:AGENT_MEMORY_CONTEXT = "acme-prod"
$env:AGENT_MEMORY_API_KEY = "sk-spec-..."
```

## Memory as function tools

Expose SurrealDB Agent Memory through CAMEL's `FunctionTool` so an agent can recall and remember on its own:

```python
import os
from camel.agents import ChatAgent
from camel.toolkits import FunctionTool
from surrealdb.memory import Memory

memory = Memory(
    endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
    context=os.environ["AGENT_MEMORY_CONTEXT"],
    api_key=os.environ["AGENT_MEMORY_API_KEY"],
)
scope = ["org/acme/user/alice"]

def remember(text: str) -> str:
    """Store a durable fact for later recall."""
    memory.remember(text, scopes=scope)
    return "stored"

def recall(query: str) -> str:
    """Retrieve relevant memory for a query."""
    return memory.query_context(query, k=8, lens=scope)

agent = ChatAgent(
    system_message="Use recall before answering and remember anything worth keeping.",
    tools=[FunctionTool(remember), FunctionTool(recall)],
)

response = agent.step("What do you know about Alice's role?")
print(response.msgs[0].content)
```

## Recall around a step

Recall relevant memory, prepend it to the system message, run the step, then store the exchange:

```python
block = memory.query_context(user_message, k=8, lens=scope)

agent = ChatAgent(system_message=f"You are a helpful assistant.\n\n## Memory\n{block}")
response = agent.step(user_message)

memory.remember_many(
    [
        {"role": "user", "content": user_message},
        {"role": "assistant", "content": response.msgs[0].content},
    ],
    scopes=scope,
)
```

> [!NOTE]
> Camel AI's own `AgentMemory` (chat-history and vector memory) manages a single agent's context window. SurrealDB Agent Memory complements it with a durable, shared substrate: server-side extraction, hybrid recall, and provenance across agents.

## Scope per user

Pass a `scope` on every call to isolate memory. A scope is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Register paths with `spectron scopes create` before first use.

## Next steps

- [Python SDK](/docs/agent-memory/integrations/sdks/python.md): the full client surface
- [MCP server](/docs/agent-memory/integrations/mcp-server/install.md): if your host speaks MCP instead

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/crewai

# CrewAI

CrewAI integration for SurrealDB Agent Memory, with memory tools and automatic per-task memory.

SurrealDB Agent Memory gives [CrewAI](https://www.crewai.com/) agents persistent, provenance-first memory. The integration offers two approaches that compose: tools an agent calls explicitly, and automatic memory that recalls before each task, writes back after it, and consolidates when the crew finishes, without changing your agents or tasks.

Package: **`spectron-crew-ai`** (PyPI). It pulls in CrewAI and the SurrealDB SDK (`surrealdb` v3, which bundles SurrealDB Agent Memory).

## Requirements

- Python 3.10+
- CrewAI 1.5+
- SurrealDB Agent Memory access: endpoint, context, and API key

## Installation

```bash
pip install spectron-crew-ai
```

## Environment

| Variable | Purpose |
| --- | --- |
| `SPECTRON_ENDPOINT` | Server URL |
| `SPECTRON_CONTEXT` | Context id |
| `SPECTRON_API_KEY` | Context API key (keep it in `.env`, not source) |
| `SPECTRON_DEFAULT_SCOPE` | Optional. Scope for writes and lens for reads, for example `user/tobie`. |
| `SPECTRON_TOP_K` | Optional. Memories recalled per query (default `5`). |

You can also pass any of these directly to `SpectronMemory(...)` or `SpectronConfig(...)` instead of using the environment.

## Memory as tools

Attach the SurrealDB Agent Memory tools and let the agent decide when to use memory:

```python
from crewai import Agent, Task, Crew
from spectron_crewai import get_spectron_tools

agent = Agent(
    role="Research Analyst",
    goal="Answer questions using long-term memory",
    backstory="You recall what you have learned before and store new findings.",
    tools=get_spectron_tools(scope="user/tobie"),
    verbose=True,
)

task = Task(
    description="What do we know about Tobie's role? Store any new facts you learn.",
    expected_output="A short summary.",
    agent=agent,
)

Crew(agents=[agent], tasks=[task]).kickoff()
```

To isolate memory per user or session, use the sessionized factory:

```python
from spectron_crewai import get_sessionized_spectron_tools

tools = get_sessionized_spectron_tools("user-123")
```

The tools map onto SurrealDB Agent Memory operations: `spectron_recall`, `spectron_remember`, `spectron_context`, `spectron_forget`, `spectron_reflect`, and `spectron_upload`.

## Automatic memory

Enable automatic memory once and run the crew as usual. Recall happens before each task, write-back runs on a background thread after each task, and consolidation runs when the crew finishes:

```python
from crewai import Agent, Task, Crew
from spectron_crewai import SpectronMemory

memory = SpectronMemory(default_scope="user/tobie")
memory.attach(verbose=True)   # register the event listener

agent = Agent(
    role="Travel Planning Specialist",
    goal="Plan trips that respect the traveller's known preferences",
    backstory="You remember past trips and preferences.",
    tools=memory.tools(),     # optional: also expose the explicit tools
)

task = Task(
    description="Plan a weekend trip for Tobie.",
    expected_output="A day-by-day plan.",
    agent=agent,
)

Crew(agents=[agent], tasks=[task]).kickoff()
memory.close()                # flush background writes on shutdown
```

`SpectronMemory` is also usable directly:

```python
memory.remember("Tobie prefers window seats", scopes="user/tobie")
hits = memory.recall("seat preference", lens="user/tobie")
answer = memory.context("What are Tobie's travel preferences?")
```

> [!NOTE]
> SurrealDB Agent Memory is exposed as tools and an event-driven memory layer rather than as a CrewAI `StorageBackend`. CrewAI's built-in storage is embedding-centric: it hands the store a vector, never the query text, whereas SurrealDB Agent Memory embeds and ranks server-side across semantic, lexical, graph, and temporal signals.

## Reliability

Writes run on a background daemon thread, so tasks never block on SurrealDB Agent Memory I/O. Every call is wrapped: failures are logged and degrade to an empty or error result rather than raising into the crew loop, and a circuit breaker disables memory for the rest of the process after repeated failures or an authentication error.

## When to use MCP or the SDK instead

- For an MCP-native host, prefer the [MCP server](/docs/agent-memory/integrations/mcp-server/install.md).
- To call SurrealDB Agent Memory directly outside CrewAI, use the [Python SDK](/docs/agent-memory/integrations/sdks/python.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/eve

# Eve

SurrealDB Agent Memory for Eve agents, via auto-memory middleware and a tool pack.

SurrealDB Agent Memory is the official memory adapter for [Eve](https://eve.dev). Eve gives agents durable execution and multi-channel reach; SurrealDB Agent Memory gives them semantic, episodic, and procedural recall with entity graphs and tri-temporal provenance.

Package: **`@surrealdb/spectron-eve`**. It ships two layers you can use independently or together: a tool pack the model calls explicitly, and auto-memory middleware that recalls before each turn and persists after it with no tool call required.

## Installation

```bash
bun add @surrealdb/spectron-eve
# peers, already present in an Eve project:
bun add eve zod
```

## Environment

```bash
AGENT_MEMORY_CONTEXT=your-context-id
AGENT_MEMORY_API_KEY=sp-...
AGENT_MEMORY_ENDPOINT=https://your-spectron-endpoint
```

You can also pass these to `createSpectronClient` directly.

## Auto-memory

Memory becomes automatic once you add two files to your `agent/` directory:

```typescript
// agent/instructions/memory.ts: recalls and injects relevant memory each turn
import { spectronMemoryInstructions } from "@surrealdb/spectron-eve";
export default spectronMemoryInstructions();
```

```typescript
// agent/hooks/memory.ts: persists the conversation back to AgentMemory
import { spectronMemoryHook } from "@surrealdb/spectron-eve";
export default spectronMemoryHook();
```

Each turn, the instructions resolver recalls memory scoped to the current user (from `ctx.session.auth`) and lowers it to a system message. The hook writes new turns back to SurrealDB Agent Memory, tagged with Eve provenance (`eve_session`, `eve_turn`, `eve_agent`, `eve_channel`).

> [!NOTE]
> Eve forbids hooks from injecting model context, which is why recall-and-inject lives in an `instructions/` resolver and only the write-back lives in a hook.

## Tool pack

To let the model recall and remember explicitly, add one static file per tool under `agent/tools/`. Eve names each tool after its filename:

```typescript
// agent/tools/recall.ts
export { recall as default } from "@surrealdb/spectron-eve/tools";
// agent/tools/remember.ts exports remember, and likewise forget, entities, timeline
```

Static per-file tools are the recommended form: they resolve once and stay stable across turns, which keeps the prompt cache warm.

| Tool | What it does |
| --- | --- |
| `recall` | Hybrid semantic retrieval over the user's memory |
| `remember` | Store a durable fact, scoped and provenance-tagged |
| `forget` | Erase memory matching a natural-language query |
| `entities` | Read the knowledge graph (entities, attributes, relations) |
| `timeline` | Tri-temporal recall: "what did we know as of ...?" |

## Memory scoping

By default a user's memory is scoped to `{ user: <principalId> }` and unified across channels, so a preference learned in one channel is recalled in another. Tune it with `ResolveScopeOptions`, which every tool factory and both middleware helpers accept:

```typescript
spectronMemoryInstructions({ scope: { includeChannel: true } }); // per-channel
spectronMemoryInstructions({ scope: { userKey: "customer" } });  // custom key
spectronMemoryInstructions({ scope: { resolve: (ctx) => ({ team: "acme" }) } });
```

## Custom client

```typescript
import { createSpectronClient, setSharedSpectronClient } from "@surrealdb/spectron-eve";

setSharedSpectronClient(
    createSpectronClient({ context: "support", endpoint: "https://...", apiKey: "sp-..." }),
);
```

## When to use MCP or the SDK instead

- For an MCP-native host, use the [MCP server](/docs/agent-memory/integrations/mcp-server/install.md).
- To call SurrealDB Agent Memory directly outside Eve, use the [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/google-adk

# Google ADK

SurrealDB Agent Memory as tools for Google's Agent Development Kit.

SurrealDB Agent Memory gives [Google ADK](https://github.com/google/adk-python) agents persistent memory that survives restarts and separate conversations. The package wraps SurrealDB Agent Memory's verbs as ADK tools; it handles entity extraction, knowledge-graph storage, temporal facts, and hybrid retrieval.

Package: **`spectron-google-adk`** (PyPI). It pulls in `google-adk` and `surrealdb`.

## Installation

```bash
pip install spectron-google-adk
```

This pulls in `google-adk` and `surrealdb` (the SurrealDB Agent Memory client ships in `surrealdb` 3.0.0a1 and later, installed automatically).

## Environment

The SurrealDB Agent Memory SDK does not read the environment itself. You pass the values in explicitly, or use `SpectronConfig.from_env()` to read them for you:

**Bash**

```bash
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_ENDPOINT="https://api.spectron.example"
export AGENT_MEMORY_API_KEY="sk-spec-..."
export GOOGLE_API_KEY="your-google-api-key"   # used by the ADK model
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_CONTEXT = "acme-prod"
$env:AGENT_MEMORY_ENDPOINT = "https://api.spectron.example"
$env:AGENT_MEMORY_API_KEY = "sk-spec-..."
$env:GOOGLE_API_KEY = "your-google-api-key"   # used by the ADK model
```

## Quickstart

`SpectronToolset` extends ADK's `BaseToolset`, so an ADK `Runner` closes it on shutdown:

```python
import asyncio
from google.adk.agents import Agent
from google.adk.runners import InMemoryRunner
from spectron_google_adk import SpectronToolset

async def main():
    toolset = SpectronToolset(
        context="acme-prod",
        endpoint="https://api.spectron.example",
        api_key="sk-spec-...",
    )

    agent = Agent(
        model="gemini-2.5-flash",
        name="assistant",
        description="An assistant with persistent memory.",
        instruction="Store durable facts with remember and look things up with recall.",
        tools=[toolset],
    )

    runner = InMemoryRunner(agent=agent)
    try:
        await runner.run_debug("Remember: Acme Corp, healthcare, 1.2M dollar contract.")
        events = await runner.run_debug("What healthcare contracts do we have?")
        for event in events:
            if event.is_final_response() and event.content:
                for part in event.content.parts:
                    if part.text:
                        print(part.text)
    finally:
        await runner.close()
        await toolset.close()

asyncio.run(main())
```

## Two ways to build tools

**`SpectronToolset`** (recommended) owns the client and manages its lifecycle. Add it as a single item in the `tools` list:

```python
toolset = SpectronToolset(config=SpectronConfig.from_env())
agent = Agent(model="gemini-2.5-flash", name="assistant", tools=[toolset])
```

**`get_spectron_tools`** returns a plain list of tools for quick scripts. Pass your own `client` to control its lifecycle:

```python
from surrealdb.memory import AsyncMemory
from spectron_google_adk import get_spectron_tools

client = AsyncMemory(context="acme-prod", endpoint="...", api_key="sk-...")
tools = get_spectron_tools(client=client)
```

## Session and tenant isolation

Bind a `session_id` (and optionally a `scope`) when you build the tools. Both are fixed at build time and are not exposed to the model, so an agent cannot read or write outside its slice of memory:

```python
toolset = SpectronToolset(config=config, session_id="user-123")
```

Two agents built with the same `session_id` share memory; different session ids stay isolated.

## Choosing which verbs to expose

All verbs are available by default. Pass `include=[...]` to narrow them, for example a collector agent that can only write and a researcher that can only read:

```python
collector = SpectronToolset(config=config, include=["remember"])
researcher = SpectronToolset(config=config, include=["recall", "reflect"])
```

The verbs are `remember`, `recall`, `forget`, `reflect`, `chat`, `consolidate`, `elaborate`, `query_context`, `inspect`, and `state`. Every tool returns a JSON-safe dict with a `status` of `"success"` or `"error"`, so a failed request reaches the model as data rather than failing the agent turn.

## When to use MCP or the SDK instead

- For an MCP-native host, use the [MCP server](/docs/agent-memory/integrations/mcp-server/install.md).
- To call SurrealDB Agent Memory directly, use the [Python SDK](/docs/agent-memory/integrations/sdks/python.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/hermes

# Hermes Agent

SurrealDB Agent Memory as a memory provider for the Hermes Agent.

SurrealDB Agent Memory is a memory provider for the [Hermes Agent](https://github.com/NousResearch/hermes-agent). Once installed and selected, the agent recalls relevant memories before each turn, writes each completed turn back to SurrealDB Agent Memory asynchronously, and consolidates memory when a session ends. It also gains explicit memory tools it can call directly.

Package: **`spectron-hermes`** (PyPI). It pulls in the SurrealDB SDK (`surrealdb` v3, which bundles SurrealDB Agent Memory) and registers the plugin with Hermes through the `hermes_agent.plugins` entry point.

> [!NOTE]
> This is the hosted provider. For a persistent filesystem on a SurrealDB instance you run, with a toolset the agent calls directly, see the [SurrealDB filesystem integration for Hermes](/docs/build/integrations/ai-frameworks/hermes.md). Only one memory provider can be active at a time, so pick one.

## Requirements

- Python 3.10+
- A running Hermes Agent install
- SurrealDB Agent Memory access: endpoint, context, and API key

## Installation

```bash
pip install spectron-hermes
```

## Environment

**Bash**

```bash
export SPECTRON_API_KEY="..."
export SPECTRON_ENDPOINT="https://your-instance.spectron.dev"
export SPECTRON_CONTEXT="my-context"
```

**PowerShell**

```powershell
$env:SPECTRON_API_KEY = "..."
$env:SPECTRON_ENDPOINT = "https://your-instance.spectron.dev"
$env:SPECTRON_CONTEXT = "my-context"
```

## Activate

Select the provider. Hermes prompts for any missing settings and writes the non-secret ones to `$HERMES_HOME/spectron.json`:

```bash
hermes memory setup      # choose "spectron"
hermes memory status     # confirm it is active
hermes                   # run a session with Spectron-backed memory
```

## Configuration

| Setting | Env var | Default | Notes |
| --- | --- | --- | --- |
| `api_key` | `SPECTRON_API_KEY` | n/a | Secret, required (kept in `.env`) |
| `endpoint` | `SPECTRON_ENDPOINT` | n/a | Required, origin with no trailing slash |
| `context` | `SPECTRON_CONTEXT` | n/a | Required; a client is pinned to one context |
| `recall_mode` | `SPECTRON_RECALL_MODE` | `hybrid` | `hybrid`, `context`, or `tools` |
| `write_frequency` | `SPECTRON_WRITE_FREQUENCY` | `turn` | `turn` or `session` |
| `top_k` | `SPECTRON_TOP_K` | `5` | Memories recalled per turn |
| `default_scope` | `SPECTRON_DEFAULT_SCOPE` | n/a | For example `user/tobie`; scope for writes and lens for reads |

**Recall modes.** `hybrid` injects raw recalled memories before each turn; `context` injects a synthesised answer instead; `tools` injects nothing and lets the model recall explicitly with `spectron_recall` / `spectron_context`.

## Tools

| Tool | Purpose |
| --- | --- |
| `spectron_recall` | Search memory across semantic, lexical, graph, and temporal signals. |
| `spectron_remember` | Store a durable fact. |
| `spectron_context` | Return a synthesised answer from memory. |
| `spectron_forget` | Supersede (default) or hard-delete. |
| `spectron_reflect` | Derive insights, optionally persisting them. |
| `spectron_upload` | Ingest a document into knowledge memory. |

## Reliability

The provider is built not to destabilise the agent. Writes run on a background daemon thread, so turns never block on I/O. Every SurrealDB Agent Memory call is wrapped; failures are logged and degrade to empty results rather than raising into the agent loop (fail open). After repeated failures or an authentication error, a circuit breaker disables memory for the rest of the session.

## When to use MCP or the SDK instead

- For an MCP-native host, use the [MCP server](/docs/agent-memory/integrations/mcp-server/install.md).
- To call SurrealDB Agent Memory directly, use the [Python SDK](/docs/agent-memory/integrations/sdks/python.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/langchain

# LangChain

LangChain and LangGraph integration for SurrealDB Agent Memory, with retrievers, agent tools, and a LangGraph store.

SurrealDB ships an official integration for the [LangChain.js](https://js.langchain.com) and [LangGraph.js](https://langchain-ai.github.io/langgraphjs/) ecosystems. It wraps the [`@surrealdb/memory`](https://www.npmjs.com/package/@surrealdb/memory) client so a chain or agent can retrieve from SurrealDB Agent Memory's knowledge base, expose memory as tools, and read memory through a LangGraph store.

The integration is TypeScript. Python applications call SurrealDB Agent Memory through the [Python SDK](/docs/agent-memory/integrations/sdks/python.md) or the [REST API](/docs/agent-memory/integrations/surfaces/rest.md) instead.

## Packages

| Package | What it provides |
| --- | --- |
| `@surrealdb/langchain-core` | Shared SurrealDB client, config, schema and filter helpers, and the SurrealDB Agent Memory HTTP client |
| `@surrealdb/langchain` | `VectorStore`, hybrid `Retriever`, `SpectronRetriever`, agent tools, and a persisting chat model wrapper |
| `@surrealdb/langgraph` | LangGraph `BaseCheckpointSaver`, `BaseStore`, and `SpectronStore`, backed by SurrealDB Agent Memory |

## Requirements

- Node.js 22+ or Bun 1+
- SurrealDB Agent Memory access: endpoint, context, and API key

## Installation

```bash
bun add @surrealdb/langchain @surrealdb/langgraph @surrealdb/memory
```

## Configure the client

Construct a client directly, or resolve one from the environment:

```typescript
import { AgentMemory } from "@surrealdb/langchain-core";

const spectron = new AgentMemory({
    context: "acme-prod",
    apiKey: process.env.AGENT_MEMORY_API_KEY!,
    endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
});
```

`resolveSpectron` reads `AGENT_MEMORY_ENDPOINT`, `AGENT_MEMORY_API_KEY`, and `AGENT_MEMORY_CONTEXT`, throwing a clear error if any is missing:

```typescript
import { resolveSpectron } from "@surrealdb/langchain-core";

const spectron = resolveSpectron({}); // all three from the environment
```

| Field | Required | Notes |
| --- | --- | --- |
| `context` | Yes | Context id, for example `"acme-prod"`. Pins every request to `/api/v1/{context}/…`. |
| `apiKey` | Yes | Bearer token, sent as `Authorization: Bearer …`. |
| `endpoint` | Yes | SurrealDB Agent Memory API origin, no trailing slash. There is no implicit default host. |

## Retrieval

`SpectronRetriever` turns knowledge-base hits into LangChain `Document`s, with the chunk text as `pageContent` and document, chunk, score, and graph metadata in `metadata`:

```typescript
import { SpectronRetriever } from "@surrealdb/langchain/retrievers";

const retriever = new SpectronRetriever({
    client: spectron,
    mode: "hybrid_graph", // "vector" | "bm25" | "hybrid" | "hybrid_graph"
    k: 8,
});

const docs = await retriever.invoke("what is the return policy?");
```

## Agent tools

Two `StructuredTool`s wire SurrealDB Agent Memory into an agent. Both accept either an instantiated `client` or a plain config object resolved from `SPECTRON_*` environment variables:

```typescript
import { SpectronQueryTool, SpectronReflectTool } from "@surrealdb/langchain/tools";

const tools = [
    new SpectronQueryTool({ client: spectron }),
    new SpectronReflectTool({ client: spectron }),
];
```

`SpectronQueryTool` takes `{ query, k?, mode?, filter? }` and returns a compact JSON array of hits. `SpectronReflectTool` takes `{ query, persist? }` and returns the reflection.

## LangGraph store

`SpectronStore` is a read-oriented `BaseStore` adapter. Reads delegate to SurrealDB Agent Memory; writes are not supported, because Agent Memory persists memory through sessions and reflections rather than raw key/value puts:

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

const store = new SpectronStore({ spectron });

await store.get(["Person"], "tobie"); // → entities.get
await store.search(["Person"], { query: "who is tobie?", limit: 5 }); // → recall
```

| Method | Backed by | Supported |
| --- | --- | --- |
| `get` | `entities.get` | Yes |
| `search` | `recall` | Yes (requires `query`) |
| `put` / `delete` / `listNamespaces` | n/a | Throws |

## Vector store without agent memory

`@surrealdb/langchain` also exposes a `VectorStore` backed by SurrealDB's native HNSW index, for RAG against a SurrealDB instance you run yourself rather than the hosted agent memory service:

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

const store = await VectorStore.initialize(new OpenAIEmbeddings(), {
    surreal: { url: "ws://localhost:8000", username: "root", password: "secret", namespace: "app", database: "rag" },
    tableName: "documents",
    dimensions: 1536,
});
```

## When to use MCP or the SDK instead

- If the host is Claude, Cursor, or another MCP-native client, prefer the [MCP server](/docs/agent-memory/integrations/mcp-server/install.md), with no adapter required.
- If your application calls SurrealDB Agent Memory directly rather than through LangChain, use the [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/llamaindex

# LlamaIndex

Adding persistent memory to LlamaIndex agents with the SurrealDB Agent Memory SDK.

[LlamaIndex](https://www.llamaindex.ai/) builds RAG and agent applications in Python. SurrealDB Agent Memory gives a LlamaIndex agent long-term memory that persists across sessions. There is no dedicated adapter. The [Python SDK](/docs/agent-memory/integrations/sdks/python.md) (`surrealdb`) exposes the memory operations you wrap as `FunctionTool`s.

> [!NOTE]
> This is an integration guide. It wires the SurrealDB Agent Memory SDK into LlamaIndex's tool interface; adapt to your installed LlamaIndex version.

## Installation

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

**Bash**

```bash
export AGENT_MEMORY_ENDPOINT="https://api.spectron.example"
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_API_KEY="sk-spec-..."
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_ENDPOINT = "https://api.spectron.example"
$env:AGENT_MEMORY_CONTEXT = "acme-prod"
$env:AGENT_MEMORY_API_KEY = "sk-spec-..."
```

## Memory as tools

Wrap the client in `FunctionTool`s and hand them to an agent:

```python
import os
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.tools import FunctionTool
from llama_index.llms.openai import OpenAI
from surrealdb.memory import Memory

memory = Memory(
    endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
    context=os.environ["AGENT_MEMORY_CONTEXT"],
    api_key=os.environ["AGENT_MEMORY_API_KEY"],
)
scope = ["org/acme/user/alice"]

def remember(text: str) -> str:
    """Store a durable fact for later recall."""
    memory.remember(text, scopes=scope)
    return "stored"

def recall(query: str) -> str:
    """Retrieve relevant memory for a query."""
    return memory.query_context(query, k=8, lens=scope)

agent = FunctionAgent(
    llm=OpenAI(model="gpt-4o"),
    tools=[FunctionTool.from_defaults(remember), FunctionTool.from_defaults(recall)],
    system_prompt="Use recall before answering and remember anything worth keeping.",
)
```

## Recall for retrieval

Because SurrealDB Agent Memory already ranks across semantic, lexical, graph, and temporal signals server-side, call `query_context` (or `recall`) directly rather than wiring it into a `VectorStoreIndex`:

```python
block = memory.query_context("what is the return policy?", k=8, lens=["org/acme"])
# inject `block` into your prompt, or return it from a query tool
```

> [!NOTE]
> LlamaIndex's built-in `Memory` stores chat history in a SQL database. SurrealDB Agent Memory is a separate, hosted memory tier with server-side extraction and hybrid retrieval. Use it when memory should be shared across agents and survive process restarts.

## Scope per user

Pass a `scope` on every call to isolate memory. A scope is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Register paths with `spectron scopes create` before first use.

## Next steps

- [Python SDK](/docs/agent-memory/integrations/sdks/python.md): the full client surface
- [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md): for LlamaIndex.TS

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/mastra

# Mastra

SurrealDB Agent Memory as a memory provider and tools for Mastra, plus the SurrealDB storage adapter.

`@surrealdb/mastra-ai` is the SurrealDB integration for [Mastra](https://mastra.ai). It ships two things that can be used separately or together: a **storage adapter** backed by a SurrealDB instance you run, and an **SurrealDB Agent Memory** memory provider backed by the hosted agent memory service.

## Requirements

- Bun 1+ or Node.js 22+
- `@mastra/core` 1.31.0+
- For the storage adapter: SurrealDB v3
- For SurrealDB Agent Memory: an endpoint, context, and API key

## Installation

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

The SurrealDB Agent Memory client ships with `zod`; install it alongside when you use that subpath:

```bash
bun add zod
```

## SurrealDB Agent Memory as a memory provider

`SpectronMemory` works standalone, with no database required. Verbatim message history is kept in-process while SurrealDB Agent Memory handles fact extraction, semantic recall, and the user profile. Every SurrealDB Agent Memory call is guarded, so a service outage degrades to verbatim-only behaviour rather than breaking the agent loop:

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

const agent = new Agent({
    name: "assistant",
    instructions: "You are a helpful assistant with long-term memory.",
    model: anthropic("claude-sonnet-4-5"),
    memory: new SpectronMemory({
        endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
        context: process.env.AGENT_MEMORY_CONTEXT!,
        apiKey: process.env.AGENT_MEMORY_API_KEY!,
    }),
});
```

Pass a Mastra `storage` to keep durable verbatim threads, messages, and working memory in SurrealDB, with SurrealDB Agent Memory layered on as the intelligence tier:

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

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

const memory = new SpectronMemory({
    endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
    context: process.env.AGENT_MEMORY_CONTEXT!,
    apiKey: process.env.AGENT_MEMORY_API_KEY!,
    storage: store, // durable verbatim history; omit to keep it in-process
});
```

## SurrealDB Agent Memory tools

Let an agent call SurrealDB Agent Memory explicitly to store, recall, forget, fetch context, and search documents:

```typescript
import { AgentMemory, createSpectronTools } from "@surrealdb/mastra-ai/spectron";

const client = new AgentMemory({
    endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
    context: process.env.AGENT_MEMORY_CONTEXT!,
    apiKey: process.env.AGENT_MEMORY_API_KEY!,
});

const agent = new Agent({
    name: "assistant",
    instructions: "Use spectronRecall before answering questions about the user.",
    model: anthropic("claude-sonnet-4-5"),
    tools: createSpectronTools(client),
});
```

The toolset is `spectronRemember`, `spectronRecall`, `spectronForget`, `spectronContext`, and `spectronSearchDocuments`. Document helpers `ingestDocument` and `searchDocuments` cover RAG.

> [!NOTE]
> Isolation is soft under a shared API key: `resourceId` maps to SurrealDB Agent Memory scopes and labels, not a hard tenant boundary. Use `client.onBehalfOf(principal)` for stronger isolation. One client is pinned to one SurrealDB Agent Memory context.

## SurrealDB storage adapter

Used on its own, `SurrealDBStore` covers conversation memory, workflow suspend/resume snapshots, scoring, observability, and native HNSW vector search against a SurrealDB instance you run:

```typescript
import { Mastra } from "@mastra/core/mastra";
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 mastra = new Mastra({ agents: { assistant }, storage: store });
await store.init();
```

It also accepts token auth or a pre-connected `Surreal` instance.

## When to use MCP or the SDK instead

- For an MCP-native host, use the [MCP server](/docs/agent-memory/integrations/mcp-server/install.md).
- To call SurrealDB Agent Memory directly, use the [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/openai-agents

# OpenAI Agents SDK

SurrealDB Agent Memory for agents built with the OpenAI Agents SDK.

SurrealDB Agent Memory gives agents built with the [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) a durable, shared memory. An agent can remember across runs, recall what it needs before answering, and share memory with other agents through a common scope.

Package: **`spectron-openai-agents-sdk`** (PyPI). Memory works two ways, and they compose: function tools the agent calls itself, and automatic memory wrapped around a run.

## Installation

```bash
pip install spectron-openai-agents-sdk
```

## Environment

**Bash**

```bash
export OPENAI_API_KEY="your-openai-api-key"
export SPECTRON_URL="https://your-spectron-endpoint"
export SPECTRON_NAMESPACE="your-namespace"
export SPECTRON_DATABASE="your-database"
export SPECTRON_TOKEN="your-token"   # optional for local, unsecured instances
```

**PowerShell**

```powershell
$env:OPENAI_API_KEY = "your-openai-api-key"
$env:SPECTRON_URL = "https://your-spectron-endpoint"
$env:SPECTRON_NAMESPACE = "your-namespace"
$env:SPECTRON_DATABASE = "your-database"
$env:SPECTRON_TOKEN = "your-token"   # optional for local, unsecured instances
```

## Memory as function tools

The agent decides when to `remember`, `recall`, `context`, `reflect`, or `forget`. `get_spectron_tools` builds the tools from the `SPECTRON_*` environment by default; pass `client=` for an explicit `SpectronClient` or `include=` to expose a subset:

```python
from agents import Agent, Runner
from spectron_openai_agents_sdk import get_spectron_tools

agent = Agent(
    name="assistant",
    instructions=(
        "You are a helpful assistant. Use recall to check memory before you "
        "answer, and use remember to store anything worth keeping."
    ),
    tools=get_spectron_tools(session_id="user-123"),
)

Runner.run_sync(agent, "My name is Ada and I work on databases.")

result = Runner.run_sync(agent, "What do you know about me?")
print(result.final_output)
```

## Automatic memory around a run

`run_with_memory` recalls memory relevant to the input, injects it into the prompt, runs the agent, and stores the result. The agent needs no memory tools of its own:

```python
import asyncio
from agents import Agent
from spectron_openai_agents_sdk import MemoryScope, run_with_memory

agent = Agent(name="assistant", instructions="You are a helpful assistant.")
scope = MemoryScope(session_id="user-123")

async def main():
    await run_with_memory(agent, "My name is Ada.", scope=scope)

    result = await run_with_memory(agent, "What is my name?", scope=scope)
    print(result.final_output)

asyncio.run(main())
```

## Persisting output with hooks

To save an agent's output while running it yourself, attach `SpectronMemoryHooks`:

```python
from agents import Runner
from spectron_openai_agents_sdk import SpectronClient, SpectronMemoryHooks, MemoryScope

client = SpectronClient.from_env()
hooks = SpectronMemoryHooks(client, MemoryScope(session_id="user-123"))

await Runner.run(agent, "Summarize our project decisions.", hooks=hooks)
```

## Multi-agent shared memory

Both approaches talk to SurrealDB Agent Memory through a single `SpectronClient`, scoped by a `MemoryScope` (`agent_id`, `session_id`, `user_id`). Agents that share a `MemoryScope` read and write the same memory, so what one agent stores is available to another.

## When to use MCP or the SDK instead

- For an MCP-native host, use the [MCP server](/docs/agent-memory/integrations/mcp-server/install.md).
- To call SurrealDB Agent Memory directly, use the [Python SDK](/docs/agent-memory/integrations/sdks/python.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/openclaw

# OpenClaw

OpenClaw plugin that backs agent memory with SurrealDB Agent Memory.

The `@surrealdb/spectron-openclaw` plugin backs [OpenClaw](https://docs.openclaw.ai/) agent memory with SurrealDB Agent Memory. It gives an agent long-term memory that survives session restarts and fresh chats: relevant memory is recalled before each turn, each turn is persisted afterwards, and the agent gains tools and a CLI for deliberate memory access.

By default the plugin **augments** OpenClaw's built-in memory. It can optionally **take over** the memory slot (see [Memory modes](#memory-modes)).

## Prerequisites

- OpenClaw 2026.4.27 or newer
- a SurrealDB Agent Memory endpoint, API key, and context id (SurrealDB Cloud or the self-hosted Agent Memory binary)

## Install

```bash
openclaw plugins install @surrealdb/spectron-openclaw
openclaw spectron setup
openclaw gateway restart
```

`openclaw spectron setup` prints the config block and the two hook flags to add.

## Configure

Configuration lives in `~/.openclaw/openclaw.json` under `plugins.entries.spectron`. Connection fields accept `${ENV_VAR}` references so secrets stay out of the file:

```json
{
  "plugins": {
    "entries": {
      "spectron": {
        "enabled": true,
        "hooks": {
          "allowConversationAccess": true,
          "allowPromptInjection": true
        },
        "config": {
          "endpoint": "${AGENT_MEMORY_ENDPOINT}",
          "apiKey": "${AGENT_MEMORY_API_KEY}",
          "context": "${AGENT_MEMORY_CONTEXT}"
        }
      }
    }
  }
}
```

> [!IMPORTANT]
> Both hook flags are required. `allowConversationAccess` lets OpenClaw deliver the `agent_end` event to the plugin. Without it, per-turn persistence is silently skipped. `allowPromptInjection` lets the plugin inject recalled context.

### Config reference

| Key | Default | Purpose |
| --- | --- | --- |
| `endpoint` | required | SurrealDB Agent Memory API origin, no trailing slash |
| `apiKey` | required | Bearer API key |
| `context` | required | Context id (namespace and database) |
| `recallScope` | whole region | Read lens narrowing recall (scope path, or array of paths) |
| `writeScope` | key default | Scope written memories are tagged with |
| `autoRecall` | `true` | Inject memory before each turn |
| `autoCapture` | `true` | Persist each turn |
| `autoConsolidate` | `true` | Consolidate at session end |
| `autoIndex` | `true` | Seed workspace memory files on start or `index` |
| `recallK` | `5` | Max hits per recall |
| `injectMode` | `"context"` | `context` (formatted text) or `recall` (ranked hits) |

## How it works

The plugin maps SurrealDB Agent Memory's operations onto OpenClaw's agent lifecycle:

| OpenClaw hook | What happens |
| --- | --- |
| `before_prompt_build` | Relevant memory is injected as `<spectron_memory>` before the model runs |
| `agent_end` | The turn is persisted, with platform metadata stripped |
| `session_end` | Recent facts are consolidated into durable observations |
| `gateway_start` / `spectron index` | Workspace memory files (`MEMORY.md`, `memory/**`) are seeded |

## Agent tools

The agent can call these directly: `spectron_recall`, `spectron_context`, `spectron_remember`, `spectron_reflect`, `spectron_forget`, `spectron_upload`, and `spectron_inspect`.

## CLI

```bash
openclaw spectron setup [--takeover]   # print config + required hook flags
openclaw spectron status               # config, slot mode, and identity
openclaw spectron health               # check the connection
openclaw spectron index                # seed workspace memory files
openclaw spectron recall <query>       # search memory
openclaw spectron reflect <query>      # synthesise an answer from memory
openclaw spectron forget <query>       # forget matching memories
```

## Memory modes

- **Augment (default):** runs alongside the built-in memory core. Nothing about the memory slot changes.
- **Takeover (experimental):** `openclaw spectron setup --takeover` prints a patch that sets `plugins.slots.memory` to `spectron`, making SurrealDB Agent Memory the sole memory provider.

---

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

# Pydantic AI

SurrealDB Agent Memory for Pydantic AI, via a toolset, auto-recall processor, and persistence helpers.

SurrealDB Agent Memory connects to [Pydantic AI](https://ai.pydantic.dev) through the framework's own extension points, so an agent can remember facts across runs, recall them when relevant, and keep a durable record of its conversations.

Package: **`spectron-pydantic-ai`** (PyPI). It gives you three surfaces, usable on their own or together:

- **Memory tools** (`SpectronToolset`): expose `recall`, `context`, `remember`, and more as tools the agent calls when it decides to.
- **Auto-recall** (`spectron_history_processor`): inject relevant memory before each model request, with no tool call required.
- **Persistence** (`store_run`, `store_messages`): write a run's messages back to SurrealDB Agent Memory so conversations survive across sessions.

## Installation

```bash
pip install spectron-pydantic-ai
```

To run against a live SurrealDB Agent Memory instance and a model provider:

```bash
pip install "spectron-pydantic-ai" "pydantic-ai-slim[openai]"
```

## Quickstart

`SpectronMemory.connect(...)` builds the client; pass the toolset to the agent:

```python
import asyncio
from pydantic_ai import Agent
from spectron_pydantic_ai import SpectronMemory, SpectronToolset

async def main():
    memory = SpectronMemory.connect(
        url="https://your-spectron-instance",
        namespace="your-namespace",
        token="your-token",
        user_id="ada",
    )
    agent = Agent("openai:gpt-4o", toolsets=[SpectronToolset(memory)])
    result = await agent.run("Remember that I prefer window seats.")
    print(result.output)

asyncio.run(main())
```

The toolset exposes `recall`, `context`, and `remember` by default. Pass `tools=ALL_TOOLS` (or a subset) to also expose `reflect` and `forget`:

```python
from spectron_pydantic_ai import ALL_TOOLS, SpectronToolset

toolset = SpectronToolset(memory, tools=ALL_TOOLS)
```

## Auto-recall

Inject relevant memory before every run without giving the agent a tool. The processor reads the latest user message, recalls related memories, and prepends them as context:

```python
from pydantic_ai import Agent
from pydantic_ai.capabilities import ProcessHistory
from spectron_pydantic_ai import spectron_history_processor

processor = spectron_history_processor(memory)
agent = Agent("openai:gpt-4o", capabilities=[ProcessHistory(processor)])
```

Use `mode="context"` to load the current working set instead of searching by the latest message.

> [!NOTE]
> Pydantic AI registers history processors through the `capabilities` argument with `ProcessHistory`, as shown. Older releases used a `history_processors=[...]` argument instead. The processor function works with both; only the way you attach it to the agent differs. Check the version in your project.

## Persistence

Store a run's messages so the next session can recall them:

```python
from spectron_pydantic_ai import store_run

result = await agent.run("I am planning a trip to Tokyo.")
await store_run(memory, result)
```

## Scoping and multi-tenancy

`SpectronMemory` carries a scope (`user_id`, `session_id`, `agent_id`) added to every operation. One connection can serve many users and sessions through narrowed views:

```python
base = SpectronMemory(client)
alice = base.scoped(user_id="alice", session_id="s1")
bob = base.scoped(user_id="bob", session_id="s2")
```

## When to use MCP or the SDK instead

- For an MCP-native host, use the [MCP server](/docs/agent-memory/integrations/mcp-server/install.md).
- To call SurrealDB Agent Memory directly outside Pydantic AI, use the [Python SDK](/docs/agent-memory/integrations/sdks/python.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/frameworks/strands-agents

# Strands Agents

SurrealDB Agent Memory as tools for the Strands Agents SDK.

SurrealDB Agent Memory exposes its memory operations as [Strands Agents](https://strandsagents.com) tools, so any Strands agent can store and retrieve long-term memory with a single line of setup.

Package: **`spectron-strands-agents`** (PyPI). It pulls in `strands-agents` and `surrealdb` (which provides the SurrealDB Agent Memory client).

```python
from strands import Agent
from spectron_strands import spectron_tools

agent = Agent(tools=spectron_tools())

agent("Remember that we signed a contract with Meditech Solutions for 1.2M GBP.")
print(agent("What is the value of the Meditech Solutions contract?"))
```

## Installation

```bash
pip install spectron-strands-agents
```

To run the examples with Amazon Bedrock (Strands' default model provider):

```bash
pip install "spectron-strands-agents[bedrock]"
```

Requires Python 3.10+.

## Environment

With these set, `spectron_tools()` builds the client for you:

**Bash**

```bash
export AGENT_MEMORY_ENDPOINT="https://api.spectron.example"
export AGENT_MEMORY_API_KEY="your-bearer-token"
export AGENT_MEMORY_CONTEXT="acme-prod"
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_ENDPOINT = "https://api.spectron.example"
$env:AGENT_MEMORY_API_KEY = "your-bearer-token"
$env:AGENT_MEMORY_CONTEXT = "acme-prod"
```

You can also pass the values directly, or hand in a client you already have:

```python
from surrealdb.memory import Memory
from spectron_strands import spectron_tools

# From explicit arguments.
tools = spectron_tools(
    endpoint="https://api.spectron.example",
    api_key="your-bearer-token",
    context="acme-prod",
)

# Or reuse an existing client.
client = Memory(context="acme-prod", endpoint="...", api_key="...")
tools = spectron_tools(client=client)
```

## Tools

`spectron_tools()` returns seven tools by default, one per SurrealDB Agent Memory operation:

| Tool | Purpose |
| --- | --- |
| `spectron_remember` | Store a fact or observation for later recall. |
| `spectron_recall` | Search memory and return the most relevant stored information. |
| `spectron_context` | Assemble a working-memory context block for a query. |
| `spectron_reflect` | Run a synthesis pass that consolidates and connects memories. |
| `spectron_forget` | Delete memories that match a query. |
| `spectron_upload` | Ingest a document so its contents become recallable. |
| `spectron_inspect` | Browse the substrate as queryable data for debugging or audit. |

Choose a subset with `include` or `exclude`:

```python
# Only the everyday read/write pair.
tools = spectron_tools(include=["remember", "recall"])

# Everything except deletion and inspection.
tools = spectron_tools(exclude=["forget", "inspect"])
```

## Scopes

Scopes isolate memory by principal, tenant, or session. A scope is a path string such as `"org/acme/user/alice"`, or a list of paths. Set a default for all tools and let the agent override it per call:

```python
tools = spectron_tools(scope="org/acme/user/alice")
```

Every tool also accepts a `scope` argument, so a multi-user agent can direct a single operation at a specific principal.

## When to use MCP or the SDK instead

- For an MCP-native host, use the [MCP server](/docs/agent-memory/integrations/mcp-server/install.md).
- To call SurrealDB Agent Memory directly, use the [Python SDK](/docs/agent-memory/integrations/sdks/python.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/antigravity

# Antigravity

Installing SurrealDB Agent Memory as an MCP server in Google Antigravity.

Installing SurrealDB Agent Memory in [Google Antigravity](https://antigravity.google/) gives the agent persistent memory across sessions, so it can recall previous decisions and project context without you repeating them.

For integration rules your agent should follow (auth, scope, endpoints, common mistakes), see **[Agent guide (AGENTS.md)](/docs/agent-memory/reference/agents.md)**.

## Configure

The `install-mcp` helper does not cover Antigravity, so configure the server by hand. Antigravity's IDE and CLI share a central MCP configuration at `~/.gemini/config/mcp_config.json`. In the IDE you can reach it through **Manage MCP servers → View raw config**.

> [!NOTE]
> Antigravity uses a stricter schema than most MCP clients: remote Streamable HTTP servers use **`serverUrl`**, not `url`.

```json
{
  "mcpServers": {
    "spectron": {
      "serverUrl": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

On **SurrealDB Cloud**, use your context host from SurrealDB Studio **API keys** (not a generic shared domain). **Self-hosted:** replace the URL with your SurrealDB Agent Memory server's base URL + `/mcp`.

> [!NOTE]
> `X-Spectron-Context` is a client-side convenience for templating. Each API key is bound to one Context, so **`context_id` is optional** on every tool call; omit it to use the key's Context. An explicit value that does not match the key still returns **`401`**.

## Verify the installation

1. Open Antigravity
2. Open the MCP store and confirm **spectron** appears with a healthy indicator
3. In a chat, ask "What MCP tools do you have available?" Antigravity should report the SurrealDB Agent Memory tools

If the server is unhealthy, check that `serverUrl` (your context host + `/mcp`) and the API key in `mcp_config.json` are correct.

## Usage examples

**Store project context:**

> "Remember that this repo uses a monorepo layout with apps in `apps/` and shared packages in `packages/`."

Antigravity calls `memory_store` with a suitable `scope`.

**Recall before answering:**

> "Where should a new shared utility live in this project?"

Antigravity calls `memory_recall` before composing its response.

## Scope per tool call

Narrow reads and writes with the per-tool **`scope`** argument (slash paths, for example `["org/acme/project/platform"]`). Register paths with `spectron scopes create` before first use. For project isolation, use separate contexts or distinct scope paths.

## Removing SurrealDB Agent Memory

Open `mcp_config.json` and delete the `"spectron"` key from `mcpServers`.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/claude-desktop-and-code

# Claude

Connecting SurrealDB Agent Memory to Claude Code, Cowork, and Claude Desktop.

The official [SurrealDB plugin marketplace for Claude](https://github.com/surrealdb/ai-claude-plugin) ships a **`spectron`** plugin that connects Claude to your SurrealDB Agent Memory instance's `/mcp` endpoint and installs a usage skill. It supports **Claude Code**, **Cowork**, and **Claude Desktop**. There is no default URL - point it at your own instance (SurrealDB Cloud: your context host from SurrealDB Studio **API keys**; self-hosted: your server's base URL).

## Claude Code and Cowork

### Install the plugin

Add the marketplace, then install the `spectron` plugin:

```bash
/plugin marketplace add surrealdb/ai-claude-plugin
/plugin install spectron@surrealdb
```

Claude Code reads the plugin directly - the MCP server and the usage skill load automatically.

### Configure

The SurrealDB Agent Memory MCP has no default URL. Set your instance endpoint and a bearer token before launching Claude Code, then restart:

**Bash**

```bash
export AGENT_MEMORY_MCP_URL="https://<your-spectron-instance>/mcp"
export AGENT_MEMORY_MCP_TOKEN="<your-api-key>"
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_MCP_URL = "https://<your-spectron-instance>/mcp"
$env:AGENT_MEMORY_MCP_TOKEN = "<your-api-key>"
```

If `AGENT_MEMORY_MCP_URL` is unset, the server will not connect. The plugin's MCP entry expands these variables:

```json
{
  "mcpServers": {
    "spectron": {
      "type": "http",
      "url": "${AGENT_MEMORY_MCP_URL}",
      "headers": {
        "Authorization": "Bearer ${AGENT_MEMORY_MCP_TOKEN}"
      }
    }
  }
}
```

The bearer token is your SurrealDB Agent Memory context API key. Each key is bound to one Context, so `context_id` is optional on every tool call.

### Verify the installation

```bash
claude mcp list
```

You should see **spectron** connected. In a session, ask "What MCP tools do you have access to?" and Claude should list the SurrealDB Agent Memory tools: `remember`, `recall`, `context`, `reflect`, `forget`, `upload`, `inspect`.

### Usage examples

**Remember architectural decisions as you code:**

```bash
claude "Remember that we have decided to use event sourcing for the orders service and that the aggregate root is OrderAggregate."
```

Claude Code calls `remember`. The next time you start a session in this project, `recall` surfaces this decision before Claude answers questions about the orders service.

**Recall context before a coding task:**

```bash
claude "What do you remember about our database schema decisions?"
```

## Claude Desktop

Claude Desktop has no plugin/marketplace format, so set it up manually. Both options use your instance's `/mcp` URL and a bearer token; there is no default.

### Option A - Connectors UI (recommended)

Open **Settings → Connectors → Add custom connector** and add:

| Name | URL | Header |
|---|---|---|
| `spectron` | your SurrealDB Agent Memory `/mcp` endpoint (for example `https://<your-spectron-instance>/mcp`) | `Authorization: Bearer <your-api-key>` |

### Option B - Manual config

Merge the following into `claude_desktop_config.json`, then restart Claude Desktop:

| Platform | Path |
|---|---|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |

```json
{
  "mcpServers": {
    "spectron": {
      "type": "http",
      "url": "https://<your-spectron-instance>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}
```

You can validate the file with `cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | python3 -m json.tool`.

### Skill (optional)

Claude Desktop cannot load plugins, but you can add the SurrealDB Agent Memory usage skill by hand: open **Settings → Skills → Upload skill** and upload the `plugins/spectron/skills/mcp/` folder from the [plugin repo](https://github.com/surrealdb/ai-claude-plugin).

### Verify the installation

1. Restart Claude Desktop completely (quit and reopen)
2. Start a new conversation
3. Click the tools icon (the hammer symbol) in the composer - the seven SurrealDB Agent Memory tools should appear
4. Ask "What MCP tools do you have?" and Claude should describe the SurrealDB Agent Memory tools

## Scope on tool calls

Narrow reads and writes with the per-tool **`scope`** argument (slash paths, for example `["org/acme/user/alice"]` or `["org/acme/project/orders-service"]`). Register paths with `spectron scopes create` before first use. Use separate contexts or distinct scope paths to keep memory isolated per repository.

## Removing SurrealDB Agent Memory

**Claude Code / Cowork:** remove the `spectron` plugin from the `/plugin` menu, or run `claude mcp remove spectron`.

**Claude Desktop:** remove the `spectron` connector, or delete the `"spectron"` key from `claude_desktop_config.json`, then restart Claude Desktop.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/codex

# Codex

Installing SurrealDB Agent Memory as an MCP server in the OpenAI Codex CLI.

Installing SurrealDB Agent Memory in the [OpenAI Codex CLI](https://developers.openai.com/codex/) gives Codex persistent memory across sessions, so it can recall previous decisions and project context without you repeating them.

For integration rules your agent should follow (auth, scope, endpoints, common mistakes), see **[Agent guide (AGENTS.md)](/docs/agent-memory/reference/agents.md)**. You can add it to your project's `AGENTS.md`, which Codex reads automatically.

> [!NOTE]
> SurrealDB also publishes a [Codex plugin](https://github.com/surrealdb/ai-codex-plugin) that adds a SurrealDB Agent Memory usage skill. It is a local marketplace for now - clone the repo, then run `codex plugin marketplace add "$PWD"` and `codex plugin add spectron@surrealdb`. The manual MCP configuration below works with or without it.

## Configure

Codex reads MCP servers from `~/.codex/config.toml` (or `.codex/config.toml` in a trusted project). Add a Streamable HTTP entry with a `url` and a bearer token sourced from an environment variable:

```toml
[mcp_servers.spectron]
url = "https://<your-context-host>/mcp"
bearer_token_env_var = "AGENT_MEMORY_API_KEY"
http_headers = { "X-Spectron-Context" = "acme-prod" }
```

Then export the key so Codex can read it:

**Bash**

```bash
export AGENT_MEMORY_API_KEY="<your-api-key>"
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_API_KEY = "<your-api-key>"
```

On **SurrealDB Cloud**, use your context host from SurrealDB Studio **API keys** (not a generic shared domain). **Self-hosted:** replace the URL with your SurrealDB Agent Memory server's base URL + `/mcp`.

Alternatively, add it from the terminal:

```bash
codex mcp add spectron --url https://<your-context-host>/mcp --bearer-token-env-var AGENT_MEMORY_API_KEY
```

> [!NOTE]
> `X-Spectron-Context` is a client-side convenience for templating. Each API key is bound to one Context, so **`context_id` is optional** on every tool call; omit it to use the key's Context. An explicit value that does not match the key still returns **`401`**.

## Verify the installation

```bash
codex mcp list
```

You should see **spectron** listed. Start a Codex session and ask "What MCP tools do you have available?" Codex should report the SurrealDB Agent Memory tools. If it does not appear, check that the URL and the `AGENT_MEMORY_API_KEY` environment variable are correct.

## Usage examples

**Remember a decision as you work:**

> "Remember that we've standardised on Zod for request validation across the API services."

Codex calls `memory_store` with an appropriate `scope` (for example `["org/acme/project/api"]`).

**Recall context before a task:**

> "What did we decide about request validation?"

Codex calls `memory_recall` before answering.

## Scope per tool call

Narrow reads and writes by passing a **`scope`** argument on each tool (slash paths, for example `["org/acme/project/api"]`). Register paths with `spectron scopes create` before first use. For project isolation, use separate contexts or distinct scope paths.

## Removing SurrealDB Agent Memory

Delete the `[mcp_servers.spectron]` block from `~/.codex/config.toml`, or run `codex mcp remove spectron`.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/cursor

# Cursor

Installing SurrealDB Agent Memory as an MCP server in Cursor.

Installing SurrealDB Agent Memory in Cursor gives every AI interaction in your editor access to persistent memory. Cursor's agent can recall previous decisions, project context, and user preferences across sessions without you having to repeat yourself.

For integration rules your agent should follow (auth, scope, endpoints, common mistakes), see **[Agent guide (AGENTS.md)](/docs/agent-memory/reference/agents.md)**. You can copy that page into `.cursor/rules/` or a project skill.

## Install

The MCP server lives at `/mcp` on your SurrealDB Agent Memory instance. On **SurrealDB Cloud** the base is your context host from SurrealDB Studio **API keys**; self-hosted, it is your server's base URL. Point Cursor at it with [`install-mcp`](https://github.com/supermemoryai/install-mcp):

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client cursor \
  --header "Authorization: Bearer <your-api-key>" \
  --oauth no
```

The URL is the first argument; auth is passed with `--header`, and `--oauth no` skips the OAuth prompt (SurrealDB Agent Memory uses a static Bearer key). The command writes the configuration to `~/.cursor/mcp.json`. If that file already exists, the `spectron` entry is merged in without disturbing other MCP servers.

### What gets written

**SurrealDB Cloud:** use your context host from SurrealDB Studio **API keys** (not a generic shared domain):

```json
{
  "mcpServers": {
    "spectron": {
      "url": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

**Self-hosted:** replace the URL with your SurrealDB Agent Memory server's base URL + `/mcp`.

> [!NOTE]
> `X-Spectron-Context` is a **client-side convenience** for templating. Each API key is bound to one Context, so **`context_id` is optional** on every tool call; omit it to use the key's Context. An explicit value that does not match the key still returns **`401`**.

### Scope per tool call

The shipped `install-mcp` helper does **not** set a default scope. Narrow reads and writes by passing a **`scope`** argument on each tool (slash paths, for example `["org/acme/user/alice"]` or `["org/acme/project/platform-v3"]`). Register paths with `spectron scopes create` before first use.

For project isolation, use separate contexts or distinct scope paths in tool arguments, not install-time flags.

## Verify the installation

1. Open Cursor
2. Navigate to **Settings → Features → MCP**
3. You should see **spectron** listed with a green indicator confirming the server is reachable
4. Open the chat panel and ask: "What MCP tools do you have available?" - Cursor should report the seven SurrealDB Agent Memory tools

If the indicator is red, check that the URL (your context host + `/mcp`) and API key in `~/.cursor/mcp.json` are correct.

## Usage examples

### Storing project context

At the start of a work session, tell Cursor about the project:

> "Remember that we're using a hexagonal architecture pattern in this codebase and that all new services should implement the `Repository` interface before touching the domain layer."

Cursor invokes `remember` with an appropriate `scope` (for example `["org/acme/project/platform-v3"]`). The next time you start a session, that instruction is retrieved by `recall` before Cursor answers questions about architecture.

### Recalling decisions before answering questions

When you ask an architectural question:

> "Should I add this feature to the gateway service or create a new service?"

Cursor calls `recall` with the query and scope before composing its response, retrieving previous decisions about service boundaries.

### Querying the knowledge base

If you have synced your documentation into SurrealDB Agent Memory's authoritative knowledge:

> "What does our internal wiki say about the deployment process?"

Cursor calls `recall` against the knowledge base and incorporates the retrieved content into its response.

## Updating the configuration

To change the API key or context host after installation, edit `~/.cursor/mcp.json` directly or re-run the install command. Re-running merges the new values over the existing entry:

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client cursor \
  --header "Authorization: Bearer <your-api-key>" \
  --oauth no
```

## Removing SurrealDB Agent Memory from Cursor

Open `~/.cursor/mcp.json` and delete the `"spectron"` key from `mcpServers`. Cursor will no longer offer the SurrealDB Agent Memory tools in subsequent sessions.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/jetbrains

# JetBrains

Installing SurrealDB Agent Memory as an MCP server in JetBrains IDEs (IntelliJ IDEA, PyCharm, WebStorm, Rider, and more).

JetBrains AI Assistant supports MCP servers across the IDE suite: IntelliJ IDEA, PyCharm, WebStorm, GoLand, Rider, and the others. Installing SurrealDB Agent Memory gives the assistant persistent memory across sessions, so it can recall previous decisions and project context without you repeating them. For the standalone [JetBrains Air](/docs/agent-memory/integrations/mcp-server/coding-assistants/jetbrains-air.md) app, follow that guide instead - it configures MCP in a different place.

For integration rules your agent should follow (auth, scope, endpoints, common mistakes), see **[Agent guide (AGENTS.md)](/docs/agent-memory/reference/agents.md)**.

## Configure

The `install-mcp` helper does not cover JetBrains IDEs, so configure the server by hand. Open **Settings → Tools → AI Assistant → Model Context Protocol (MCP)**, click **Add**, and paste a JSON configuration. AI Assistant connects to remote servers over Streamable HTTP:

```json
{
  "mcpServers": {
    "spectron": {
      "url": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

On **SurrealDB Cloud**, use your context host from SurrealDB Studio **API keys** (not a generic shared domain). **Self-hosted:** replace the URL with your SurrealDB Agent Memory server's base URL + `/mcp`.

> [!NOTE]
> `X-Spectron-Context` is a client-side convenience for templating. Each API key is bound to one Context, so **`context_id` is optional** on every tool call; omit it to use the key's Context. An explicit value that does not match the key still returns **`401`**.

## Verify the installation

1. Reopen **Settings → Tools → AI Assistant → Model Context Protocol (MCP)** and confirm **spectron** is listed and connected
2. Open the AI Assistant chat and ask "What MCP tools do you have available?" It should report the SurrealDB Agent Memory tools

If the server fails to connect, check that the URL (your context host + `/mcp`) and API key are correct.

## Usage examples

**Store a project decision:**

> "Remember that this service uses hexagonal architecture and all adapters live under `internal/adapters`."

AI Assistant calls `memory_store` with a suitable `scope`.

**Recall context before a change:**

> "What did we decide about where adapters live in this project?"

AI Assistant calls `memory_recall` before answering.

## Scope per project

Narrow reads and writes with the per-tool **`scope`** argument (slash paths, for example `["org/acme/project/platform"]`). Register paths with `spectron scopes create` before first use. For project isolation, use separate contexts or distinct scope paths.

## Removing SurrealDB Agent Memory

Open **Settings → Tools → AI Assistant → Model Context Protocol (MCP)**, select the **spectron** entry, and remove it.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/jetbrains-air

# JetBrains Air

Installing SurrealDB Agent Memory as an MCP server in JetBrains Air.

[JetBrains Air](https://www.jetbrains.com/air/) is a standalone agentic development environment that runs Claude Agent, OpenAI Codex, Gemini CLI, Junie, or any ACP-compatible agent installed on your machine. Installing SurrealDB Agent Memory gives the agent you select persistent memory across sessions, so it can recall previous decisions and project context without you repeating them.

For integration rules your agent should follow (auth, scope, endpoints, common mistakes), see **[Agent guide (AGENTS.md)](/docs/agent-memory/reference/agents.md)**. Air picks up instruction files that already exist in your project, including `CLAUDE.md` and the `.claude` folder for Claude Agent, and passes them to the selected agent.

> [!NOTE]
> This page covers JetBrains Air. For AI Assistant inside IntelliJ IDEA, PyCharm, WebStorm and the other IDEs, see the [JetBrains](/docs/agent-memory/integrations/mcp-server/coding-assistants/jetbrains.md) guide.

## Configure

The `install-mcp` helper does not cover JetBrains Air, so configure the server by hand.

1. Open **Settings** and go to **AI | MCP Servers**
2. Turn on **Enable MCP support**
3. Click **Add Global MCP Server**, or use the drop-down to choose **Add Local MCP Server** or **Add Workspace MCP Server**
4. Paste the configuration below and save it

```json
{
  "mcpServers": {
    "spectron": {
      "url": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

On **SurrealDB Cloud**, use your context host from SurrealDB Studio **API keys** (not a generic shared domain). **Self-hosted:** replace the URL with your SurrealDB Agent Memory server's base URL + `/mcp`.

Air connects on save, because SurrealDB Agent Memory authenticates with a static Bearer key rather than OAuth.

### Choosing a scope

The three **Add** options write the same JSON to different places:

| Scope | Where it lives | Use it for |
| --- | --- | --- |
| Global | Air's own settings on your machine | A personal key you want in every project |
| Local | `.air/mcp.json` in the project root | Project-specific configuration, stored with Air's other project settings |
| Workspace | `.mcp.json` in the project root | A repository that already carries MCP configuration |

Workspace servers start only when **Launch workspace MCP servers** is also enabled on the same settings page.

> [!WARNING]
> Air reads the bearer token from the configuration file itself, and tokens passed through environment variables are not supported. Both `.air/mcp.json` and `.mcp.json` sit in the project root, so add the file to `.gitignore` before you paste a key, or use the **Global** scope to keep the key out of the repository.

> [!NOTE]
> `X-Spectron-Context` is a client-side convenience for templating. Each API key is bound to one Context, so **`context_id` is optional** on every tool call; omit it to use the key's Context. An explicit value that does not match the key still returns **`401`**.

## Verify the installation

1. Reopen **Settings → AI | MCP Servers** and confirm **spectron** is in the **Added** list with a green status indicator and a tool count
2. Start a task and ask "What MCP tools do you have available?" The agent should report the SurrealDB Agent Memory tools

If the indicator is not green, check that the URL (your context host + `/mcp`) and API key are correct.

## Usage examples

**Store a project decision:**

> "Remember that this service uses hexagonal architecture and all adapters live under `internal/adapters`."

The agent calls `remember` with a suitable `scope`.

**Recall context before a change:**

> "What did we decide about where adapters live in this project?"

The agent calls `recall` before answering.

Air's permission mode, cycled with `Shift+Tab`, applies to the task as a whole rather than to individual tool calls. In **Ask** mode the agent requests approval before it changes files or runs commands.

## Scope per tool call

Narrow reads and writes with the per-tool **`scope`** argument (slash paths, for example `["org/acme/project/platform"]`). Register paths with `spectron scopes create` before first use. For project isolation, use separate contexts or distinct scope paths.

## Removing SurrealDB Agent Memory

Open **Settings → AI | MCP Servers**, select the **spectron** entry, and remove it. For the Local and Workspace scopes you can instead delete the `"spectron"` key from `.air/mcp.json` or `.mcp.json`.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/opencode

# OpenCode

Installing SurrealDB Agent Memory as an MCP server in OpenCode.

Installing SurrealDB Agent Memory in [OpenCode](https://opencode.ai/) gives the agent persistent memory across sessions, so it can recall previous decisions and project context without you repeating them.

For integration rules your agent should follow (auth, scope, endpoints, common mistakes), see **[Agent guide (AGENTS.md)](/docs/agent-memory/reference/agents.md)**. OpenCode reads a project `AGENTS.md` automatically.

## Quick install

Install with [`install-mcp`](https://github.com/supermemoryai/install-mcp) - the `/mcp` URL is the first argument, auth goes through `--header`, and `--oauth no` skips the OAuth prompt (SurrealDB Agent Memory uses a static Bearer key):

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client opencode \
  --header "Authorization: Bearer <your-api-key>" \
  --oauth no
```

Or configure it by hand, as below.

## Configure

OpenCode reads configuration from `opencode.json` in your workspace root (or `~/.config/opencode/opencode.json` for a global entry). Add SurrealDB Agent Memory as a remote MCP server:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "spectron": {
      "type": "remote",
      "url": "https://<your-context-host>/mcp",
      "enabled": true,
      "headers": {
        "Authorization": "Bearer {env:AGENT_MEMORY_API_KEY}",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

The `{env:AGENT_MEMORY_API_KEY}` syntax reads the key from the environment, keeping the secret out of the file:

**Bash**

```bash
export AGENT_MEMORY_API_KEY="<your-api-key>"
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_API_KEY = "<your-api-key>"
```

On **SurrealDB Cloud**, use your context host from SurrealDB Studio **API keys** (not a generic shared domain). **Self-hosted:** replace the URL with your SurrealDB Agent Memory server's base URL + `/mcp`.

> [!NOTE]
> `X-Spectron-Context` is a client-side convenience for templating. Each API key is bound to one Context, so **`context_id` is optional** on every tool call; omit it to use the key's Context. An explicit value that does not match the key still returns **`401`**.

## Verify the installation

Start OpenCode and ask "What MCP tools do you have available?" It should report the SurrealDB Agent Memory tools. If they do not appear, check that `opencode.json` is valid JSON and that the URL and `AGENT_MEMORY_API_KEY` are correct.

## Usage examples

**Remember a decision as you code:**

> "Remember that we use conventional commits and squash-merge every pull request."

OpenCode calls `memory_store` with a suitable `scope`.

**Recall context before a task:**

> "What's our commit and merge convention?"

OpenCode calls `memory_recall` before answering.

## Scope per tool call

Narrow reads and writes with the per-tool **`scope`** argument (slash paths, for example `["org/acme/project/cli"]`). Register paths with `spectron scopes create` before first use. For project isolation, use separate contexts or distinct scope paths.

## Removing SurrealDB Agent Memory

Delete the `"spectron"` entry from the `mcp` object in `opencode.json`, or set `"enabled": false` to disable it without removing the configuration.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/vscode

# VS Code

Installing SurrealDB Agent Memory as an MCP server in VS Code.

VS Code supports MCP servers through the [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) extension (version 1.99 or later with agent mode enabled) and through compatible AI extensions that implement the MCP client protocol.

## Prerequisites

- VS Code 1.99 or later
- GitHub Copilot extension with agent mode enabled, **or** another MCP-compatible VS Code extension

To enable Copilot agent mode: open VS Code settings, search for `github.copilot.chat.agent.enabled`, and set it to `true`.

## Install

The MCP server lives at `/mcp` on your SurrealDB Agent Memory instance. On **SurrealDB Cloud** the base is your context host from SurrealDB Studio **API keys**; self-hosted, it is your server's base URL. Point VS Code at it with [`install-mcp`](https://github.com/supermemoryai/install-mcp):

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client vscode \
  --header "Authorization: Bearer <your-api-key>" \
  --oauth no
```

The URL is the first argument; auth is passed with `--header`, and `--oauth no` skips the OAuth prompt (SurrealDB Agent Memory uses a static Bearer key). The installer writes the configuration to `.vscode/mcp.json` in your current working directory, which makes the MCP server available to everyone who opens the workspace - suitable for shared team projects.

To make the server available in every workspace instead, add the same entry to your global VS Code config by hand (see the paths below).

### Config file locations

| Scope | Path |
|---|---|
| Workspace | `.vscode/mcp.json` (relative to your project root) |
| Global (macOS) | `~/Library/Application Support/Code/User/mcp.json` |
| Global (Windows) | `%APPDATA%\Code\User\mcp.json` |
| Global (Linux) | `~/.config/Code/User/mcp.json` |

### What gets written

**`.vscode/mcp.json` (workspace):**

```json
{
  "servers": {
    "spectron": {
      "type": "http",
      "url": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

VS Code uses the `servers` key (rather than `mcpServers`) and requires a `type` field. The `install-mcp` command handles this difference automatically.

### Committing the config

The `.vscode/mcp.json` file can be committed to your repository so the team shares the same MCP server configuration. The API key should **not** be committed directly. Instead, use a VS Code input variable to prompt for the key at runtime:

```json
{
  "inputs": [
    {
      "id": "spectronApiKey",
      "type": "promptString",
      "description": "Spectron API key",
      "password": true
    }
  ],
  "servers": {
    "spectron": {
      "type": "http",
      "url": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer ${input:spectronApiKey}",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

VS Code will prompt each developer for their API key the first time they use the MCP server in the workspace.

## Verify the installation

1. Open VS Code and the workspace
2. Open the Copilot chat panel (`Ctrl+Shift+I` or `Cmd+Shift+I`)
3. Switch to **Agent** mode using the mode selector in the chat panel
4. Type `@spectron` or ask: "What MCP servers are available?"
5. Copilot should acknowledge the SurrealDB Agent Memory tools

Alternatively, open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) and run **MCP: List Servers** to see the status of all configured MCP servers including SurrealDB Agent Memory.

## Usage examples

### Remembering project decisions in agent mode

In Copilot agent mode:

> "Remember that we use Prisma for all database access in this project and that direct SQL queries are not permitted outside of migration scripts."

Copilot calls `remember`. The decision persists across VS Code sessions and is recalled the next time you ask a database-related question.

### Recalling context before refactoring

Before asking Copilot to refactor a module:

> "What do you know about the design decisions behind the authentication module?"

Copilot calls `recall`, retrieves the relevant context, and incorporates it into its refactoring suggestions.

### Searching project documentation

If your team's documentation is synced into SurrealDB Agent Memory's knowledge base:

> "What does our architecture decision record say about the API gateway pattern?"

Copilot calls `recall` and returns the relevant ADR content.

## Scope per workspace

Keep memory isolated per repository by passing a **`scope`** argument on each tool call (for example `["org/acme/project/platform-v3"]`), or by using separate SurrealDB Agent Memory contexts. The install helper does not set default scope headers.

## Removing SurrealDB Agent Memory

Delete the `"spectron"` entry from `.vscode/mcp.json` (workspace) or from the global `mcp.json` file. VS Code will stop offering the SurrealDB Agent Memory tools in subsequent sessions.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/windsurf

# Windsurf

Installing SurrealDB Agent Memory as an MCP server in Windsurf.

Windsurf's Cascade agent supports MCP servers natively. Installing SurrealDB Agent Memory gives Cascade persistent memory - it can recall decisions, project context, and user preferences across sessions without requiring you to re-explain your codebase on every invocation.

## Install

The MCP server lives at `/mcp` on your SurrealDB Agent Memory instance. On **SurrealDB Cloud** the base is your context host from SurrealDB Studio **API keys**; self-hosted, it is your server's base URL. Point Windsurf at it with [`install-mcp`](https://github.com/supermemoryai/install-mcp):

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client windsurf \
  --header "Authorization: Bearer <your-api-key>" \
  --oauth no
```

The URL is the first argument; auth is passed with `--header`, and `--oauth no` skips the OAuth prompt (SurrealDB Agent Memory uses a static Bearer key). The installer writes the configuration to Windsurf's global MCP config file and merges it with any existing entries.

### Config file location

| Platform | Path |
|---|---|
| macOS | `~/.codeium/windsurf/mcp_config.json` |
| Windows | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` |
| Linux | `~/.codeium/windsurf/mcp_config.json` |

### What gets written

```json
{
  "mcpServers": {
    "spectron": {
      "serverUrl": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

Windsurf uses `serverUrl` rather than `url`. The `install-mcp` command writes the correct key automatically.

## Scope on tool calls

Pass a **`scope`** argument on each tool (slash paths, for example `["org/acme/user/alice"]`). The install helper does not set default scope; register paths with `spectron scopes create` before first use.

## Verify the installation

1. Restart Windsurf
2. Open the Cascade panel
3. Run a prompt: "What MCP tools do you have access to?" - Cascade should report the seven SurrealDB Agent Memory tools
4. Alternatively, open **Windsurf → Settings → MCP** to see a list of configured servers and their connection status

If SurrealDB Agent Memory does not appear or shows as disconnected, check that the config file is valid JSON and that the API key and URL are correct.

## Usage examples

### Storing project conventions

At the start of a new project or codebase:

> "Remember that this project uses domain-driven design with bounded contexts. The `orders` and `inventory` contexts must never share a database table directly - communication is via events only."

Cascade calls `remember`. The architectural constraint is available in all future sessions and is recalled automatically when Cascade encounters related code.

### Recalling previous decisions

Before suggesting a solution:

> "What do you remember about how we handle authentication in this codebase?"

Cascade calls `recall` and returns relevant stored context before composing its response.

### Querying the knowledge base

If your internal documentation is synced into SurrealDB Agent Memory's authoritative knowledge store:

> "What does our runbook say about rolling back a failed deployment?"

Cascade calls `recall` and retrieves the relevant runbook section.

### Building a persistent project brief

Ask Cascade to save a comprehensive project brief at the start of an engagement:

> "Store the following as project context: we are building a multi-tenant SaaS platform in Go, using SurrealDB for application data, deployed on Kubernetes in AWS eu-west-1."

Cascade calls `remember` with the full brief. Subsequent sessions recall this context automatically via `recall`.

## Updating the configuration

To change the API key or context host, edit `~/.codeium/windsurf/mcp_config.json` directly or re-run the install command:

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client windsurf \
  --header "Authorization: Bearer <your-api-key>" \
  --oauth no
```

Re-running the command merges the updated values over the existing configuration entry.

## Removing SurrealDB Agent Memory

Open `~/.codeium/windsurf/mcp_config.json` and delete the `"spectron"` key from `mcpServers`. Restart Windsurf to apply the change.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/coding-assistants/zed

# Zed

Installing SurrealDB Agent Memory as an MCP server in the Zed editor.

Zed calls MCP servers **context servers**. Installing SurrealDB Agent Memory gives Zed's assistant persistent memory across sessions, so it can recall previous decisions and project context without you repeating them.

For integration rules your agent should follow (auth, scope, endpoints, common mistakes), see **[Agent guide (AGENTS.md)](/docs/agent-memory/reference/agents.md)**. Zed reads a project `AGENTS.md` automatically.

## Quick install

Install with [`install-mcp`](https://github.com/supermemoryai/install-mcp) - the `/mcp` URL is the first argument, auth goes through `--header`, and `--oauth no` skips the OAuth prompt (SurrealDB Agent Memory uses a static Bearer key):

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client zed \
  --header "Authorization: Bearer <your-api-key>" \
  --oauth no
```

Or configure it by hand, as below.

## Configure

Open Zed settings (`cmd`/`ctrl` `,`), which edits `~/.config/zed/settings.json`, and add SurrealDB Agent Memory under `context_servers`. Recent Zed versions connect to a remote server directly from a `url`:

```json
{
  "context_servers": {
    "spectron": {
      "url": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

On **SurrealDB Cloud**, use your context host from SurrealDB Studio **API keys** (not a generic shared domain). **Self-hosted:** replace the URL with your SurrealDB Agent Memory server's base URL + `/mcp`.

> [!NOTE]
> Zed runs stdio context servers as child processes. If your Zed version does not yet accept a remote `url`, bridge the HTTP endpoint through `mcp-remote` instead:
> ```json
> {
>   "context_servers": {
>     "spectron": {
>       "command": {
>         "path": "npx",
>         "args": ["-y", "mcp-remote", "https://<your-context-host>/mcp", "--header", "Authorization: Bearer <your-api-key>"]
>       }
>     }
>   }
> }
> ```

## Verify the installation

1. Open the Agent panel (the assistant icon in the status bar)
2. Open the settings view and confirm **spectron** appears under context servers with a running indicator
3. Ask "What MCP tools do you have available?" Zed should report the SurrealDB Agent Memory tools

If the server does not start, check that `settings.json` is valid JSON and that the URL and API key are correct.

## Usage examples

**Store a project decision:**

> "Remember that we render Markdown with a custom pipeline and never pull in a heavyweight parser."

Zed calls `memory_store` with a suitable `scope`.

**Recall context before a task:**

> "How do we handle Markdown rendering in this project?"

Zed calls `memory_recall` before answering.

## Scope per project

Narrow reads and writes with the per-tool **`scope`** argument (slash paths, for example `["org/acme/project/editor"]`). Register paths with `spectron scopes create` before first use. For project isolation, use separate contexts or distinct scope paths.

## Removing SurrealDB Agent Memory

Delete the `"spectron"` entry from `context_servers` in `~/.config/zed/settings.json`.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/install

# Installing the MCP server

Connect SurrealDB Agent Memory to Claude, Cursor, Windsurf, VS Code, and other MCP clients.

SurrealDB Agent Memory serves MCP (Streamable HTTP) at **`/mcp`** on the same host and port as the REST API. Point any MCP client at that endpoint:

- **SurrealDB Cloud** - your context host from SurrealDB Studio **API keys** with `/mcp` appended, for example `https://abc123.spectron.cloud/mcp`.
- **Self-hosted** - your server's base URL plus `/mcp`, for example `http://localhost:9090/mcp`.

Authentication uses **`Authorization: Bearer`**, the same as REST. Each key is bound to one Context, so `context_id` is optional on every tool call. The tools map to the unified substrate: `remember`, `recall`, `context`, `reflect`, `forget`, `upload`, `inspect`.

## Quick install

[`install-mcp`](https://github.com/supermemoryai/install-mcp) writes the correct config for most clients. Pass the `/mcp` URL as the first argument, your key with `--header`, and `--oauth no` to skip the OAuth prompt (SurrealDB Agent Memory uses a static Bearer key, not OAuth):

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client cursor \
  --header "Authorization: Bearer <your-api-key>" \
  --oauth no
```

Use this for `cursor`, `vscode`, `windsurf`, `zed`, and `opencode`. **Claude** (Code, Cowork, Desktop) and **Codex** have a dedicated SurrealDB plugin - see the [Claude](/docs/agent-memory/integrations/mcp-server/coding-assistants/claude-desktop-and-code.md) and [Codex](/docs/agent-memory/integrations/mcp-server/coding-assistants/codex.md) guides. JetBrains IDEs, JetBrains Air, and Antigravity use the manual configuration below.

## Manual configuration

Any MCP client can be configured by hand:

```json
{
  "mcpServers": {
    "spectron": {
      "url": "https://<your-context-host>/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}
```

Some clients use a different key or shape - `serverUrl` in Windsurf and Antigravity, a `servers` object with a `type` field in VS Code. The per-client guides show each one.

## Per-client guides

- [Claude Desktop and Claude Code](/docs/agent-memory/integrations/mcp-server/coding-assistants/claude-desktop-and-code.md)
- [Cursor](/docs/agent-memory/integrations/mcp-server/coding-assistants/cursor.md)
- [VS Code](/docs/agent-memory/integrations/mcp-server/coding-assistants/vscode.md)
- [Windsurf](/docs/agent-memory/integrations/mcp-server/coding-assistants/windsurf.md)
- [Codex](/docs/agent-memory/integrations/mcp-server/coding-assistants/codex.md)
- [Antigravity](/docs/agent-memory/integrations/mcp-server/coding-assistants/antigravity.md)
- [OpenCode](/docs/agent-memory/integrations/mcp-server/coding-assistants/opencode.md)
- [JetBrains](/docs/agent-memory/integrations/mcp-server/coding-assistants/jetbrains.md)
- [JetBrains Air](/docs/agent-memory/integrations/mcp-server/coding-assistants/jetbrains-air.md)
- [Zed](/docs/agent-memory/integrations/mcp-server/coding-assistants/zed.md)

## MCP vs SDK

| Situation | Use |
| --- | --- |
| Coding assistant with native MCP | `install-mcp` or manual `/mcp` config |
| Application code (Python/TS) | `surrealdb` / `@surrealdb/memory` |
| Agent framework with harness adapter | `spectron-crew-ai`, `@surrealdb/spectron-vercel-ai`, … |
| Custom infrastructure | [REST API](/docs/agent-memory/reference/rest-api.md) |

Tool schemas: [MCP tools reference](/docs/agent-memory/reference/mcp-tools.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/mcp-server/tools-reference

# MCP tools reference

Complete reference for all seven SurrealDB Agent Memory MCP tools.

SurrealDB Agent Memory exposes seven tools over the Model Context Protocol at `/mcp`: **`remember`**, **`recall`**, **`context`**, **`reflect`**, **`forget`**, **`upload`**, and **`inspect`**. All tools use the same **`Authorization: Bearer`** authentication as the REST API. Point the MCP URL at `https://<your-context-host>/mcp` on SurrealDB Cloud.

> [!NOTE]
> Older docs used names such as `memory_store`, `memory_recall`, and `knowledge_search`. Those prefixes are retired - use the short names above.

> [!NOTE]
> REST-aligned responses use **camelCase** (`queryMs`, `traceId`, `trace.traceId`). **Scope** arguments are slash paths (for example `org/acme/user/alice`). Register paths with `spectron scopes create` before use.

## Common fields

Tool responses correlate with graph-resident traces via **`traceId`** or **`trace.traceId`** (depending on the underlying endpoint). Use `GET .../traces/{traceId}` for the full record, or MCP **`inspect`** with `ref: "trace:<id>"`.

Every tool accepts an optional **`context_id`**. When omitted, the server uses the Context bound to the bearer API key. An explicit `context_id` that does not match that binding returns **`401`**.

---

## `remember`

Store a conversational exchange or free-text fact. SurrealDB Agent Memory auto-classifies content, reconciles against existing memory, and persists structured records.

**REST equivalent:** `POST /api/v1/{context_id}/facts`

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `text` | string | Yes | Content to remember |
| `session_id` | string | No | Existing session to append to |
| `scope` | string[] or nested arrays | No | DNF write selector within `memory:write` |
| `labels` | string[] | No | Descriptive `key=value` labels |
| `infer` | `"full"` \| `"preview"` \| `"none"` | No | Default `full` |
| `context_id` | string | No | Omit to use the bearer key's Context |

### Output

Structured diff: entities, attributes, relations, instructions, uncertainties, corrections, plus a stand-in `trace_id`.

### Example

```text
Tool: remember
Input: {
  "text": "I just got promoted to VP of Engineering and I'm moving to Singapore next month.",
  "scope": ["org/acme/user/alice"]
}
```

---

## `recall`

Unified search over experiential facts and document passages. Returns ranked hits (not a synthesised answer).

**REST equivalent:** `POST /api/v1/{context_id}/query`

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `query` | string | Yes | Natural-language question |
| `k` | integer | No | Hit count (default 10, max 50) |
| `mode` | string | No | `vector` \| `bm25` \| `graph` \| `hybrid` |
| `lens` | string[] or nested arrays | No | DNF read lens |
| `labels` | string[] | No | Optional filters |
| `context_id` | string | No | Omit to use the bearer key's Context |

### Example

```text
Tool: recall
Input: {
  "query": "What role does Alice have?",
  "k": 10,
  "lens": ["org/acme"]
}
```

---

## `context`

Assemble a markdown context block for prompt injection (profile + relevant facts).

**REST equivalent:** `POST /api/v1/{context_id}/context`

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `query` | string | Yes | What context to assemble |
| `lens` | string[] or nested arrays | No | DNF read lens |
| `labels` | string[] | No | Optional filters |
| `context_id` | string | No | Omit to use the bearer key's Context |

---

## `reflect`

Synthesise insights across memory. Optionally persist with `persist: true`.

**REST equivalent:** `POST /api/v1/{context_id}/reflect`

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `query` | string | Yes | Synthesis question |
| `persist` | boolean | No | Default `false` |
| `context_id` | string | No | Omit to use the bearer key's Context |

---

## `forget`

Soft-delete attributes that match a natural-language query. Use `purge: true` for permanent erasure including history.

**REST equivalent:** `POST /api/v1/{context_id}/forget`

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `query` | string | Yes | What to stop believing |
| `purge` | boolean | No | Also erase supersession history |
| `context_id` | string | No | Omit to use the bearer key's Context |

---

## `upload`

Upload a document (base64). Processing is asynchronous - poll with `inspect` or REST.

**REST equivalent:** `POST /api/v1/{context_id}/documents`

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `bytes_base64` | string | Yes | Document bytes (RFC 4648) |
| `title` | string | No | Display title |
| `source` | string | No | Provenance string |
| `mime_type` | string | No | MIME type |
| `filename` | string | No | Original filename |
| `scopes` | nested arrays | No | DNF write selector (`scope` alias accepted) |
| `labels` | string[] | No | Labels on the document |
| `context_id` | string | No | Omit to use the bearer key's Context |

### Example

```text
Tool: upload
Input: {
  "bytes_base64": "aGVsbG8=",
  "title": "Team handbook",
  "scopes": [["org/acme/team/eng"]],
  "labels": ["team=eng"]
}
```

---

## `inspect`

Look up an entity, trace, or document by typed reference.

**REST equivalents:** `GET .../entities/...`, `GET .../traces/{id}`, `GET .../documents/{id}`

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `ref` | string | Yes | `entity:<Type>/<Name>`, `trace:<id>`, or `document:<id>` |
| `context_id` | string | No | Omit to use the bearer key's Context |

### Example

```text
Tool: inspect
Input: {
  "ref": "document:01hx9…"
}
```

---

## Errors

Operation failures return **`isError: true`** tool results with `structuredContent.error.status` mirroring REST (404, 403, 429, 401, 500). JSON-RPC **`error`** is reserved for protocol faults (bad params, unknown tool). Auth and missing-Context failures are masked as **401**. See [MCP tools - error handling](/docs/agent-memory/reference/mcp-tools.md#error-handling).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/observability/agentops

# AgentOps

Using SurrealDB Agent Memory in an agent instrumented by AgentOps.

[AgentOps](https://www.agentops.ai/) monitors AI agents: session replay, LLM cost tracking, and tool-call telemetry across most agent frameworks. It is complementary to SurrealDB Agent Memory: AgentOps observes the run; SurrealDB Agent Memory provides the memory. This guide uses both together with the [Python SDK](/docs/agent-memory/integrations/sdks/python.md) (`surrealdb`).

## Installation

> [!NOTE]
> This is an integration guide. AgentOps and SurrealDB Agent Memory are separate services; AgentOps instruments the agent, and Agent Memory's calls show up in the captured session.

```bash
pip install agentops openai
pip install --pre surrealdb
```

**Bash**

```bash
export AGENTOPS_API_KEY="..."
export AGENT_MEMORY_ENDPOINT="https://api.spectron.example"
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_API_KEY="sk-spec-..."
```

**PowerShell**

```powershell
$env:AGENTOPS_API_KEY = "..."
$env:AGENT_MEMORY_ENDPOINT = "https://api.spectron.example"
$env:AGENT_MEMORY_CONTEXT = "acme-prod"
$env:AGENT_MEMORY_API_KEY = "sk-spec-..."
```

## Instrument the agent, remember with Agent Memory

`agentops.init()` auto-instruments supported LLM and framework calls. Use the SurrealDB Agent Memory client for recall and storage inside the instrumented run:

```python
import os
import agentops
from openai import OpenAI
from surrealdb.memory import Memory

agentops.init(os.environ["AGENTOPS_API_KEY"])

llm = OpenAI()
memory = Memory(
    endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
    context=os.environ["AGENT_MEMORY_CONTEXT"],
    api_key=os.environ["AGENT_MEMORY_API_KEY"],
)
scope = ["org/acme/user/alice"]

def answer(user_message: str) -> str:
    block = memory.query_context(user_message, k=8, lens=scope)

    completion = llm.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": f"You are a helpful assistant.\n\n## Memory\n{block}"},
            {"role": "user", "content": user_message},
        ],
    )
    reply = completion.choices[0].message.content

    memory.remember_many(
        [{"role": "user", "content": user_message}, {"role": "assistant", "content": reply}],
        scopes=scope,
    )
    return reply
```

The model call is captured in the AgentOps session automatically. To attribute the SurrealDB Agent Memory operations too, wrap them in an AgentOps operation span (`@agentops.operation`) so recall and storage appear in the session timeline.

## Scope per user

Pass a `scope` on every SurrealDB Agent Memory call to isolate memory. A scope is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Register paths with `spectron scopes create` before first use.

## Next steps

- [Python SDK](/docs/agent-memory/integrations/sdks/python.md): the full client surface
- [Agent frameworks](/docs/agent-memory/integrations/frameworks/crewai.md): AgentOps also instruments CrewAI, AutoGen, and others that have first-party SurrealDB Agent Memory adapters

---

Source: https://surrealdb.com/docs/agent-memory/integrations/observability/respan

# Respan

Using SurrealDB Agent Memory alongside Respan tracing and the Respan LLM gateway.

[Respan](https://www.respan.ai/) is an LLM engineering platform: tracing, evals, and a gateway across many model providers. It is complementary to SurrealDB Agent Memory: Respan observes and evaluates your agent, while Agent Memory gives it memory. This guide runs the two together with the [Python SDK](/docs/agent-memory/integrations/sdks/python.md) (`surrealdb`).

> [!NOTE]
> This is an integration guide. Respan and SurrealDB Agent Memory are separate services; the code shows how they sit side by side in one request.

## Installation

> [!NOTE]
> `Spectron` was the project name for SurrealDB Agent Memory. These type names
> will be renamed in a future release.

```bash
pip install openai
pip install --pre surrealdb
```

```bash
export AGENT_MEMORY_ENDPOINT="https://api.spectron.example"
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_API_KEY="sk-spec-..."
export RESPAN_API_KEY="..."
```

## Route model calls through Respan, memory through SurrealDB Agent Memory

Point your model client's base URL at the Respan gateway so every call is traced and cost-attributed, and use the SurrealDB Agent Memory client for recall and storage. Check the [Respan docs](https://www.respan.ai/docs/documentation/overview) for the current gateway base URL:

```python
import os
from openai import OpenAI
from surrealdb.memory import Memory

# Model calls flow through Respan (traced, logged, cost-attributed).
llm = OpenAI(
    base_url="https://gateway.respan.ai/v1",  # see Respan docs for the exact URL
    api_key=os.environ["RESPAN_API_KEY"],
)

# Memory is handled by SurrealDB Agent Memory.
memory = Memory(
    endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
    context=os.environ["AGENT_MEMORY_CONTEXT"],
    api_key=os.environ["AGENT_MEMORY_API_KEY"],
)
scope = ["org/acme/user/alice"]

def answer(user_message: str) -> str:
    block = memory.query_context(user_message, k=8, lens=scope)

    completion = llm.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": f"You are a helpful assistant.\n\n## Memory\n{block}"},
            {"role": "user", "content": user_message},
        ],
    )
    reply = completion.choices[0].message.content

    memory.remember_many(
        [{"role": "user", "content": user_message}, {"role": "assistant", "content": reply}],
        scopes=scope,
    )
    return reply
```

The recall and storage calls appear in Respan's trace tree alongside the model call, so you can see the memory operations and their latency for each turn.

## Scope per user

Pass a `scope` on every SurrealDB Agent Memory call to isolate memory. A scope is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Register paths with `spectron scopes create` before first use.

## Next steps

- [Python SDK](/docs/agent-memory/integrations/sdks/python.md): the full client surface
- [Traces](/docs/agent-memory/reference/rest-api.md): SurrealDB Agent Memory's own decision traces, correlated by `traceId`

---

Source: https://surrealdb.com/docs/agent-memory/integrations/sdks/dart

# Dart SDK

Using SurrealDB Agent Memory from Dart and Flutter applications.

The SurrealDB Agent Memory client ships in the [`surrealdb`](https://github.com/surrealdb/surrealdb.dart) package on pub, as a standalone import that does not depend on the database driver. It works in any Dart project and on every Flutter target (Android, iOS, web, and desktop).

## Installation

Add the package to `pubspec.yaml`:

```yaml
dependencies:
  surrealdb:
```

```bash
dart pub get
# or, in a Flutter project
flutter pub get
```

## Client construction

Import the SurrealDB Agent Memory entry point directly. The client is pinned to one context and talks to SurrealDB Agent Memory's REST API over HTTPS:

```dart
import 'package:surrealdb/memory.dart';

final client = AgentMemory(
  endpoint: 'https://api.memory.example',
  context: 'acme-prod',
  apiKey: 'sp-your-key',
);
```

Importing `package:surrealdb/memory.dart` pulls in only the SurrealDB Agent Memory client, not the SurrealDB database driver.

## Remember, recall, and chat

```dart
await client.remember('I was promoted to CTO', scopes: 'user/tobie');

final hits = await client.recall("What is Tobie's role?", k: 10);

await for (final chunk in client.chatStream('Tell me a story')) {
  stdout.write(chunk.delta);
}

client.close();
```

`scopes` accepts a slash-path string or a list of paths. Register paths with `spectron scopes create` before first use.

The client covers the rest of the SurrealDB Agent Memory end-user surface (documents, sessions, entities, and governance) over the same REST API. See the [REST API](/docs/agent-memory/integrations/surfaces/rest.md) for the full contract.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/sdks/elixir

# Elixir SDK

Using SurrealDB Agent Memory from Elixir applications.

The SurrealDB Agent Memory client ships in the [`:surrealdb`](https://github.com/surrealdb/surrealdb.elixir) package on Hex as a typed REST client. It aims for feature parity with the JavaScript SDKs (`surrealdb` and `@surrealdb/memory`).

## Installation

Add `:surrealdb` to your `mix.exs`:

```elixir
def deps do
  [
    {:surrealdb, "~> 0.1"}
  ]
end
```

## Client construction

Create a client pinned to one context and pass it to every call:

```elixir
client =
  SurrealDB.Memory.new(
    endpoint: System.fetch_env!("AGENT_MEMORY_ENDPOINT"),
    context: "acme-prod",
    api_key: System.fetch_env!("AGENT_MEMORY_API_KEY")
  )
```

## Remember, recall, and chat

```elixir
{:ok, _} = SurrealDB.Memory.remember(client, "I just got promoted to CTO", scopes: "user/tobie")
{:ok, hits} = SurrealDB.Memory.recall(client, "What is Tobie's role?", k: 10)
{:ok, %{"reply" => reply}} = SurrealDB.Memory.chat(client, "What do you know about me?")
```

Stream the chat loop with `stream: true`:

```elixir
{:ok, stream} = SurrealDB.Memory.chat(client, "Tell me a story", stream: true)

for chunk <- stream do
  IO.write(chunk["delta"])
end
```

## Namespaces

Grouped operations live in dedicated modules, each taking the client first:

```elixir
{:ok, doc} = SurrealDB.Memory.Documents.upload(client, title: "Handbook", file: "handbook.pdf")
{:ok, session} = SurrealDB.Memory.Sessions.create(client)
{:ok, minted} = SurrealDB.Memory.Keys.create(client, name: "ci", ttl_seconds: 3600)
```

The available namespaces are `Documents`, `Entities`, `Sessions`, `Lifecycle`, `Traces`, `Principals`, `Scopes`, and `Keys`.

## Delegation

`on_behalf_of/2` returns a new client whose requests carry the `X-Spectron-On-Behalf-Of` header; the original client is unchanged:

```elixir
as_alex = SurrealDB.Memory.on_behalf_of(client, "principal:alex")
{:ok, _} = SurrealDB.Memory.remember(as_alex, "Reviewed the Q3 plan")
```

## Errors

SurrealDB Agent Memory calls return `{:error, %SurrealDB.Memory.Error{}}` whose `kind` is one of `:auth`, `:validation`, `:not_found`, `:rate_limit`, `:scope`, `:server`, or `:connection`. Each error carries the response `trace_id` when the server provided one.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/sdks/go

# Go SDK

Using SurrealDB Agent Memory from Go applications and agents.

The SurrealDB Agent Memory client ships **inside** the Go SDK, in the `spectron` package of [`surrealdb.go`](https://github.com/surrealdb/surrealdb.go). It is a typed REST client whose types track the SurrealDB Agent Memory OpenAPI specification. Every method takes a `context.Context`, and a `*memory.Client` is safe for concurrent use.

## Installation

```bash
go get github.com/surrealdb/surrealdb.go/memory
```

```go
import "github.com/surrealdb/surrealdb.go/memory"
```

## Client construction

A `*memory.Client` is pinned to a single context and calls `/api/v1/{context}/...`:

```go
client, err := memory.New(
    "acme-prod",                    // context id
    "https://api.memory.example", // endpoint
    "sk-spec-...",                  // api key
    memory.WithTimeout(30*time.Second),
    memory.WithMaxRetries(3),
)
if err != nil {
    return err
}
defer client.Close()
```

| Argument | Required | Notes |
| --- | --- | --- |
| `contextID` | Yes | Context id, for example `"acme-prod"`. |
| `endpoint` | Yes | Full URL of the SurrealDB Agent Memory host. Trailing slashes are trimmed. |
| `apiKey` | Yes | Bearer token, sent as `Authorization: Bearer <key>`. The SDK never reads environment variables. |

## Remember and recall

```go
ctx := context.Background()

client.Remember(ctx, &memory.RememberRequest{
    Text:   "Acme acquired Beta",
    Scopes: memory.ScopeSets{{"team/acme"}},
    Infer:  memory.InferFull,
})

client.RememberMany(ctx, &memory.RememberManyRequest{
    Messages: []memory.BatchMessage{
        {Role: memory.RoleUser, Content: "I just got promoted to CTO"},
        {Role: memory.RoleAssistant, Content: "Congratulations!"},
    },
    Extract: memory.ExtractWholeConversation,
})

res, err := client.Recall(ctx, &memory.RecallRequest{
    Query: "what role do I have at Acme",
    K:     10,
    Mode:  memory.MemoryModeHybrid,
})
for _, hit := range res.Hits {
    fmt.Println(hit.Score, hit.Source, hit.Text)
}
```

`Remember` and `RememberMany` send an `Idempotency-Key` header derived from the method, path, body, and a 30-second bucket, so a retry within the bucket collapses onto the previous attempt server-side.

## Chat

```go
reply, _ := client.Chat(ctx, &memory.ChatRequest{Message: "what's my role?"})
fmt.Println(reply.Reply)

// Streaming via Go 1.23 range-over-func.
for chunk, err := range client.ChatStream(ctx, &memory.ChatRequest{Message: "what's my role?"}) {
    if err != nil {
        return err
    }
    fmt.Print(chunk.Delta)
    if chunk.Done {
        fmt.Println("\n[trace]", chunk.TraceID)
        break
    }
}
```

## Documents and sub-clients

Grouped operations live under sub-client accessors:

```go
f, _ := os.Open("returns.pdf")
defer f.Close()

doc, err := client.Documents().Upload(ctx, f,
    memory.WithFilename("returns.pdf"),
    memory.WithContentType("application/pdf"),
)
fmt.Println(doc.ID, doc.Status)
```

| Accessor | Methods |
| --- | --- |
| `Documents()` | `Upload`, `Reprocess`, `List`, `Get`, `Delete`, `Chunks`, `FetchRaw`, `Query`, `RecomputeLinks`, keyword helpers |
| `Entities()` | `List`, `Get`, `Delete`, `History` |
| `Sessions()` | `Create`, `Delete`, `Context`, `Turns` |
| `Scopes()` | `List`, `Register`, `Delete`, `Forget`, `Grants` |
| `Principals()` | `List`, `Get`, `Effective`, `Grant`, `Revoke` |
| `Keys()` | `Create`, `List`, `Delete`, `Rotate` |
| `Traces()` | `List`, `Get`, `Stats` |

Top-level verbs also include `Forget`, `QueryContext`, `Consolidate`, `Reflect`, `Elaborate`, `Inspect`, `State`, `Profile`, `Whoami`, and `Health`.

## Errors

Failures wrap `*memory.APIError` and match sentinel errors such as `memory.ErrNotFound`:

```go
_, err := client.Recall(ctx, &memory.RecallRequest{Query: "x"})
if errors.Is(err, memory.ErrNotFound) {
    var api *memory.APIError
    if errors.As(err, &api) {
        fmt.Printf("not found (trace=%s)\n", api.TraceID)
    }
}
```

`GET` requests and idempotent writes retry on connection errors and 5xx responses (default 3 attempts); other writes do not. See the [REST API](/docs/agent-memory/reference/rest-api.md) for the full contract.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/sdks/haskell

# Haskell SDK

Using SurrealDB Agent Memory from Haskell applications.

The SurrealDB Agent Memory client ships as the `surrealdb-memory` package in the [SurrealDB Haskell SDK](https://github.com/surrealdb/surrealdb.haskell). It is a typed client for the SurrealDB Agent Memory platform: store and recall memories, drive the chat loop, and manage documents, entities, sessions, lifecycle, traces, principals, scopes, and keys.

## Installation

There are no package-manager releases yet, so add the package from the repository. With cabal, in `cabal.project`:

```cabal
packages: .

source-repository-package
  type: git
  location: https://github.com/surrealdb/surrealdb.haskell
  subdir: surrealdb-memory
```

Then add `surrealdb-memory` to your component's `build-depends`. The project builds with GHC 9.4 and 9.6.

## Client construction

```haskell
{-# LANGUAGE OverloadedStrings #-}

import AgentMemory

main :: IO ()
main = do
  client <- newAgentMemory
    (defaultAgentMemoryOptions "acme-prod" "sk_your_api_key" "https://api.memory.example")
  ...
```

`defaultAgentMemoryOptions` takes the context id, the API key, and the endpoint.

## Remember, recall, and chat

```haskell
  -- Store a memory.
  _ <- remember client "Alice moved to Berlin" defaultRememberOptions

  -- Recall relevant memories.
  answer <- recall client "Where does Alice live?" defaultRecallOptions
  print answer

  -- Chat with the memory loop.
  reply <- chat client "What do you know about me?" defaultChatOptions
  print reply
```

The client also exposes `context`, `reflect`, and `forget`, alongside the document, entity, session, lifecycle, trace, principal, scope, and key operations.

## Delegation

Act on behalf of another principal, which sends the `X-Spectron-On-Behalf-Of` header:

```haskell
  let delegated = onBehalfOf client "principal:alice"
  _ <- remember delegated "note for Alice" defaultRememberOptions
```

## Errors

The SurrealDB Agent Memory client throws `AgentMemoryError`, classified by HTTP status into kinds such as `AuthFailed`, `ScopeRejected`, `RateLimited`, and `ServerFailed`.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/sdks/javascript-and-typescript

# JavaScript and TypeScript SDK

Using SurrealDB Agent Memory from JavaScript and TypeScript applications.

Published package: **`@surrealdb/memory`**, a typed REST client for the SurrealDB Agent Memory end-user API. It uses platform `fetch`, ships no runtime dependencies, and aligns with SurrealDB Agent Memory’s OpenAPI specification.

> [!NOTE]
> **npm:** The client is published under the **`@surrealdb`** scope (`@surrealdb/memory`), so no third party can squat the namespace. It was previously published as `@surrealdb/spectron`; that package is deprecated.

## Installation

```bash
npm install @surrealdb/memory
# or: pnpm / yarn / bun add @surrealdb/memory
```

Node.js 18+ or a modern bundler for browser use.

## Client construction

```typescript
import { AgentMemory } from "@surrealdb/memory";

const client = new AgentMemory({
  endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
  context: "acme-prod",
  apiKey: process.env.AGENT_MEMORY_API_KEY!,
});
```

The client is **async-only** (all methods return Promises). It is pinned to one context and calls `/api/v1/{context}/…`.

| Option | Default | Description |
| --- | --- | --- |
| `context` | required | Context id. |
| `endpoint` | required | SurrealDB Agent Memory host URL. |
| `apiKey` | required | Bearer token. |
| `timeout` | `30000` | Milliseconds per request. |
| `maxRetries` | `3` | Retries for GETs and idempotent writes. |

## Scope

On the wire, scope is a **ScopeSet**: an ordered array of slash-path strings (for example `["org/acme/user/alice"]`). Register paths before first use; see [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md).

The TypeScript client accepts a path string or an array of paths, and both serialise to the wire `ScopeSet`.

## Remember and recall

```typescript
await client.remember("Alice was promoted to CTO.", {
  infer: "full",
  scope: ["org/acme/user/alice"],
});

await client.rememberMany([
  { role: "user", content: "I was promoted to CTO." },
  { role: "assistant", content: "Congratulations!" },
], { scope: ["org/acme/user/alice"] });

const hits = await client.recall("What is Alice's role?", { k: 10 });

const block = await client.context("What is Alice's role?", { k: 10 });
```

`remember` and `rememberMany` attach an `Idempotency-Key` header for safe retries within a 30-second window.

## Documents and chat

```typescript
const doc = await client.documents.upload({
  file: documentFile,
  title: "Returns policy",
  scope: ["org/acme/team/eng"],
  labels: ["team=eng"],
});

await client.chat("Summarise what you know about Alice", {
  scope: ["user/alice"],
});

const stream = await client.chat("Tell me a story", { stream: true });
for await (const chunk of stream) {
  process.stdout.write(chunk.delta);
}
```

## Other verbs and namespaces

Top-level: `forget`, `consolidate`, `reflect`, `elaborate`, `state`, `profile`, `inspect`, `audit`, `fsck`, `health`.

Namespaces: `client.documents` (including `keywords`), `client.sessions`, `client.entities`, `client.scopes`, `client.principals`, `client.traces`, `client.lifecycle`.

→ Full tables: [JavaScript SDK reference](/docs/agent-memory/reference/sdk-javascript.md)

## Errors and retries

The client throws typed errors so you can branch on failure precisely.

```typescript
import {
  AuthError,
  ConnectionError,
  NotFoundError,
  RateLimitError,
  ScopeError,
  ServerError,
  AgentMemoryError,
  ValidationError,
} from "@surrealdb/memory";

try {
  const hits = await client.recall("what is my name?", { scope: ["user/alice"] });
} catch (err) {
  if (err instanceof AuthError) { /* 401 */ }
  else if (err instanceof ScopeError) { /* 403 */ }
  else if (err instanceof NotFoundError) { /* 404 */ }
  else if (err instanceof ValidationError) { /* 400 / 422 */ }
  else if (err instanceof RateLimitError) { console.log(err.retryAfter); }
  else if (err instanceof ServerError) { /* 5xx after retries */ }
  else if (err instanceof ConnectionError) { /* network / timeout */ }
  else if (err instanceof AgentMemoryError) { /* other */ }
}
```

| Exception | HTTP | When it occurs |
| --- | --- | --- |
| `AgentMemoryError` | n/a | Base class |
| `AuthError` | 401 | Invalid or missing API key |
| `ScopeError` | 403 | Scope or principal denial |
| `NotFoundError` | 404 | Resource not found |
| `ValidationError` | 400 / 422 | Malformed request |
| `RateLimitError` | 429 | Rate or token budget exceeded (`retryAfter` when provided) |
| `ServerError` | 5xx | Server error, retried for idempotent calls |
| `ConnectionError` | n/a | Network failure or timeout |

`GET` requests and idempotent writes (`remember`, `rememberMany`) retry automatically on connection errors and 5xx responses: up to `maxRetries` attempts (default 3) with 250 ms, 500 ms, 1000 ms backoff. Other writes and 4xx responses are not retried. Tune or disable on the constructor:

```typescript
const client = new AgentMemory({ ..., maxRetries: 0, timeout: 10000 });
```

The default timeout is 30,000 ms; streaming chat disables the read timeout while tokens arrive. On a 429, read `RateLimitError.retryAfter` and back off before retrying manually. All errors follow [RFC 7807 Problem Details](/docs/agent-memory/reference/errors.md).

## Vercel AI SDK adapter

```bash
npm install @surrealdb/spectron-vercel-ai
```

→ [Vercel AI SDK](/docs/agent-memory/integrations/ai-sdks/vercel-ai-sdk.md)

## Reference

[JavaScript SDK reference](/docs/agent-memory/reference/sdk-javascript.md) · [REST API](/docs/agent-memory/reference/rest-api.md)

---

Source: https://surrealdb.com/docs/agent-memory/integrations/sdks/kotlin

# Kotlin SDK

Using SurrealDB Agent Memory from Kotlin applications and agents on JVM, Android, and iOS.

The SurrealDB Agent Memory client for Kotlin ships inside the [SurrealDB Kotlin SDK](/docs/reference/kotlin.md); there is no separate package. It lives in the `com.surrealdb.kotlin.memory` package and talks to SurrealDB Agent Memory's HTTP API directly, independently of the SurrealDB RPC engine. Like the rest of the Kotlin SDK, it is [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) (JVM, Android, iOS) and every method is a `suspend` function.

> [!NOTE]
> [!NOTE]
> The Kotlin SDK is in early development (`0.1.0-SNAPSHOT`) and the SurrealDB Agent Memory client is not yet released. The APIs below are provisional.

## Installation

Add the Kotlin SDK to your project as described in the [installation guide](/docs/reference/kotlin/installation.md):

```kotlin
dependencies {
    implementation("com.surrealdb:kotlin:0.1.0-SNAPSHOT")
}
```

## Configuration

Construct a [`AgentMemory`](/docs/agent-memory/reference/sdk-kotlin.md) client with a context id, an API key, and your endpoint. Authentication uses the **`Authorization: Bearer`** header on every request.

```kotlin
import com.surrealdb.kotlin.memory.AgentMemory

val memory = AgentMemory(
    contextId = "acme-prod",
    apiKey = "sk-spec-...",
    endpoint = "https://api.memory.example",
)
```

| Parameter | Default | Description |
| --- | --- | --- |
| `contextId` | required | The context to operate in, e.g. `"acme-prod"`. |
| `apiKey` | required | Bearer token. Mutable; takes effect on the next request. |
| `endpoint` | required | Base URL, e.g. `"https://api.memory.example"`. |
| `timeout` | `30s` | Per-request timeout. |
| `maxRetries` | `3` | GET-only retries on 5xx and connection errors. |
| `httpClient` | platform default | Optional Ktor `HttpClient` to inject. |
| `json` | lenient | Optional `kotlinx.serialization` `Json` instance. |

Wrap calls in a coroutine (for example `runBlocking { ... }` from a synchronous caller) and call `memory.close()` when finished.

## Remember (facts)

Store a free-form fact (extracted server-side) or caller-supplied triples.

```kotlin
import com.surrealdb.kotlin.memory.model.InferMode

// Free-form fact, extracted server-side.
memory.remember("Christian was promoted to CTO", infer = InferMode.FULL)
```

```kotlin
import com.surrealdb.kotlin.memory.model.Triple
import com.surrealdb.kotlin.memory.model.TripleEntity

// Caller-supplied triples, no LLM.
memory.remember(
    triples = listOf(
        Triple(entity = TripleEntity("christian", "Person"), key = "role", value = "CTO"),
    ),
    infer = InferMode.TRIPLES,
)
```

Ingest a whole conversation in one call with `rememberMany`:

```kotlin
import com.surrealdb.kotlin.memory.model.BatchMessage
import com.surrealdb.kotlin.memory.model.TurnRole

memory.rememberMany(
    messages = listOf(
        BatchMessage("I was promoted to CTO.", role = TurnRole.USER),
        BatchMessage("Congratulations!", role = TurnRole.ASSISTANT),
    ),
    scope = listOf("org/acme/user/alice"),
)
```

## Recall

```kotlin
val result = memory.recall("What role does Christian have?", k = 10, mode = "hybrid")
result.hits.forEach { println("${it.score} ${it.text}") }

// Assemble a ready-to-use context block.
memory.queryContext("brief on tobie", k = 10)
```

## Documents

Upload a document (pass a `ByteArray`), then query across passages.

```kotlin
val doc = memory.documents.upload(
    file = bytes,
    filename = "returns.pdf",
    contentType = "application/pdf",
    title = "Returns Policy",
    scope = listOf("org/acme/team/eng"),
    labels = listOf("team=eng"),
)

memory.documents.get(doc.id)
memory.documents.list(status = "ready", mimeType = "application/pdf")
```

```kotlin
import com.surrealdb.kotlin.memory.model.QueryMode

val hits = memory.documents.query(
    "what is the return window for unopened items?",
    mode = QueryMode.HYBRID_GRAPH,
    k = 10,
)
```

## Chat

Run a server-driven turn (retrieve, generate, and persist) in one call.

```kotlin
val reply = memory.chat("What do you know about me?", sessionId = session.id)
println(reply.reply)
```

## Sessions

Create a session handle and either let SurrealDB Agent Memory drive the turn or drive it yourself.

```kotlin
import com.surrealdb.kotlin.memory.model.TurnRole

val session = memory.sessions.create(scope = listOf("user/tobie"))

// Server-driven turn scoped to the session.
val reply = session.chat("What do you know about me?")

// Or drive the turns yourself.
session.remember("I just got promoted to CTO", role = TurnRole.USER)
val ctx = session.context("What is Tobie's role?")
```

## Scopes and acting on behalf of others

Scopes are hierarchical slash-path strings. Build them with the `scopePaths` helper:

```kotlin
import com.surrealdb.kotlin.memory.scopePaths

scopePaths("team" to "eng", "org" to "acme") // ["team/eng", "org/acme"]
```

Every method accepts an optional `onBehalfOf`, which sends the `X-Spectron-On-Behalf-Of` header so a privileged caller can act as another principal:

```kotlin
memory.recall("open incidents", onBehalfOf = "alpha-bot")
memory.documents.list(status = "ready", onBehalfOf = "alpha-bot")
```

## Error handling

All failures throw a subclass of `AgentMemoryException`. See the [Kotlin SDK reference](/docs/agent-memory/reference/sdk-kotlin.md#errors) for the full exception to status mapping, and [error responses](/docs/agent-memory/reference/errors.md) for the shared RFC 7807 format.

```kotlin
import com.surrealdb.kotlin.memory.AgentMemoryNotFoundException
import com.surrealdb.kotlin.memory.AgentMemoryRateLimitException

try {
    memory.documents.get("doc:missing")
} catch (e: AgentMemoryNotFoundException) {
    println("${e.status}: ${e.title}")
} catch (e: AgentMemoryRateLimitException) {
    println("retry after ${e.retryAfter}")
}
```

## Learn more

- [Kotlin SDK reference](/docs/agent-memory/reference/sdk-kotlin.md) for package layout and the full surface
- [SurrealDB Kotlin SDK](/docs/reference/kotlin.md) for the database client in the same package
- [REST API](/docs/agent-memory/reference/rest-api.md) for the underlying HTTP surface

---

Source: https://surrealdb.com/docs/agent-memory/integrations/sdks/python

# Python SDK

Using SurrealDB Agent Memory from Python applications and agents.

The SurrealDB Agent Memory client ships as its own distribution, **`surrealdb-memory`**, pulled in through the `surrealdb[memory]` extra. It is versioned independently of the database driver and imported as `surrealdb.memory`.

> [!NOTE]
> **Package naming:** On [PyPI](https://pypi.org), the client is **`surrealdb-memory`**, installed through the **`surrealdb[memory]`** extra rather than by name. The SDK lands in the **3.x** line, which is still prerelease, so the install needs **`--pre`** - a bare `pip install surrealdb` resolves to the 2.x stable release, which has no `memory` extra.

## Installation

```bash
pip install --pre 'surrealdb[memory]'

# Using uv
uv add --prerelease=allow 'surrealdb[memory]'
```

Python 3.10+ recommended.

## Clients

`Memory` is synchronous (uses `requests`). `AsyncMemory` is async (uses `aiohttp`). Both expose the same method names; add `await` on the async client.

```python
from surrealdb.memory import Memory, AsyncMemory

with Memory(
    context="acme-prod",
    endpoint="https://api.memory.example",
    api_key="sk-spec-...",
) as memory:
    memory.remember("Alice was promoted to CTO.")
    hits = memory.recall("What is Alice's role?", k=10)
    for hit in hits.hits:
        print(hit.score, hit.text)

async with AsyncMemory(
    context="acme-prod",
    endpoint="https://api.memory.example",
    api_key="sk-spec-...",
) as memory:
    await memory.remember("Alice was promoted to CTO.")
    hits = await memory.recall("What is Alice's role?", k=10)
```

Both clients are pinned to one context and call `/api/v1/{context}/…`. Pass `context`, `endpoint`, and `api_key` explicitly; the SDK does **not** read environment variables.

## Scope

On the wire, scope is a **ScopeSet**: an ordered array of slash-path strings (for example `["org/acme/user/alice"]`). Register paths with `spectron scopes create` before first use; see [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md).

The Python client accepts:

- A single path: `scope="org/acme/user/alice"`
- A list of paths: `scope=["org/acme/user/alice"]`

| Argument | Default | Description |
| --- | --- | --- |
| `context` | required | Context id, e.g. `"acme-prod"`. |
| `endpoint` | required | SurrealDB Agent Memory host URL, e.g. `"https://api.memory.example"`. |
| `api_key` | required | Bearer token (`Authorization: Bearer …`). |
| `timeout` | `30.0` | Per-request timeout in seconds. |
| `max_retries` | `3` | Retries for GETs and idempotent writes. |

## Remember (facts)

```python
memory.remember("Alice was promoted to CTO.", infer="full", scopes=["org/acme/user/alice"])
memory.remember("Q3 board notes", labels=["topic=board"], memory_category="context")

memory.remember_many(
    [
        {"role": "user", "content": "I was promoted to CTO."},
        {"role": "assistant", "content": "Congratulations!"},
    ],
    extract="whole_conversation",
    scopes=["org/acme/user/alice"],
)
```

`remember` and `remember_many` send an `Idempotency-Key` header (derived from method, path, body, and a 30-second bucket) so safe retries collapse server-side.

## Recall and context

```python
result = memory.recall(
    "What is Alice's role?",
    k=10,
    mode="hybrid",
    lens=["org/acme/user/alice"],
)

block = memory.query_context(
    "What is Alice's role?",
    k=10,
    lens=["org/acme/user/alice"],
)
```

Optional filters include `labels`, `lens`, `scope_view` (`strict` | `merged` | `crossTeam`), temporal bounds (`as_of`, `valid_from`, …), and geo `location`.

## Documents

```python
upload = memory.documents.upload(
    "policy.pdf",
    content_type="application/pdf",
    title="Returns policy",
    scope=["org/acme/team/eng"],
    labels=["team=eng"],
)
doc = memory.documents.get(upload.id)
hits = memory.documents.query("refund window", k=5, mode="hybrid")
chunks = memory.documents.chunks(upload.id)
memory.documents.keywords.search("returns policy", k=10)
```

## Chat (including streaming)

```python
reply = memory.chat("Summarise what you know about Alice", scopes=["user/alice"])
print(reply.reply)

for chunk in memory.chat("Summarise what you know about Alice", stream=True):
    if chunk.delta:
        print(chunk.delta, end="", flush=True)
    if chunk.done:
        print("\n[trace]", chunk.trace_id)
```

## Other verbs and namespaces

Top-level methods: `consolidate`, `reflect`, `elaborate`, `forget`, `state`, `whoami`, `profile`, `inspect`, `audit`, `health`.

Grouped resources: `memory.documents`, `memory.sessions`, `memory.entities`, `memory.scopes`, `memory.principals`, `memory.keys`, `memory.traces`, `memory.lifecycle`.

→ Full method tables: [Python SDK reference](/docs/agent-memory/reference/sdk-python.md)

## Errors and retries

The SDK raises typed exceptions so you can handle auth, scope, and not-found cases precisely.

```python
from surrealdb.memory import MemoryAPIError, MemoryAuthError, MemoryNotFoundError, MemoryScopeError

try:
    hits = memory.recall("what is my name?", lens=["user/alice"])
except MemoryAuthError as exc:
    print(exc.status_code, exc.message)
except MemoryScopeError as exc:
    print(exc.status_code, exc.message)
except MemoryNotFoundError as exc:
    print(exc.status_code, exc.message)
except MemoryAPIError as exc:
    print(exc.status_code, exc.message, exc.trace_id, exc.body)
```

| Exception | HTTP | When it occurs |
| --- | --- | --- |
| `MemoryServiceError` | n/a | Base class |
| `MemoryAPIError` | Other non-2xx | Generic API failure; carries `status_code`, `message`, `trace_id`, `body` |
| `MemoryAuthError` | 401 | Missing or invalid API key |
| `MemoryScopeError` | 403 | Scope floor or principal rejects the call |
| `MemoryNotFoundError` | 404 | Context, session, document, or other resource not found |

Responses of 400, 422, 429, and 5xx that are not mapped to a subclass surface as `MemoryAPIError`. Inspect `status_code` and `body` (RFC 7807 problem details) for validation and rate-limit information. The async client raises the same exceptions.

`GET` requests and idempotent writes (`remember`, `remember_many`) retry automatically on connection errors and 5xx responses: up to `max_retries` attempts (default 3) with 250 ms, 500 ms, 1000 ms backoff. Other writes and 4xx responses are not retried. Tune or disable on the constructor:

```python
memory = Memory(..., max_retries=0, timeout=10.0)
```

The default timeout is 30 seconds; streaming chat disables the read timeout while tokens arrive. On a 429, read the problem-detail `body` and back off before retrying manually. All errors follow [RFC 7807 Problem Details](/docs/agent-memory/reference/errors.md).

## Response types

Typed dataclasses include `RememberResponse`, `RecallResponse`, `RecallHit`, `ChatResponse`, `Document`, `Chunk`, `StateResponse`, and others. Import from `surrealdb.memory` when you need explicit types:

```python
from surrealdb.memory import RecallResponse, RecallHit
```

## Harness adapters (zero prompt change)

For agent frameworks that should auto-record every turn, SurrealDB Agent Memory ships Python adapters that build on this SDK:

```bash
pip install spectron-crew-ai              # CrewAI
pip install spectron-openai-agents-sdk    # OpenAI Agents SDK
pip install spectron-strands-agents       # Strands Agents
pip install spectron-google-adk           # Google ADK
```

→ [Agent frameworks](/docs/agent-memory/integrations/frameworks/crewai.md)

## CLI alternative

The **`spectron`** binary exposes the same operations without the SDK:

```bash
spectron remember "Alice was promoted to CTO."
spectron recall "What is Alice's role?" --json
```

→ [CLI reference](/docs/agent-memory/reference/cli.md) · [REST API](/docs/agent-memory/reference/rest-api.md)

---

Source: https://surrealdb.com/docs/agent-memory/integrations/sdks/swift

# Swift SDK

Using SurrealDB Agent Memory from Swift applications and agents.

The `AgentMemory` client is shipped alongside the SurrealDB Swift SDK in the [surrealdb.swift](https://github.com/surrealdb/surrealdb.swift) package. It is a separate product that talks to the SurrealDB Agent Memory API, built on Swift async/await and `URLSession` with a swappable `HTTPClient` for testing. The client is `Sendable`.

## Installation

Add the `AgentMemory` product to your target's dependencies:

```swift
.product(name: "AgentMemory", package: "surrealdb.swift")
```

Then import it:

```swift
import AgentMemory
```

See the [SurrealDB Swift SDK installation guide](/docs/reference/swift/installation.md) for how to add the package itself.

## Configuration

```swift
let memory = try AgentMemory(
    context: "acme-prod",
    endpoint: "https://api.memory.example",
    apiKey: "sk-spec-..."
)
```

Authentication uses the `Authorization: Bearer` header on every request (handled by the client). Functionality is organised into three namespaces: **`documents`**, **`memory`** (sessions, entities, facts) and governance (**`scopes`**, **`principals`**, **`keys`**). Common operations are also exposed directly on the client.

## Remember (facts)

Ingest structured memories as explicit triples:

```swift
_ = try await memory.remember(
    triples: [
        Triple(
            entity: TripleEntity(type: "Person", name: "tobie"),
            key: "role",
            value: "CTO",
            memoryCategory: .identity
        )
    ],
    infer: .triples
)
```

Bulk conversation ingest:

```swift
_ = try await memory.rememberMany(
    [
        BatchMessage(role: .user, content: "I work at SurrealDB"),
        BatchMessage(role: .assistant, content: "Noted.")
    ],
    extract: .wholeConversation
)
```

## Recall

```swift
let hits = try await memory.query("What role does Christian have?", k: 10)
let block = try await memory.context("brief on tobie", k: 10)
```

## Documents

Upload multipart documents with optional metadata, then query them:

```swift
let doc = try await memory.documents.upload(
    file: .fileURL(URL(fileURLWithPath: "returns.pdf"), filename: nil, mimeType: "application/pdf"),
    title: "Returns Policy",
    source: "https://example.com/returns"
)

let results = try await memory.documents.query(
    "what is the return window for unopened items?",
    mode: .hybridGraph,
    k: 10,
    threshold: 0.5
)

_ = try await memory.documents.list(status: .ready, mimeType: "application/pdf")
try await memory.documents.delete(doc.id)
```

## Sessions

Sessions bundle conversation turns and provide context for retrieval:

```swift
let session = try await memory.sessions.create(scope: ["user/tobie"])

_ = try await session.ingest(text: "I just got promoted to CTO", role: .user)

let ctx = try await session.context("What is Tobie's role?")
let reply = try await myLLM.chat(system: ctx.context, user: userMessage)
_ = try await session.ingest(text: reply, role: .assistant)

try await session.close()
```

## Chat

Run the managed chat loop with context retrieval and persistence:

```swift
let reply = try await memory.chat("What do you know about me?", sessionId: session.id)
```

Stream responses incrementally over Server-Sent Events:

```swift
for try await chunk in try await memory.chatStream("Summarise what you know about me") {
    if chunk.done {
        print("\n[trace: \(chunk.traceId ?? "")]")
    } else {
        print(chunk.delta, terminator: "")
    }
}
```

## Entities

```swift
_ = try await memory.entities.list(type: "Person")
_ = try await memory.entities.get(type: "Person", name: "christian_battaglia")
_ = try await memory.entities.history(type: "Person", name: "christian_battaglia", key: "role")
try await memory.entities.delete(type: "Person", name: "christian_battaglia")
```

## Governance

Manage scopes, principals and self-service API keys:

```swift
// Scopes
_ = try await memory.scopes.register(path: "org/anneal", displayName: "Anneal")

// Principals
_ = try await memory.principals.grant(principalId: "agent:reader", path: "org/anneal", verbs: ["read"])

// API keys (the secret is returned only at creation)
let minted = try await memory.keys.create(name: "ci", grants: ["org/anneal": ["read"]], ttlSeconds: 3600)
```

## Delegation

Act as another principal by passing `onBehalfOf:`, which is sent as the `X-Spectron-On-Behalf-Of` header. Writes also carry an `Idempotency-Key` header for safe retry deduplication.

```swift
let docs = try await memory.documents.list(onBehalfOf: "agent:reader")
let me = try await memory.whoami(onBehalfOf: "agent:reader")
```

## Error handling

All failures throw `AgentMemoryError`, whose `kind` maps the HTTP status to `.base`, `.auth`, `.scope`, `.notFound`, `.validation`, `.rateLimit` or `.server`:

```swift
do {
    _ = try await memory.documents.get("doc:missing")
} catch let error as AgentMemoryError where error.isNotFound {
    print(error.status, error.title)
} catch let error as AgentMemoryError where error.isRateLimit {
    print("retry after", error.retryAfter ?? 0, "seconds")
}
```

See the [Swift SDK reference](/docs/agent-memory/reference/sdk-swift.md) and [REST API](/docs/agent-memory/reference/rest-api.md) for the full contract.

---

Source: https://surrealdb.com/docs/agent-memory/integrations/surfaces/embedded-library

# Embedded library

In-process integration surfaces.

SurrealDB Agent Memory runs as a **horizontally scalable HTTP service** in front of SurrealDB. Application code integrates through:

| Surface | Description |
| --- | --- |
| **REST** | `/api/v1/{context_id}/...` |
| **MCP** | `/mcp` on the same port |
| **SDKs** | `surrealdb`, `@surrealdb/memory` |
| **Harness adapters** | LangChain, Vercel AI SDK, OpenAI Agents, n8n, Claude Code hook |

There is no supported in-process library that runs extraction and recall inside your binary without the SurrealDB Agent Memory server.

## Rust agents

Deploy **`spectrond`** and call the HTTP API or generated client.

## Related

- [Embedded quickstart](/docs/agent-memory/quickstarts/embedded.md)
- [Hosted quickstart](/docs/agent-memory/quickstarts/hosted.md)

---

Source: https://surrealdb.com/docs/agent-memory/integrations/surfaces/filesystem-view

# Filesystem view

Accessing SurrealDB Agent Memory's knowledge layer as a virtual filesystem.

The filesystem view is an experimental MCP feature that mounts SurrealDB Agent Memory's knowledge store as a virtual filesystem. Agents and tools that can read files - including coding assistants, file-browsing tools, and MCP-aware agents - can navigate the knowledge layer without needing to know document identifiers or query the API directly.

> **Experimental.** The filesystem view is available in current SurrealDB Agent Memory releases but its structure and behaviour may change before stabilisation. Do not rely on the path conventions described here in production systems.

## How it works

When an MCP client connects to SurrealDB Agent Memory's MCP server (`/mcp`), the server exposes a set of MCP resources alongside the standard tools. The filesystem view is one of those resources: it presents the knowledge store as a navigable directory tree.

- **Documents appear as files.** Each ingested document is accessible as a file at a path derived from its title or original filename. The file content is the extracted text of the document, ready for the agent to read.
- **Knowledge nodes appear as directories.** Entities extracted from the memory layer - people, projects, organisations, concepts - appear as directories. Each directory contains attribute files (key-value facts about that entity) and relation files (edges to other entities).
- **Scope is respected.** The filesystem view only surfaces content visible to the authenticated API key. A key granted `memory:read` on `org/acme/agent/planner` sees only the knowledge reachable within that region.

## Directory structure

```text
/
├── documents/
│   ├── architecture-spec.pdf.txt
│   ├── onboarding-guide.docx.txt
│   └── q3-roadmap.pdf.txt
├── entities/
│   ├── alice/
│   │   ├── name.txt
│   │   ├── role.txt
│   │   ├── location.txt
│   │   └── relations/
│   │       ├── works-at -> ../acme/
│   │       └── manages -> ../planner-agent/
│   └── acme/
│       ├── type.txt
│       ├── industry.txt
│       └── relations/
│           └── employs -> ../alice/
└── sessions/
    └── sess_01hx3.../
        ├── turns/
        │   ├── 001-user.txt
        │   └── 002-assistant.txt
        └── metadata.json
```

The exact path structure may evolve as the feature matures.

## Use cases

The filesystem view is particularly useful when:

- **The agent uses file-reading tools** - many agent frameworks expose `read_file` or `list_directory` as tool functions. The filesystem view makes that knowledge accessible through those tools without any dedicated integration.
- **Document IDs are not available** - when an agent needs to find a document by title or topic rather than by ID, browsing the `documents/` directory is more natural than constructing a recall query.
- **Debugging and inspection** - inspect the knowledge store structure through an MCP client without writing code.
- **Agent-to-agent knowledge transfer** - an agent that builds up knowledge in one session can expose that knowledge as a filesystem resource for another agent to browse.

## Accessing the filesystem view

The filesystem view is available through any MCP client connected to SurrealDB Agent Memory's MCP server. No additional configuration is required beyond the standard MCP connection.

In Claude Desktop, Cursor, or another MCP-aware tool, the filesystem view appears as a set of resources in the MCP resource list. The agent can use the `resources/list` and `resources/read` MCP methods to enumerate and read the virtual filesystem entries.

Example MCP resource URI pattern:

```text
spectron://fs/documents/architecture-spec.pdf.txt
spectron://fs/entities/alice/role.txt
```

### Manual access via MCP tools

If your MCP client exposes MCP resource access as tools:

```json
{
  "method": "resources/list",
  "params": { "uri": "spectron://fs/entities/" }
}
```

```json
{
  "method": "resources/read",
  "params": { "uri": "spectron://fs/entities/alice/role.txt" }
}
```

## Enabling the filesystem view

The filesystem view is enabled by default when the MCP server is active. Set `SPECTRON_FS_VIEW=false` to disable it if you do not want to expose knowledge store contents as MCP resources.

## Limitations

- The filesystem view is read-only. You cannot create, modify, or delete knowledge nodes by writing to the virtual filesystem; use the standard memory operations API for that.
- Very large knowledge stores may have slow initial enumeration. The view is not paginated at the filesystem level.
- Symlinks in the `relations/` directories are representational - they indicate graph edges but are not traversable as real filesystem symlinks in all MCP clients.
- In-progress extraction (documents currently being processed) may not appear until extraction completes.

## Next steps

- [MCP server: install](/docs/agent-memory/integrations/mcp-server/install.md) - connecting an MCP client to SurrealDB Agent Memory
- [MCP tools reference](/docs/agent-memory/reference/mcp-tools.md) - the full set of MCP tools and resources
- [Uploading documents](/docs/agent-memory/ingest/authoritative/uploading-documents.md) - ingesting documents that appear in the filesystem view

---

Source: https://surrealdb.com/docs/agent-memory/integrations/surfaces/rest

# REST API

Accessing SurrealDB Agent Memory directly via its HTTP REST API.

SurrealDB Agent Memory exposes a REST API over HTTP from the **api** role. Every SDK and harness adapter maps to these endpoints, so you can integrate from any language that can make HTTP requests.

## URL structure

One host (default port **9090**) serves:

| Path | Purpose |
| --- | --- |
| `/api/v1/{context_id}/...` | End-user operations: facts, documents, query, chat, sessions, traces |
| `/api/v1/contexts/...` | Management: Context lifecycle and key provisioning (management key) |
| `/api/v1/health` | Liveness |
| `/mcp` | MCP server (Streamable HTTP); same Bearer auth |

The `{context_id}` segment is the identifier you assigned at bootstrap (for example `dev`), not an opaque UUID unless you chose one.

## Authentication

```http
Authorization: Bearer <api_key>
```

Do not send a raw secret without the `Bearer` prefix. Management and end-user keys both use the same header; the server infers capabilities from the key material.

Optional on writes:

```http
Idempotency-Key: <stable-id>
```

## The four verbs

| Verb | Endpoint | When to use |
| --- | --- | --- |
| Remember | `POST /api/v1/{ctx}/facts`, `POST .../facts/batch` | Conversations, single facts, harness batch capture |
| Upload | `POST /api/v1/{ctx}/documents` | PDFs, manuals, code, media |
| Recall | `POST /api/v1/{ctx}/query` | Ranked hits over the unified substrate |
| Chat | `POST /api/v1/{ctx}/chat` | Let SurrealDB Agent Memory run recall + synthesis |

Formatted prompt text without raw hit lists: `POST /api/v1/{ctx}/context`.

## Example: remember and recall

```bash
export SPECTRON_URL=http://localhost:9090
export SPECTRON_API_KEY=<context-key>
export SPECTRON_CONTEXT_ID=dev

curl -sS "$SPECTRON_URL/api/v1/$SPECTRON_CONTEXT_ID/facts" \
  -H "Authorization: Bearer $SPECTRON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Alice was promoted to CTO.","infer":"full","scope":["org/acme/user/alice"]}'

curl -sS "$SPECTRON_URL/api/v1/$SPECTRON_CONTEXT_ID/query" \
  -H "Authorization: Bearer $SPECTRON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"What is Alice'\''s role?","limit":10,"scope":["org/acme/user/alice"]}'
```

The [`spectron`](/docs/agent-memory/reference/cli.md) CLI wraps the same paths (`spectron remember`, `spectron recall`, `spectron chat`).

## Full reference

Endpoint tables, session introspection routes, document APIs, traces, and scope/principal management are documented in [REST API reference](/docs/agent-memory/reference/rest-api.md).

---

Source: https://surrealdb.com/docs/agent-memory/integrations/voice/elevenlabs

# ElevenLabs

Giving an ElevenLabs Conversational AI agent memory with SurrealDB Agent Memory.

[ElevenLabs Conversational AI](https://elevenlabs.io/docs/conversational-ai/overview) runs the voice agent on ElevenLabs' side and reaches your systems through **server tools** (webhooks it calls mid-conversation) and **post-call webhooks** (fired when a conversation ends). SurrealDB Agent Memory sits behind both: a server tool for recall during the call, and a post-call webhook to store the transcript. There is no dedicated adapter. You expose a small HTTP endpoint that forwards to SurrealDB Agent Memory.

> [!NOTE]
> This is an integration guide. It shows the two webhook shapes ElevenLabs calls and how each maps to SurrealDB Agent Memory; wire them to your own hosting.

## Recall as a server tool

Add a [server tool](https://elevenlabs.io/docs/conversational-ai/customization/tools) to the agent (for example `recall_memory(query)`) pointing at an endpoint you host. When the agent decides it needs context, ElevenLabs calls the tool and passes the return value back into the conversation. Handle it with the [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md):

```typescript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({
    endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
    context: process.env.AGENT_MEMORY_CONTEXT!,
    apiKey: process.env.AGENT_MEMORY_API_KEY!,
});

// POST /tools/recall (configured as an ElevenLabs server tool)
export async function POST(request: Request) {
    const { query, user_id } = await request.json();
    const block = await memory.context(query, {
        scope: [`org/acme/user/${user_id}`],
        k: 8,
    });
    return Response.json({ memory: block });
}
```

Pass the caller's `user_id` as a [dynamic variable](https://elevenlabs.io/docs/conversational-ai/customization/personalization/dynamic-variables) so the tool scopes recall to the right person.

## Store the conversation with a post-call webhook

Configure a [post-call webhook](https://elevenlabs.io/docs/conversational-ai/workflows/post-call-webhooks). ElevenLabs POSTs the full transcript when the conversation ends; store the turns so they are available next time:

```typescript
// POST /webhooks/elevenlabs (post-call webhook)
export async function POST(request: Request) {
    const payload = await request.json();
    const { transcript, conversation_id } = payload.data;
    const userId = payload.data.metadata?.user_id ?? "anonymous";

    const turns = transcript.map((t: { role: string; message: string }) => ({
        role: t.role === "agent" ? "assistant" : "user",
        content: t.message,
    }));

    await memory.rememberMany(turns, {
        scope: [`org/acme/user/${userId}`],
    });

    return new Response("ok");
}
```

> [!IMPORTANT]
> ElevenLabs signs post-call webhooks with an HMAC header. Verify the signature against your webhook secret before trusting the payload.

## Scope per caller

Both endpoints scope to the caller with a slash path such as `["org/acme/user/alice"]`, derived from the dynamic variable or conversation metadata. Register paths with `agent-memory scopes create` before first use.

## Next steps

- [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md): the full client surface
- [REST API](/docs/agent-memory/integrations/surfaces/rest.md): calling SurrealDB Agent Memory over HTTP without the SDK

---

Source: https://surrealdb.com/docs/agent-memory/integrations/voice/gradium

# Gradium

Building a Gradium voice agent with long-term memory using LiveKit Agents and SurrealDB Agent Memory.

[Gradium](https://docs.gradium.ai/) provides streaming speech-to-text and text-to-speech models for realtime voice agents. Gradium plugs into a [LiveKit Agents](https://docs.livekit.io/agents/) pipeline as the STT and TTS stages through the official `livekit-plugins-gradium` plugin, and SurrealDB Agent Memory hooks into the same pipeline's turn lifecycle: recall before the model speaks, store after. Use the [Python SDK](/docs/agent-memory/integrations/sdks/python.md) (`surrealdb`); there is no dedicated Gradium adapter.

> [!NOTE]
> This is an integration guide. The code shows where SurrealDB Agent Memory fits in a LiveKit agent's turn lifecycle with Gradium speech models; adapt the hook names to your installed `livekit-agents` version.

## Installation

```bash
pip install "livekit-agents[gradium,openai]"
pip install --pre surrealdb
```

Set the connection details. Create the Gradium API key in the [Gradium Studio console](https://studio.gradium.ai/platform/api-keys):

**Bash**

```bash
export GRADIUM_API_KEY="gd_..."
export AGENT_MEMORY_ENDPOINT="https://memory.example.com"
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_API_KEY="sk-mem-..."
```

**PowerShell**

```powershell
$env:GRADIUM_API_KEY = "gd_..."
$env:AGENT_MEMORY_ENDPOINT = "https://memory.example.com"
$env:AGENT_MEMORY_CONTEXT = "acme-prod"
$env:AGENT_MEMORY_API_KEY = "sk-mem-..."
```

## Recall before the model, store after

LiveKit calls `on_user_turn_completed` once Gradium has transcribed the caller's speech, before the LLM runs. Recall relevant memory there and add it to the turn context; store the exchange when the turn finishes. Gradium's STT performs semantic turn detection itself, so the session needs no separate VAD plugin:

```python
import os
from livekit import agents
from livekit.agents import Agent, AgentServer, AgentSession, ChatContext, ChatMessage
from livekit.plugins import gradium, openai
from surrealdb import AsyncAgentMemory

class MemoryAgent(Agent):
    def __init__(self, memory: AsyncAgentMemory, scope: list[str]):
        super().__init__(instructions="You are a helpful voice assistant.")
        self._memory = memory
        self._scope = scope

    async def on_user_turn_completed(self, turn_ctx: ChatContext, new_message: ChatMessage):
        # Recall relevant memory and inject it as context for this turn.
        block = await self._memory.query_context(
            new_message.text_content, k=8, lens=self._scope,
        )
        if block:
            turn_ctx.add_message(role="system", content=f"## Memory\n{block}")

        # Store the caller's turn for future recall (non-blocking).
        await self._memory.remember(new_message.text_content, scopes=self._scope)


server = AgentServer()

@server.rtc_session()
async def entrypoint(ctx: agents.JobContext):
    memory = AsyncAgentMemory(
        endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
        context=os.environ["AGENT_MEMORY_CONTEXT"],
        api_key=os.environ["AGENT_MEMORY_API_KEY"],
    )

    session = AgentSession(
        stt=gradium.STT(model_name="default", language="en"),
        llm=openai.LLM(model="gpt-4o"),
        tts=gradium.TTS(model_name="default", voice_id="4SZHfMpw-p46Ywgs"),
    )

    await session.start(
        agent=MemoryAgent(memory, scope=["org/acme/user/alice"]),
        room=ctx.room,
    )

if __name__ == "__main__":
    agents.cli.run_app(server)
```

Pass `voice_id` explicitly; the plugin's default voice has changed between releases. Gradium's [flagship voices](https://docs.gradium.ai/guides/voices/flagship-voices) cover English, French, Spanish, Portuguese, and German, and `gradium.STT` accepts the same five languages through its `language` option.

## Scope per caller

Bind a `scope` to the caller's identity so each person's memory stays isolated. It is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Derive it from the LiveKit participant identity when the room connects. Register paths with `agent-memory scopes create` before first use.

## Latency

Voice turns are latency-sensitive. Gradium's streaming models and semantic turn detection keep the speech stages fast, which leaves the recall call as the main added latency in the loop. Keep recall to a single `query_context` call with a modest `k`, and let the write to `remember` run without blocking the response. For heavier synthesis, run `reflect` or `consolidate` between calls rather than inside a turn.

## Next steps

- [Python SDK](/docs/agent-memory/integrations/sdks/python.md): the full client surface
- [Recalling memories](/docs/agent-memory/retrieve/recall.md): recall modes and filters
- [LiveKit](/docs/agent-memory/integrations/voice/livekit.md): the same lifecycle with other speech providers

---

Source: https://surrealdb.com/docs/agent-memory/integrations/voice/livekit

# LiveKit

Adding SurrealDB Agent Memory to a LiveKit voice agent.

[LiveKit Agents](https://docs.livekit.io/agents/) build realtime voice agents from a speech-to-text, LLM, and text-to-speech pipeline. SurrealDB Agent Memory gives that agent long-term memory: recall what the caller said in past sessions before the model speaks, and store each turn afterwards. Use the [Python SDK](/docs/agent-memory/integrations/sdks/python.md) (`surrealdb`); there is no dedicated LiveKit adapter. The pipeline's speech stages are pluggable; for a pipeline built on Gradium's speech models, see [Gradium](/docs/agent-memory/integrations/voice/gradium.md).

> [!NOTE]
> This is an integration guide. The code shows where SurrealDB Agent Memory fits in a LiveKit agent's turn lifecycle; adapt the hook names to your installed `livekit-agents` version.

## Installation

```bash
pip install "livekit-agents[openai,silero]"
pip install --pre surrealdb
```

Set the connection details:

**Bash**

```bash
export AGENT_MEMORY_ENDPOINT="https://memory.example.com"
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_API_KEY="sk-mem-..."
```

**PowerShell**

```powershell
$env:AGENT_MEMORY_ENDPOINT = "https://memory.example.com"
$env:AGENT_MEMORY_CONTEXT = "acme-prod"
$env:AGENT_MEMORY_API_KEY = "sk-mem-..."
```

## Recall before the model, store after

LiveKit calls `on_user_turn_completed` once the caller's speech has been transcribed, before the LLM runs. Recall relevant memory there and add it to the turn context; store the exchange when the turn finishes:

```python
import os
from livekit import agents
from livekit.agents import Agent, AgentServer, AgentSession, ChatContext, ChatMessage
from livekit.plugins import openai, silero
from surrealdb import AsyncAgentMemory

class MemoryAgent(Agent):
    def __init__(self, memory: AsyncAgentMemory, scope: list[str]):
        super().__init__(instructions="You are a helpful voice assistant.")
        self._memory = memory
        self._scope = scope

    async def on_user_turn_completed(self, turn_ctx: ChatContext, new_message: ChatMessage):
        # Recall relevant memory and inject it as context for this turn.
        block = await self._memory.query_context(
            new_message.text_content, k=8, lens=self._scope,
        )
        if block:
            turn_ctx.add_message(role="system", content=f"## Memory\n{block}")

        # Store the caller's turn for future recall (non-blocking).
        await self._memory.remember(new_message.text_content, scopes=self._scope)


server = AgentServer()

@server.rtc_session()
async def entrypoint(ctx: agents.JobContext):
    memory = AsyncAgentMemory(
        endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
        context=os.environ["AGENT_MEMORY_CONTEXT"],
        api_key=os.environ["AGENT_MEMORY_API_KEY"],
    )

    session = AgentSession(
        stt=openai.STT(),
        llm=openai.LLM(model="gpt-4o"),
        tts=openai.TTS(),
        vad=silero.VAD.load(),
    )

    await session.start(
        agent=MemoryAgent(memory, scope=["org/acme/user/alice"]),
        room=ctx.room,
    )

if __name__ == "__main__":
    agents.cli.run_app(server)
```

## Scope per caller

Bind a `scope` to the caller's identity so each person's memory stays isolated. It is a slash path or an array of paths, for example `["org/acme/user/alice"]`. Derive it from the LiveKit participant identity when the room connects. Register paths with `agent-memory scopes create` before first use.

## Latency

Voice turns are latency-sensitive. Keep recall to a single `query_context` call with a modest `k`, and let the write to `remember` run without blocking the response. For heavier synthesis, run `reflect` or `consolidate` between calls rather than inside a turn.

## Next steps

- [Python SDK](/docs/agent-memory/integrations/sdks/python.md): the full client surface
- [Recalling memories](/docs/agent-memory/retrieve/recall.md): recall modes and filters

---

Source: https://surrealdb.com/docs/agent-memory/reference

# Agent Memory reference

Complete API and configuration reference for SurrealDB Agent Memory.

Complete technical reference for all SurrealDB Agent Memory APIs, configuration options, data structures, and command-line tools.

## In this section

- **[Agent guide (AGENTS.md)](/docs/agent-memory/reference/agents.md)** - Instructions for coding agents.
- **[REST API](/docs/agent-memory/reference/rest-api.md)** - End-user HTTP endpoints for memory and knowledge operations.
- **[Management API](/docs/agent-memory/reference/management-api.md)** - Control-plane endpoints for creating and configuring Contexts and API keys.
- **[Python SDK](/docs/agent-memory/reference/sdk-python.md)** - Complete method reference for the Python SDK.
- **[JavaScript SDK](/docs/agent-memory/reference/sdk-javascript.md)** - Complete method reference for the JavaScript and TypeScript SDK.
- **[MCP tools](/docs/agent-memory/reference/mcp-tools.md)** - Input/output schemas for all seven MCP tools.
- **[Configuration](/docs/agent-memory/reference/configuration.md)** - Server and per-Context configuration options.
- **[CLI](/docs/agent-memory/reference/cli.md)** - Command-line interface reference.
- **[Data model and schema](/docs/agent-memory/reference/data-model-and-schema.md)** - SurrealDB schema for Context-local tables (authoritative and experiential streams share one database).
- **[Errors](/docs/agent-memory/reference/errors.md)** - HTTP error codes and SDK exception types.
- **[Glossary](/docs/agent-memory/reference/glossary.md)** - Definitions of key terms (see also the [Architecture glossary](/docs/agent-memory/architecture/glossary.md) for pillar and memory vocabulary).

## SDK references

- [Swift SDK reference](/docs/agent-memory/reference/sdk-swift.md) - the Swift client's types and methods

---

Source: https://surrealdb.com/docs/agent-memory/reference/agents

# Agent guide (AGENTS.md)

Copy these into your Cursor rules or agent skills. Instructions for coding agents integrating with SurrealDB Agent Memory.

This page is written for **coding agents** (Cursor, Claude Code, Copilot, and similar) building on SurrealDB Agent Memory. Humans can read it too, but the tone is imperative: what to do, what not to do, and where the sharp edges are.

> [!NOTE]
**Use it as a Cursor skill:** copy this file into `.cursor/rules/spectron.mdc`, add it as a project rule, or save it under `.cursor/skills/spectron/SKILL.md` with a short `description` in the frontmatter so the agent loads it when working on SurrealDB Agent Memory integrations.

Full product docs can be found at [SurrealDB Agent Memory documentation](/docs/agent-memory.md). This guide is the minimum viable canon for “vibe coding” without reading all of it.

---

## What Agent Memory is

SurrealDB Agent Memory is a **memory and knowledge layer for AI agents** backed by SurrealDB. You send turns and documents in; SurrealDB Agent Memory extracts structured facts (entities, attributes, relations), reconciles contradictions, and retrieves context for later queries.

Two streams share **one graph**:

- **Experiential** - chat turns, sessions, reflections.
- **Authoritative** - uploaded documents and curated knowledge.

When they disagree, SurrealDB Agent Memory records **uncertainty** - it does not silently pick a winner.

---

## Authentication

Every request uses a Bearer token:

```http
Authorization: Bearer sk-…
```

Do **not** use `API-KEY`, `X-API-Key`, or query-string secrets.

- **Data-plane keys** - bound to a **principal** with grant regions. Used for `/api/v1/{context_id}/…` routes.
- **Management keys** - control plane only (`/api/v1/contexts/…`). Never embed in client apps or MCP configs shipped to end users.

Context id is in the **URL path**, not a header (except MCP - see below).

---

## Scope and grants

Memory is partitioned by **scope** - hierarchical paths like `org/acme/user/alice`. Every read and write is clamped to the caller’s **effective grants**.

Grant verbs (always `noun:verb`):

| Verb | Use for |
| --- | --- |
| `memory:read` | Recall, query, chat, document GET/list |
| `memory:write` | Turns, fact writes, document upload |
| `memory:forget` | Forget, entity delete, scoped erasure |
| `scope:read` | List scope names (no data access) |
| `scope:create` | Register scope paths |
| `scope:delete` | Remove scope paths |
| `grant:manage` | Grant/revoke on principals |

**Rules agents must respect:**

1. Pass **`scope`** on reads/writes when the user or session is scoped - never assume Context-wide access.
2. **`labels`** and **`lens`** filter within the grant; they **never widen** access.
3. Delegation uses **`X-Spectron-On-Behalf-Of: <principal_id>`** (depth 1 only). Effective authority is the **intersection** of caller and target grants.
4. Flat verb names (`read`, `write`, …) are **rejected** - use namespaced forms (`memory:read`, `memory:write`, …).

Introspect the caller without admin access:

```http
GET /api/v1/{context_id}/me
```

---

## Core HTTP surfaces

Base path: `/api/v1/{context_id}/…`

| Goal | Method | Path |
| --- | --- | --- |
| Structured recall | `POST` | `/query` |
| LLM-ready context string | `POST` | `/context` |
| Managed chat loop | `POST` | `/chat` or `/sessions/{id}/chat` |
| Record a turn | `POST` | `/sessions/{id}/turns` |
| Upload document | `POST` | `/documents` (multipart) |
| Document-only search | `POST` | `/documents/query` |
| On-demand reflection | `POST` | `/reflect` |
| Semantic forget (preview or apply) | `POST` | `/forget` - use `dryRun: true` to preview |
| Trace detail | `GET` | `/traces/{trace_id}` |

### `/query` essentials

```json
{
  "query": "What is Alice's role?",
  "scope": ["org/acme/user/alice"],
  "limit": 10,
  "include": ["facts", "passages"],
  "as_of": "2025-02-01T00:00:00Z",
  "source": "my-app"
}
```

- **`as_of`** - known-time recall (what we believed then). Distinct from valid-time on entities.
- **`source`** - audit label on the retrieval trace only; does not change ranking.
- **`mode`** - `hybrid`, `vector`, `bm25`, or `graph` only; invalid values → `400`.
- Response includes **`tier`**, **`hits`**, inline **`trace`**, and **`queryMs`**.

### Sessions

Create a session, append turns, or let SurrealDB Agent Memory run the full loop:

```http
POST /api/v1/{context_id}/sessions                       # create
GET  /api/v1/{context_id}/sessions/{session_id}/turns    # read the transcript
POST /api/v1/{context_id}/sessions/{session_id}/context  # retrieve for this session
POST /api/v1/{context_id}/facts                          # append a turn (session_id in body)
POST /api/v1/{context_id}/chat                           # full loop (sessionId in body)
```

Turns are appended through **`/facts`** with a `session_id`, not through a
per-session turns route - that route is read-only. Likewise `/chat` is
Context-level and takes `sessionId` in the body.

Use **`remember()` + `sessions.context()`** when you need your own LLM, tools, or streaming. Use **`chat()`** when SurrealDB Agent Memory should retrieve, call the response model, and persist the reply.

---

## MCP (Cursor, Claude Desktop, …)

Remote MCP endpoint: your instance base URL plus `/mcp` - for example `https://abc123.spectron.cloud/mcp` (SurrealDB Cloud: host from SurrealDB Studio **API keys**) or `http://localhost:9090/mcp` (self-hosted).

Headers:

```json
{
  "Authorization": "Bearer <api-key>",
  "X-Spectron-Context": "<context_id>"
}
```

`X-Spectron-Context` is a client-side convenience - **`context_id` is optional** on each tool because the bearer key already pins one Context. Scope is per tool via a **`scope`** argument (slash paths, for example `["org/acme/user/alice"]`).

Seven tools: `remember`, `recall`, `context`, `reflect`, `forget`, `upload`, `inspect` - see [MCP tools](/docs/agent-memory/reference/mcp-tools.md).

**Limits:** `k` / `limit` on `recall` and `context` defaults to **10** and is capped at **50** (`SPECTRON_MAX_QUERY_K`, clamp-down only). The retrieval candidate pool is internal (`SPECTRON_RETRIEVAL_POOL_SIZE`, default 256) and is not widened by raising `k`. Oversized `k` returns **`400`** before retrieval runs.

**Errors:** operation failures use `isError: true` with `structuredContent.error.status` (same codes as REST). JSON-RPC `error` is for protocol faults only. See [MCP error handling](/docs/agent-memory/reference/mcp-tools.md#error-handling).

Install helper ([`install-mcp`](https://github.com/supermemoryai/install-mcp) - pass the `/mcp` URL as the first argument, auth via `--header`):

```bash
npx install-mcp https://<your-context-host>/mcp \
  --client cursor \
  --header "Authorization: Bearer <api-key>" --oauth no
```

See [Cursor](/docs/agent-memory/integrations/mcp-server/coding-assistants/cursor.md).

---

## SDKs

Prefer an official SDK over raw HTTP when available:

```python
from surrealdb.memory import Memory

memory = Memory(context="acme-prod",
    api_key=os.environ["SPECTRON_API_KEY"])
await memory.sessions.create(scopes=["org/acme/user/alice"])
```

```javascript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "acme-prod",
    apiKey: process.env.SPECTRON_API_KEY });
```

Model assignment is **per Context** for LLM stages; **embedding is deployment-fixed** - do not try to set `models.embedding` in config patches.

---

## Keys: minting patterns

| Who | How |
| --- | --- |
| Operator | `POST /api/v1/contexts/{ctx}/principals/{principal_id}/keys/{name}` (management key) |
| Cloud proxy | `POST /api/v1/contexts/{ctx}/access-tokens` with `external_id` + required `ttl_seconds` |
| Member | `POST /api/v1/{ctx}/keys` after holding a brokered key (self-service; grants may only **attenuate**) |

Unbound or “scoped mint in body” key routes are **removed**. Always bind keys to a principal.

Rotate in place:

```http
POST /api/v1/{context_id}/keys/{name}/rotate?ttl_seconds=2592000
```

---

## Common agent mistakes

1. **No scope on writes** - facts land in the wrong region or get rejected.
2. **Treating labels as scope** - `labels=["team=platform"]` filters; it does not grant access.
3. **Using management keys in the app** - use principal-bound data-plane keys.
4. **Expecting embedding config per Context** - set `SPECTRON_MODEL_EMBEDDING` on the server; reindex after model changes.
5. **Ignoring `memory_updates` / trace** - when debugging wrong answers, fetch `GET …/traces/{traceId}` and check `resolutionTier`.
6. **Assuming last-write-wins** - contradictions become **`uncertainty`** records; design UIs accordingly.
7. **Idempotent retries without idempotency keys** - duplicate writes may return **`409`**; use idempotency headers where documented.
8. **`use_reranker: true` without server reranker** - requires `SPECTRON_RERANKER_URL` + `SPECTRON_RERANKER_MODEL` or it falls back to bi-encoder order.
9. **MCP JSON-RPC errors for business failures** - not-found and auth failures return **`isError: true`** with `error.status`, not JSON-RPC `-32603`.
10. **`POST /forget` without checking dry run** - pass **`dryRun: true`** (or `spectron forget --dry-run`) to preview; omitting it expires records immediately.
11. **Per-Context OCR/STT config** - multimodal HTTP providers are **deployment env vars** (`SPECTRON_OCR_*`, `SPECTRON_CLIP_*`, `SPECTRON_STT_*`), not Context patch fields.

---

## Idempotency and errors

Errors are [RFC 7807 problem details](/docs/agent-memory/reference/errors.md). Typical codes:

- **`401`** - missing/invalid/expired key
- **`403`** - grant does not cover requested scope
- **`400`** - invalid `mode`, grant widening, or limit exceeded
- **`409`** - duplicate key name or idempotency conflict

---

## What to read next

| Topic | Doc |
| --- | --- |
| REST surface | [REST API](/docs/agent-memory/reference/rest-api.md) |
| Control plane | [Management API](/docs/agent-memory/reference/management-api.md) |
| Scope model | [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md) |
| Retrieval tiers | [Coherence, retrieval, and cost tiers](/docs/agent-memory/architecture/coherence-retrieval-and-tiers.md) |
| Keys and Cloud brokerage | [Key policy](/docs/agent-memory/reference/configuration.md#key-policy) |
| MCP schemas | [MCP tools](/docs/agent-memory/reference/mcp-tools.md) |

SurrealDB Agent Memory is designed to be auditable, so **verify against traces** before changing application logic when behaviour seems wrong.

---

Source: https://surrealdb.com/docs/agent-memory/reference/cli

# CLI

Command-line interface reference.

SurrealDB Agent Memory ships two binaries:

| Binary | Role |
| --- | --- |
| **`spectrond`** | Server (runs in your container or cluster): `api`, `worker`, `scheduler`, `management`, `bootstrap` |
| **`spectron`** | Client: `remember`, `recall`, `chat`, `documents`, provisioning helpers |

The **`spectron`** CLI is what integrators install locally. **`spectrond`** is operated via Docker, Kubernetes, or your platform team.

> [!NOTE]
> Both binaries carry **Spectron**, the project name SurrealDB Agent Memory was developed under. The product name changed ahead of the executables, which will be renamed in a future release.

## Installing the CLI

Prebuilt `spectron` binaries for macOS, Linux, and Windows are published to `download.surrealdb.com` under a version path. On macOS and Linux the install script resolves the latest version, verifies the checksum, and installs to `/usr/local/bin` (or `~/.local/bin` when that is not writable):

```bash
curl -fsSL https://download.surrealdb.com/spectron/install.sh | sh
```

Set **`SPECTRON_INSTALL_DIR`** to install somewhere else. Once installed, the CLI updates itself in place, so you do not need to re-run the script:

```bash
spectron upgrade
```

### Manual download

To install by hand, resolve the current version from the pointer file, then download the archive for your platform:

```bash
VERSION=$(curl -fsSL https://download.surrealdb.com/spectron/latest.txt)
BASE="https://download.surrealdb.com/spectron/${VERSION}/spectron-${VERSION}"

# macOS (Apple Silicon)
curl -fsSL "${BASE}.darwin-arm64.tgz" | tar -xz && sudo mv spectron /usr/local/bin/

# macOS (Intel)
curl -fsSL "${BASE}.darwin-amd64.tgz" | tar -xz && sudo mv spectron /usr/local/bin/

# Linux (x86_64)
curl -fsSL "${BASE}.linux-amd64.tgz" | tar -xz && sudo mv spectron /usr/local/bin/

# Linux (arm64)
curl -fsSL "${BASE}.linux-arm64.tgz" | tar -xz && sudo mv spectron /usr/local/bin/
```

On **Windows**, download `spectron-<version>.windows-amd64.zip` from `https://download.surrealdb.com/spectron/<version>/` (use `latest.txt` for `<version>`), extract it, and put the folder containing `spectron.exe` on `PATH`. `spectron upgrade` works on Windows once a first install is on `PATH`.

### Verifying the download

Each archive has a `.sha256` sidecar next to it:

```bash
shasum -a 256 -c spectron-*.tgz.sha256   # macOS / Linux
```

The binaries are unsigned. Downloading with `curl` avoids the macOS Gatekeeper quarantine flag that a browser download adds; if you did download through a browser, clear it first with `xattr -d com.apple.quarantine ./spectron`.

## Connection flags (client)

Most `spectron` subcommands accept:

| Flag | Environment variable | Description |
| --- | --- | --- |
| `--url` / `-u` | `SPECTRON_URL` | Server base URL (for example `http://localhost:9090`) |
| `--api-key` / `-a` | `SPECTRON_API_KEY` | Context API key |
| `--context-id` / `-c` | `SPECTRON_CONTEXT_ID` | Context id in `/api/v1/{context_id}/...` |

```bash
spectron login --url http://localhost:9090 \
  --api-key "$SPECTRON_API_KEY" \
  --context-id dev
```

Stores a named profile for later commands.

### Local config and secrets

`spectron login` and `spectron config set` write profiles to `~/.config/spectron/config.toml` with owner-only permissions (`0600` on Unix). **`config set`** prints the key name, never the value. To display a stored secret:

```bash
spectron config get api_key --reveal
```

Without **`--reveal`**, `api_key` is shown as `<hidden>`.

---

## `spectrond` - server (operators)

Run inside the SurrealDB Agent Memory container or host image.

### `bootstrap`

One-time control-plane initialisation. Prints management and context API keys. Fails if the Context already exists.

```bash
docker compose exec spectron spectrond bootstrap \
  --connection-string "ws://surrealdb:8000;root;root"
```

Pin a stable Context API key (instead of a random mint) with **`--context-api-key`** or **`SPECTRON_API_KEY`**, mirroring how **`--management-api-key`** / **`SPECTRON_MANAGEMENT_API_KEY`** pins the management key. Validate the value with `spectrond generate-key` first.

### Development runtime

Local bring-up runs **api + worker + scheduler** in one process (not for production). Pass **`--bootstrap`** to seed the Context on an empty database, print the keys, then serve - on later restarts against the same database the seed is a no-op (logs and skips) instead of erroring:

```bash
spectrond dev start --bootstrap \
  --connection-string "$SURREALDB_CONNECTION" \
  --bind-address 0.0.0.0:9090 \
  --context-api-key "$SPECTRON_API_KEY"
```

Without `--bootstrap`, run standalone `spectrond bootstrap` once, then `dev start` as before.

```bash
spectrond dev start \
  --connection-string "$SURREALDB_CONNECTION" \
  --bind-address 0.0.0.0:9090
```

Export LLM provider keys before `dev start` if you need extraction or chat (`infer: full` is not embeddings-only). A bare `spectrond start` alias exists for older single-process layouts but is hidden from `--help` - prefer `dev start` or the split production roles below.

For a full provisioning walkthrough (bootstrap, scopes, principals, keys, upload, query), follow the [Hosted quickstart](/docs/agent-memory/quickstarts/hosted.md) and [CLI](#connection-flags-client) reference below.

### Production roles

```bash
spectrond api start …          # REST + MCP
spectrond worker start …       # job queue consumer
spectrond scheduler start …    # periodic background work
spectrond management start …   # management REST only
```

Common flags:

| Flag | Env | Default |
| --- | --- | --- |
| `--connection-string` | `SURREALDB_CONNECTION` | - |
| `--embeddings-api-key` | `SPECTRON_EMBEDDINGS_API_KEY` | - |
| `--bind-address` | `SPECTRON_BIND_ADDRESS` | `0.0.0.0:9090` |
| `--object-store-url` | `SPECTRON_OBJECT_STORE_URL` | - |

---

## `spectron` - data plane

### Unified verbs

| Command | REST equivalent |
| --- | --- |
| `spectron remember "…"` | `POST /api/v1/{ctx}/facts` |
| `spectron recall "…"` | `POST /api/v1/{ctx}/query` |
| `spectron context "…"` | `POST /api/v1/{ctx}/context` |
| `spectron chat [message]` | `POST /api/v1/{ctx}/chat` |
| `spectron reflect "…"` | `POST /api/v1/{ctx}/reflect` |
| `spectron forget "…"` | `POST /api/v1/{ctx}/forget` |

`forget` supports **`--dry-run`** to preview matches without expiring records.

`remember` flags: `--infer full|triples|preview|none`, `--from-file`, `--transcript`, `--scope org/acme/user/alice`, `--extract whole_conversation|per_message` (batch).

`recall` flags: `--limit`, `--mode hybrid|vector|bm25|graph`, `--include facts,passages`. Pass **`scope`** on the REST `/query` body - the CLI does not expose `--scope` on `recall` today.

**Unsupported CLI flags (rejected with a clear error):** `remember --confidence`, `--trust`, `--location`; `recall --min-trust`; `spectron lifecycle expire --older-than` (expiry thresholds are configured per Context, not per CLI invocation). Use REST or management API where those controls exist.

### Documents

```bash
spectron documents upload ./manual.pdf --scope org/acme/team/eng --label team=eng
spectron ingest ./folder --scope org/acme/team/eng --label team=eng
spectron documents list
spectron recall "return policy" --include passages
```

`--scope` on upload narrows tagging to a path within the caller's `memory:write` region (same semantics as `remember --scope`). `--label` may be repeated for `key=value` tags stamped on the document and chunks. Omit `--scope` to use the full write region.

### Sessions, entities, traces

```bash
spectron sessions list
spectron entities show Person/alice
spectron traces show <trace_id>
```

### MCP server

The MCP server is served at `/mcp` on the same host and port as the REST API - no CLI step is needed. Point your client at that endpoint, or run [`install-mcp`](https://github.com/supermemoryai/install-mcp). See the [MCP server install guide](/docs/agent-memory/integrations/mcp-server/install.md).

### Operator provisioning

```bash
spectrond contexts create …
spectrond keys generate-key …
spectrond keys rotate <context_id> <key_name> [--expires-in <seconds>]
```

**Create principals** (management API - not the data-plane Context key). The two
binaries reach the control plane over different transports, so each takes its own
URL:

| Binary | Transport | Flag reads | Default port |
| --- | --- | --- | --- |
| `spectrond` (`contexts`, `principals`, `keys`) | gRPC | `SPECTRON_MANAGEMENT_GRPC_URL` | `9091` |
| `spectron` (thin client) | REST | `SPECTRON_MANAGEMENT_URL` | `9090` |

**Bash**

```bash
export SPECTRON_MANAGEMENT_GRPC_URL=http://127.0.0.1:9091   # spectrond speaks gRPC
export SPECTRON_MANAGEMENT_URL=http://127.0.0.1:9090        # spectron speaks REST
export SPECTRON_MANAGEMENT_API_KEY=sp-…

spectrond principals create demo "Planner bot" \
  --kind agent \
  --grant memory:read=team/eng \
  --grant memory:write=team/eng \
  --url "$SPECTRON_MANAGEMENT_GRPC_URL" \
  --api-key "$SPECTRON_MANAGEMENT_API_KEY"

# thin client (reads SPECTRON_MANAGEMENT_* + SPECTRON_CONTEXT_ID from env)
spectron principals create "Planner bot" --kind agent -c demo \
  --grant memory:read=team/eng --grant memory:write=team/eng
```

**PowerShell**

```powershell
$env:SPECTRON_MANAGEMENT_GRPC_URL = "http://127.0.0.1:9091"   # spectrond speaks gRPC
$env:SPECTRON_MANAGEMENT_URL = "http://127.0.0.1:9090"   # spectron speaks REST
$env:SPECTRON_MANAGEMENT_API_KEY = "sp-…"

spectrond principals create demo "Planner bot" `
  --kind agent `
  --grant memory:read=team/eng `
  --grant memory:write=team/eng `
  --url "$SPECTRON_MANAGEMENT_GRPC_URL" `
  --api-key "$SPECTRON_MANAGEMENT_API_KEY"

# thin client (reads SPECTRON_MANAGEMENT_* + SPECTRON_CONTEXT_ID from env)
spectron principals create "Planner bot" --kind agent -c demo `
  --grant memory:read=team/eng --grant memory:write=team/eng
```

Prints the server-minted principal `id`. Mint an agent key for that principal via the management API or `spectrond keys generate-key`.

> [!NOTE]
> Point `spectrond` at the REST port and the call fails with `grpc-status header
> missing, mapped from HTTP status code 404`. The endpoint is reachable; it
> speaks the other protocol. Both servers run at full parity, so the choice is
> transport only.

The management REST default (`9090`) is the same port the end-user API server
uses. When you run both on one host, override one of them - for example
`--bind-address 0.0.0.0:9095 --grpc-bind-address 0.0.0.0:9096` on
`spectrond management start` - and use the ports you chose in the variables
above.

### Terminal workbench

| Command | Description |
| --- | --- |
| `spectron tui` | Four-pane workbench: input, entity tree, trace timeline, inspector (`Tab` cycles panes). `--session <id>` pins a session; `--replay <path>` plays a recorded jsonl without HTTP. |
| `spectron repl` | Interactive REPL: bare lines and **`/remember`** write facts (`infer: full`); `/recall`, `/chat`, `/inspect`, `/scope`, `/as-of`, `/upload`, `/forget`, `/record`; tab completion from prior responses. Colour is on when stdout is a terminal; pass **`--ascii`** for plain output (same flag as `spectron tui`). |

Scope in the REPL and TUI uses **slash paths** (`org/acme/user/alice`), matching the wire `ScopeSet`.

### REPL triple syntax

Interactive mode supports structured triple writes:

```text
/fact entity=Person/Alice attr=role val=CTO
```

Uses the same triple syntax as `spectron remember --triple` (`infer=triples`).

Run `spectron --help` for the full command tree.

---

Source: https://surrealdb.com/docs/agent-memory/reference/configuration

# Configuration

Context config, models, and limits.

SurrealDB Agent Memory has two configuration surfaces: server-wide settings (in the server binary or environment) and per-Context configuration (stored in the control plane and patchable at runtime).

## Server-wide configuration

Server configuration is provided via environment variables or a TOML configuration file passed at startup. These settings apply to all Contexts unless overridden at the Context level.

> [!NOTE]
> Every variable keeps the `SPECTRON_` prefix from **Spectron**, the project name SurrealDB Agent Memory was developed under. These names are part of the shipped interface, so the product renaming leaves them unchanged for now; they will be renamed in a future release.

### Core settings

| Variable | Type | Default | Description |
|---|---|---|---|
| `SPECTRON_BIND` | string | `0.0.0.0:8080` | Listen address and port |
| `SPECTRON_SURREALDB_URL` | string | - | SurrealDB connection URL (required) |
| `SPECTRON_SURREALDB_USER` | string | - | SurrealDB username (required) |
| `SPECTRON_SURREALDB_PASS` | string | - | SurrealDB password (required) |
| `SPECTRON_OBJECT_STORE` | string | `local://./data` | Object store backend (see below) |

### Default model settings

Per-stage defaults are cost-tiered for the deployment’s chosen LLM provider (`SPECTRON_LLM_PROVIDER` / implicit Google). Optional env overrides:

| Variable | Description |
|---|---|
| `SPECTRON_LLM_MODEL` | Global model override when a stage has no per-Context selection |
| `SPECTRON_MODEL_EMBEDDING` | Must be `gemini-embedding-2` (3072-dim). Embedding is **fixed per deployment** - not a free per-Context choice |

The embedding model is **fixed per deployment** - it is not a per-Context override. Context config rejects any `models.embedding` value other than the deployment default. Changing the server embedding model requires a [reindex](/docs/agent-memory/reference/management-api.md#force-reindex) so vectors and HNSW indexes stay in the same embedding space.

### Reranker (optional)

Cross-encoder reranking for `/documents/query` when `use_reranker=true`:

| Variable | Description |
| --- | --- |
| `SPECTRON_RERANKER_URL` | POST endpoint for the reranker service. Unset ⇒ no provider; requests fall through to bi-encoder ordering. |
| `SPECTRON_RERANKER_MODEL` | Required when URL is set. Boot error if URL is set without a model. |
| `SPECTRON_RERANKER_API_KEY` | Optional bearer token (`Authorization: Bearer …`). |

### Multimodal providers (optional)

HTTP OCR, CLIP, and speech-to-text for document ingestion (read by the **`worker`** role). An HTTP provider **takes precedence** over the built-in local fallback for the same modality. Misconfigured URLs fail at boot.

| Variable | Description |
| --- | --- |
| `SPECTRON_OCR_URL` | POST endpoint for OCR. Unset ⇒ built-in or local Tesseract (when enabled). |
| `SPECTRON_OCR_MODEL` | Required when OCR URL is set. |
| `SPECTRON_OCR_API_KEY` | Optional bearer token. |
| `SPECTRON_CLIP_URL` | POST endpoint for visual embeddings. Output must match the **3072**-dim `image_chunk` width (same space as `gemini-embedding-2` when using Gemini CLIP). |
| `SPECTRON_CLIP_MODEL` | Required when CLIP URL is set. |
| `SPECTRON_CLIP_API_KEY` | Optional bearer token. |
| `SPECTRON_STT_URL` | POST endpoint for speech-to-text. |
| `SPECTRON_STT_MODEL` | Required when STT URL is set. |
| `SPECTRON_STT_API_KEY` | Optional bearer token. |

See [Multimodal content](/docs/agent-memory/ingest/authoritative/multimodal-content.md).

### Default provider API keys

| Variable | Description |
|---|---|
| `SPECTRON_PROVIDER_OPENAI_API_KEY` | OpenAI key available to Contexts and stages |
| `SPECTRON_PROVIDER_ANTHROPIC_API_KEY` | Anthropic key available to Contexts and stages |
| `SPECTRON_PROVIDER_GOOGLE_API_KEY` | Google (Gemini) key; also used when Gemini is the implicit request-path default |
| `SPECTRON_LLM_PROVIDER` | Explicit default provider for unset stages: `openai` \| `anthropic` \| `google`. Unset ⇒ a present Google key makes Gemini the implicit default on `/chat` and `/facts?infer=full` |
| `SPECTRON_EMBEDDINGS_API_KEY` | Gemini Developer API key for embeddings (`gemini-embedding-2`, 3072-dim). Embeddings are Gemini-only |

### Object store configuration

| Backend | `SPECTRON_OBJECT_STORE` format | Notes |
|---|---|---|
| Local filesystem | `local:///path/to/data` | Development and single-node deployments |
| Amazon S3 | `s3://bucket-name/prefix` | Requires `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` or instance role |
| Google Cloud Storage | `gcs://bucket-name/prefix` | Requires `GOOGLE_APPLICATION_CREDENTIALS` |
| Azure Blob Storage | `azure://container/prefix` | Requires `AZURE_STORAGE_ACCOUNT` + `AZURE_STORAGE_ACCESS_KEY` |

### CORS (browser clients)

Cross-origin browser calls to the API are **off by default**. Enable an origin allowlist when a web client (for example SurrealDB Studio against a Cloud-brokered access token) calls the user API from a different origin than the API host.

| Service | Variable | CLI flag |
| --- | --- | --- |
| User API | `SPECTRON_CORS_ALLOWED_ORIGINS` | `--cors-allowed-origins` |
| Management API | `SPECTRON_MANAGEMENT_CORS_ALLOWED_ORIGINS` | `--cors-allowed-origins` |

Comma-separated origins. Entries are trimmed, lower-cased, and normalised (trailing `/` stripped). Exact entries match the `Origin` header verbatim; entries containing `*` are **anchored globs** on both sides (bare `*` or `https://*` are rejected). Allowed origins are echoed in `Access-Control-Allow-Origin`; credentials are not used - callers authenticate with `Authorization`, not cookies. Preflight mirrors request headers so SDK headers (`api-version`, `X-Spectron-Context`, `Idempotency-Key`, and others) pass without a fixed allowlist.

The management API is normally server-side only; CORS is optional there for operator tooling.

## Per-Context configuration

Each Context stores a `config` object in the control plane. This is updated via `PATCH /api/v1/contexts/{id}` and applies immediately to new requests.

### Full config schema

```json
{
  "config": {
    "token_limit": 1000000,
    "models": {
      "extraction": { "provider": "google", "model": "gemini-2.5-flash" },
      "synthesis": { "provider": "google", "model": "gemini-2.5-pro" },
      "elaboration_consolidation": { "provider": "google", "model": "gemini-2.5-flash" },
      "embedding": "gemini-embedding-2"
    },
    "providers": {
      "google": "…",
      "openai": "sk-…",
      "anthropic": "sk-ant-…"
    }
  }
}
```

### Config fields

| Field | Type | Description |
|---|---|---|
| `token_limit` | integer (optional) | Soft monthly token cap for metering and billing. Does not reject requests while `enforcement_blocked` is `false`. `null` = no cap. |
| `ingestion_profile` | string | Document ingest dial: `TextOnly`, `TextPlusKeyword`, `StandardMultimodal`, or `MultimodalFull` (default). See [Multimodal content](/docs/agent-memory/ingest/authoritative/multimodal-content.md). |
| `models.extraction` | `{provider, model}` | LLM for turn and document extraction. |
| `models.reconciliation` | `{provider, model}` (optional) | LLM assist when structural entity merge is inconclusive. |
| `models.synthesis` | `{provider, model}` | LLM for `/chat` and `/reflect`. |
| `models.elaboration_consolidation` | `{provider, model}` | LLM for worker elaboration and consolidation. |
| `models.embedding` | string | Must be `gemini-embedding-2` when set (3072-dim; deployment-fixed). |
| `providers.google` | string | Google (Gemini) API key for this Context. |
| `providers.openai` | string | OpenAI API key for this Context. Overrides the server-wide default. |
| `providers.anthropic` | string | Anthropic API key for this Context. |

### Provider API key visibility

Provider API keys are **write-only** on the API surface. The read projection for a Context replaces the key values with a **`providers_configured`** summary - names of providers for which **this Context** stores its own key:

```json
{
  "config": {
    "providers_configured": ["google", "openai", "anthropic"]
  }
}
```

This is **not** the same as **`GET /api/v1/{context_id}/providers`**, which lists providers reachable via a global deployment key **or** a per-Context key and includes selectable model ids. The two surfaces are not derivable from each other.

The raw key values never appear in read responses.

### Top-level Context fields (outside `config`)

These fields live on the Context record itself, not inside the `config` object. A `PATCH` that only updates `config` cannot change them.

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `enforcement_blocked` | boolean | `false` | When `true`, gated LLM-backed requests return **`429`** regardless of the soft `token_limit`. When `false`, usage may exceed `token_limit` (pay-as-you-go). |

### Patching config

Send only the fields you want to change. Unset fields are left unchanged (deep merge):

```http
PATCH /api/v1/contexts/acme-prod
Content-Type: application/json
Authorization: Bearer mgmt-...

{
  "config": {
    "token_limit": 2000000,
    "models": {
      "reflection": "anthropic/claude-opus-4-7"
    }
  }
}
```

## Extraction tuning

Additional per-Context settings control extraction behaviour:

| Field | Type | Default | Description |
|---|---|---|---|
| `llm_extraction_enabled` | boolean | `false` | Whether typed-node extraction runs at all. With it off, documents and turns are still stored and searchable, but produce no entities, attributes, or relations |
| `pii_redaction_enabled` | boolean | `false` | Redact detected personal data during ingest |
| `reconciliation.confidence_floor` | float | `0.7` | Posterior-confidence floor for auto-supersession. Below it, a conflicting assertion records an `uncertainty` instead of replacing the prior value |
| `ingestion_profile` | string | - | Which pipeline steps run during document ingest |
| `chunking_strategy` | string | - | How document text is split before embedding |

There is no setting that constrains which entity types, attribute keys, or relation labels extraction may produce. Entity types come from a fixed vocabulary; keys and labels converge through reuse. See [Extraction vocabulary](/docs/agent-memory/tuning/ontology-grounding.md).

## Cache settings

| Field | Type | Default | Description |
|---|---|---|---|
| `response_cache.enabled` | boolean | `true` | Master switch. With it `false`, every `/chat` and `/reflect` call skips the cache tier |
| `response_cache.similarity_threshold` | float | `0.92` | Cosine-similarity floor a prior query must score against the new one before its answer is reused |
| `response_cache.freshness_window_seconds` | integer | `3600` | Soft cap on how old a reusable response may be |

## Key policy

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `allow_self_service_keys` | boolean | `true` | When `false`, members cannot mint keys via `POST /{ctx}/keys`; use Cloud-brokered access tokens only. |
| `max_token_ttl_seconds` | integer (optional) | none | Maximum TTL clamp applied to every key mint (management, broker, self-service). `null` = no clamp. |

Every **data-plane** API key must be **bound to a principal**. Keys with no principal binding are rejected with **`401`** - there is no unscoped passthrough mode. Mint keys under a principal (management API or self-service `POST /{ctx}/keys`).

## Request and list limits

Operator-tunable ceilings (env vars, read at process start):

| Variable | Default | Caps |
| --- | --- | --- |
| `SPECTRON_DEFAULT_PAGE_SIZE` | 100 | Default `limit` when listing session turns and omitted elsewhere |
| `SPECTRON_MAX_LIST_LIMIT` | 500 | Maximum rows per list response (`list_turns`, traces, audit) |
| `SPECTRON_MAX_QUERY_K` | 50 | Maximum `limit` / `k` on `/query`, `/context`, document query, and MCP `recall` / `context`. **Clamp-down only** - the env var can lower the ceiling but never raise it above 50. Default answer size **`k`** / **`limit`** is **10**. |
| `SPECTRON_RETRIEVAL_POOL_SIZE` | 256 | Internal candidate-pool breadth for fused retrieval. **Decoupled from `k`** - `k` only truncates the fused answer; raising `k` does not widen the search pool. |
| `SPECTRON_RETRIEVAL_SECTION_EXPANSION` | `true` | When on (default), pull same-section sibling passages into `contextHits` after ranking so synthesis sees section bodies, not only heading/pointer chunks. Opt out with `0` / `false`. Does not change ranked `hits`. See [Section expansion](/docs/agent-memory/retrieve/recall.md#section-expansion). |
| `SPECTRON_DB_WS_MAX_MESSAGE_BYTES` | `134217728` (128 MiB) | Client-side WebSocket per-message cap for pooled SurrealDB connections. Raise in lockstep with the server's `SURREAL_WEBSOCKET_MAX_MESSAGE_SIZE` when large document persists fail with `Message too long`. |
| `SPECTRON_TRACE_FEATURE_TTL_SECS` | 60 | TTL (seconds) for the in-process per-(Context, scope) trace-features cache in the fused ranker - how long prior retrieval outcomes re-weight candidates before recomputation. Process-local; `0` or invalid values fall back to the default. |

Requests above the query ceiling return **`400 Bad Request`**.

## Reconciliation

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `reconciliation.confidence_floor` | float | `0.7` | Minimum confidence required for same-provenance supersession |

## Defaults reference

When a per-Context field is not set, the server-wide default applies. The effective configuration for a Context is always visible at:

```http
GET /api/v1/contexts/{id}
```

The response includes the `config` object with all effective values merged - Context-level overrides where set, server-wide defaults elsewhere.

---

Source: https://surrealdb.com/docs/agent-memory/reference/data-model-and-schema

# Data model and schema

Tables, relations, and indexes in SurrealDB.

SurrealDB Agent Memory stores all state in SurrealDB. This page describes the key tables, their fields, and the indexes that power retrieval. Schema migrations are bundled in the SurrealDB Agent Memory binary and applied automatically when a Context is created or upgraded.

## Schema namespaces

| Namespace | Database | Contents |
|---|---|---|
| `spectron` | `metadata` | Control plane: Context registry, API keys |
| `spectron` | `_jobqueue` | Async ingestion job queue |
| `<context_ns>` | `<context_db>` | All authoritative knowledge and experiential memory data for one Context |

Each Context is bound to its own `(namespace, database)` pair. Tables are never shared across Contexts.

## Scope on stored records

Scoped tables carry **`scope_sets`** - content-addressed OR-of-AND clauses referencing hierarchical **`scopes`** paths. Entity and keyword index records also store derived **`visible_scope_sets`** so name/type lookups cannot leak facts the caller cannot read whole. See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md) for the visibility rules.

## Control plane tables

### `context`

The Context registry. Records each Context's binding and embedded configuration.

| Field | Type | Notes |
|---|---|---|
| `id` | string | Context identifier (e.g. `acme-prod`) |
| `namespace` | string | Bound SurrealDB namespace |
| `database` | string | Bound SurrealDB database |
| `config` | object | Embedded config (models, providers, limits) |
| `created_at` | datetime | |

### `api_key`

Management keys (not per-Context). Used for creating and managing Contexts.

### `context_api_key`

Per-Context end-user keys, stored as Argon2 hashes. Each key binds to a principal and optionally attenuates that principal's grants.

| Field | Type | Notes |
|---|---|---|
| `context` | `record<context>` | The Context this key belongs to |
| `name` | string | Human-readable label, unique per Context |
| `hash` | string | Argon2 hash of the secret |
| `principal_id` | option\<string\> | The principal this key acts as. Stored as a plain string because the `principal` row lives in the Context's own database, not the control plane |
| `grants` | option\<object\> | Attenuating per-verb pattern map (`grants.*` is `array<string>`). Absent ⇒ inherit the principal's grants |
| `strict` | bool | Opt into hard out-of-region errors instead of empty results. Defaults to `false` |
| `valid_until` | option\<datetime\> | Mint-time expiry. Absent ⇒ no expiry |
| `created_at` | datetime | |
| `last_used_at` | option\<datetime\> | Vestigial - no longer written. Last use is derived as `max(used_at)` over `context_api_key_usage`; the column stays defined so older rows deserialise |

### `context_api_key_usage`

Append-only key-use log: one row per successful data-plane key validation, throttled to at most one row per key per minute. This is what backs the derived `lastUsedAt` on the self-service key listing - it records recent activity, not a per-request audit trail.

| Field | Type | Notes |
|---|---|---|
| `key` | `record<context_api_key>` | The key that validated |
| `context` | `record<context>` | |
| `used_at` | datetime | |

## Knowledge tables (Authoritative pillar, per Context)

### `document`

Source files and their processing state.

```surql
DEFINE TABLE document SCHEMAFULL;
DEFINE FIELD title              ON document TYPE string;
DEFINE FIELD mime_type          ON document TYPE string;
DEFINE FIELD source             ON document TYPE string;
DEFINE FIELD storage_key        ON document TYPE string;
DEFINE FIELD content_hash       ON document TYPE string;
DEFINE FIELD size_bytes         ON document TYPE int;
DEFINE FIELD observed_at        ON document TYPE option<datetime>;
DEFINE FIELD scope              ON document TYPE array<record<scope_sets>>;
DEFINE FIELD version            ON document TYPE int DEFAULT 1;
DEFINE FIELD status             ON document TYPE string DEFAULT "queued"
    ASSERT $value IN ["queued", "extracting", "chunking", "embedding", "keywording", "ready", "failed"];
DEFINE FIELD error              ON document TYPE option<string>;
DEFINE FIELD processing_started_at   ON document TYPE option<datetime>;
DEFINE FIELD processing_completed_at ON document TYPE option<datetime>;
DEFINE FIELD created_at         ON document TYPE datetime DEFAULT time::now() READONLY;
DEFINE FIELD updated_at         ON document TYPE datetime DEFAULT time::now();
DEFINE INDEX content_hash_index ON document FIELDS content_hash;
DEFINE INDEX scope_index        ON document FIELDS scope;
DEFINE INDEX status_index       ON document FIELDS status;
```

### `knowledge_chunk`

Text segments extracted from documents, with vector embeddings.

```surql
DEFINE TABLE knowledge_chunk SCHEMAFULL;
DEFINE FIELD document    ON knowledge_chunk TYPE record<document>;
DEFINE FIELD text        ON knowledge_chunk TYPE string;
DEFINE FIELD embedding   ON knowledge_chunk TYPE option<array<float, 3072>>;
DEFINE FIELD position    ON knowledge_chunk TYPE int;
DEFINE FIELD section     ON knowledge_chunk TYPE option<string>;
DEFINE FIELD char_start  ON knowledge_chunk TYPE int;
DEFINE FIELD char_end    ON knowledge_chunk TYPE int;
DEFINE FIELD token_count ON knowledge_chunk TYPE option<int>;
DEFINE FIELD simhash     ON knowledge_chunk TYPE option<int>;
DEFINE FIELD duplicate_of ON knowledge_chunk TYPE option<record<knowledge_chunk>>;
DEFINE FIELD scope       ON knowledge_chunk TYPE set<record<scope_attribute>>;
DEFINE FIELD created_at  ON knowledge_chunk TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX embedding_index ON knowledge_chunk FIELDS embedding HNSW DIMENSION 3072 DIST COSINE TYPE F32;
DEFINE INDEX document_index  ON knowledge_chunk FIELDS document;
DEFINE INDEX scope_index     ON knowledge_chunk FIELDS scope;
```

Embedding width is fixed deployment-wide at **3072** (`gemini-embedding-2`). Optional **`simhash`** / **`duplicate_of`** support near-duplicate suppression on recall (see **`includeDuplicates`** on `/query`).

### `keyword`

RAKE-extracted keyphrases from documents, stored as nodes in the keyword graph.

```surql
DEFINE TABLE keyword SCHEMAFULL;
DEFINE FIELD text           ON keyword TYPE string;
DEFINE FIELD normalised     ON keyword TYPE string;
DEFINE FIELD embedding      ON keyword TYPE option<array<float, 3072>>;
DEFINE FIELD scope          ON keyword TYPE set<record<scope_attribute>> DEFAULT [];
DEFINE FIELD document_count ON keyword TYPE int DEFAULT 0;
DEFINE FIELD created_at     ON keyword TYPE datetime DEFAULT time::now() READONLY;
DEFINE FIELD updated_at     ON keyword TYPE datetime DEFAULT time::now();
DEFINE INDEX embedding_index
  ON keyword FIELDS embedding HNSW DIMENSION 3072 DIST COSINE TYPE F32;
DEFINE INDEX normalised_index ON keyword FIELDS normalised UNIQUE;
```

### `knowledge_has_keyword`

Relation edge from a document to its keywords.

```surql
DEFINE TABLE knowledge_has_keyword TYPE RELATION IN document OUT keyword SCHEMAFULL;
DEFINE FIELD score      ON knowledge_has_keyword TYPE float;
DEFINE FIELD scope      ON knowledge_has_keyword TYPE set<record<scope_attribute>> DEFAULT [];
DEFINE FIELD created_at ON knowledge_has_keyword TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX unique_pair ON knowledge_has_keyword FIELDS in, out UNIQUE;
```

Structured authoritative facts extracted from documents (or ingested via `POST /facts` with `infer: "triples"`) are stored as **`entity`**, **`attribute`**, and **`relates_to`** records with `source.kind = "document"`.

## Experiential pillar tables (per Context)

### `scope_attribute`

Canonical key-value scope tags shared across all scoped records.

```surql
DEFINE TABLE scope_attribute SCHEMAFULL;
DEFINE FIELD key   ON scope_attribute TYPE string;
DEFINE FIELD value ON scope_attribute TYPE string;
DEFINE INDEX key_value ON scope_attribute FIELDS key, value UNIQUE;
```

### `session`

First-class conversation records.

```surql
DEFINE TABLE session SCHEMAFULL;
DEFINE FIELD scope      ON session TYPE set<record<scope_attribute>>;
DEFINE FIELD metadata   ON session TYPE option<object> FLEXIBLE;
DEFINE FIELD created_at ON session TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX scope_index ON session FIELDS scope;
```

### `turn`

Individual messages within a session.

```surql
DEFINE TABLE turn SCHEMAFULL;
DEFINE FIELD session    ON turn TYPE record<session>;
DEFINE FIELD role       ON turn TYPE string
    ASSERT $value IN ["user", "assistant", "system", "tool"];
DEFINE FIELD content    ON turn TYPE string;
DEFINE FIELD seq        ON turn TYPE int;
DEFINE FIELD created_at ON turn TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX session_seq ON turn FIELDS session, seq UNIQUE;
```

### `entity`

Named things tracked in experiential memory.

```surql
DEFINE TABLE entity SCHEMAFULL;
DEFINE FIELD id              ON entity TYPE array<string, 2>;
DEFINE FIELD name            ON entity TYPE string;
DEFINE FIELD type            ON entity TYPE string;
DEFINE FIELD memory_category ON entity TYPE string
    ASSERT $value IN ["identity", "knowledge", "context"];
DEFINE FIELD scope           ON entity TYPE set<record<scope_attribute>>;
DEFINE FIELD embedding       ON entity TYPE option<array<float, 3072>>;
DEFINE FIELD resolves_to     ON entity TYPE option<record<knowledge>>;
DEFINE FIELD source_turn     ON entity TYPE option<record<turn>>;
DEFINE FIELD created_at      ON entity TYPE datetime DEFAULT time::now() READONLY;
DEFINE FIELD updated_at      ON entity TYPE datetime DEFAULT time::now();
DEFINE INDEX embedding_index 
  ON entity FIELDS embedding HNSW DIMENSION 3072 DIST COSINE TYPE F32;
DEFINE INDEX type_index       ON entity FIELDS type;
DEFINE INDEX scope_index      ON entity FIELDS scope;
DEFINE INDEX resolves_to_index ON entity FIELDS resolves_to;
```

### `attribute`

Key-value properties on entities, with supersession chains.

```surql
DEFINE TABLE attribute SCHEMAFULL;
DEFINE FIELD entity          ON attribute TYPE record<entity>;
DEFINE FIELD key             ON attribute TYPE string;
DEFINE FIELD value           ON attribute TYPE string;
DEFINE FIELD memory_category ON attribute TYPE string
    ASSERT $value IN ["identity", "knowledge", "context"];
DEFINE FIELD scope           ON attribute TYPE set<record<scope_attribute>>;
DEFINE FIELD supersedes      ON attribute TYPE option<record<attribute>>;
DEFINE FIELD superseded_by   ON attribute TYPE option<record<attribute>>;
DEFINE FIELD source_turn     ON attribute TYPE option<record<turn>>;
DEFINE FIELD valid_from      ON attribute TYPE option<datetime>;
DEFINE FIELD valid_until     ON attribute TYPE option<datetime>;
DEFINE FIELD created_at      ON attribute TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX entity_key      ON attribute FIELDS entity, key;
DEFINE INDEX scope_index     ON attribute FIELDS scope;
DEFINE INDEX temporal_index  ON attribute FIELDS valid_from, valid_until;
```

### `relates_to`

Graph edges between entities.

```surql
DEFINE TABLE relates_to TYPE RELATION IN entity OUT entity SCHEMAFULL;
DEFINE FIELD label           ON relates_to TYPE string;
DEFINE FIELD memory_category ON relates_to TYPE string
    ASSERT $value IN ["identity", "knowledge", "context"];
DEFINE FIELD scope       ON relates_to TYPE set<record<scope_attribute>>;
DEFINE FIELD source_turn ON relates_to TYPE option<record<turn>>;
DEFINE FIELD valid_from  ON relates_to TYPE option<datetime>;
DEFINE FIELD valid_until ON relates_to TYPE option<datetime>;
DEFINE FIELD created_at  ON relates_to TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX label_index    ON relates_to FIELDS label;
DEFINE INDEX scope_index    ON relates_to FIELDS scope;
DEFINE INDEX temporal_index ON relates_to FIELDS valid_from, valid_until;
```

### `instruction`

Behavioural directives for the agent.

```surql
DEFINE TABLE instruction SCHEMAFULL;
DEFINE FIELD label       ON instruction TYPE string;
DEFINE FIELD description ON instruction TYPE string;
DEFINE FIELD active      ON instruction TYPE bool DEFAULT true;
DEFINE FIELD scope       ON instruction TYPE set<record<scope_attribute>>;
DEFINE FIELD source_turn ON instruction TYPE option<record<turn>>;
DEFINE FIELD created_at  ON instruction TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX scope_index ON instruction FIELDS scope;
```

### `uncertainty`

Ambiguous or unresolved information from extraction.

```surql
DEFINE TABLE uncertainty SCHEMAFULL;
DEFINE FIELD about      ON uncertainty TYPE string;
DEFINE FIELD reason     ON uncertainty TYPE string;
DEFINE FIELD scope      ON uncertainty TYPE set<record<scope_attribute>>;
DEFINE FIELD source_turn ON uncertainty TYPE record<turn>;
DEFINE FIELD resolved   ON uncertainty TYPE bool DEFAULT false;
DEFINE FIELD created_at ON uncertainty TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX scope_index ON uncertainty FIELDS scope;
```

### `memory_chunk`

Raw text segments from turns, embedded for semantic recall.

```surql
DEFINE TABLE memory_chunk SCHEMAFULL;
DEFINE FIELD session      ON memory_chunk TYPE record<session>;
DEFINE FIELD text         ON memory_chunk TYPE string;
DEFINE FIELD embedding    ON memory_chunk TYPE option<array<float, 3072>>;
DEFINE FIELD scope        ON memory_chunk TYPE set<record<scope_attribute>>;
DEFINE FIELD source_turn  ON memory_chunk TYPE option<record<turn>>;
DEFINE FIELD turn         ON memory_chunk TYPE option<record<turn>>;
DEFINE FIELD position     ON memory_chunk TYPE option<int>;
DEFINE FIELD char_start   ON memory_chunk TYPE option<int>;
DEFINE FIELD char_end     ON memory_chunk TYPE option<int>;
DEFINE FIELD token_count  ON memory_chunk TYPE option<int>;
DEFINE FIELD role         ON memory_chunk TYPE option<string>;
DEFINE FIELD simhash      ON memory_chunk TYPE option<int>;
DEFINE FIELD duplicate_of ON memory_chunk TYPE option<record<memory_chunk>>;
DEFINE FIELD created_at   ON memory_chunk TYPE datetime DEFAULT time::now() READONLY;
DEFINE INDEX embedding_index ON memory_chunk FIELDS embedding HNSW DIMENSION 3072 DIST COSINE TYPE F32;
DEFINE INDEX session_index   ON memory_chunk FIELDS session;
DEFINE INDEX scope_index     ON memory_chunk FIELDS scope;
```

Optional **`role`**, **`simhash`**, and **`duplicate_of`** support role-aware retrieval and near-duplicate suppression (see **`includeDuplicates`** on `/query`). **`char_start` / `char_end` / `token_count`** locate each sub-chunk in the turn’s text when a long passage is split for embedding.

### `decision_trace`

Audit record for every retrieval and storage operation.

| Field | Description |
|---|---|
| `query` | The original query or operation description |
| `tier` | Resolution tier: `direct`, `cache`, `hybrid`, `full_context` |
| `scope` | The scope used for this operation |
| `sources` | IDs of contributing memory/knowledge chunks |
| `token_cost` | Tokens consumed by any LLM call |
| `duration_ms` | Total duration |
| `api_key_id` | The key that made the request |
| `session_id` | Associated session (if any) |
| `created_at` | Timestamp |

## Migrations

Schema is bundled into the SurrealDB Agent Memory binary and applied automatically when a Context is created or upgraded. See [Migration status](/docs/agent-memory/reference/management-api.md#migration-status) for the fleet-wide catch-up view.

---

Source: https://surrealdb.com/docs/agent-memory/reference/errors

# Errors

Error codes and troubleshooting.

SurrealDB Agent Memory uses standard HTTP status codes and follows [RFC 7807 Problem Details](https://www.rfc-editor.org/rfc/rfc7807) for all error responses.

## Error response format

All errors return a JSON body with the following fields:

```json
{
  "type": "https://spectron.dev/errors/context-not-found",
  "title": "Context not found",
  "status": 404,
  "detail": "No context with id 'acme-staging' exists on this server.",
  "instance": "/api/v1/contexts/acme-staging"
}
```

| Field | Description |
|---|---|
| `type` | A URI identifying the error type. Stable across versions. |
| `title` | A short, human-readable summary of the problem type. |
| `status` | The HTTP status code. |
| `detail` | A human-readable explanation specific to this occurrence. |
| `instance` | The request path that produced the error. |

## HTTP status codes

### 400 Bad Request

The request body or query parameters are invalid. Common causes:

- Malformed JSON body
- Missing required fields
- Invalid field values (e.g. unknown `mode` for knowledge query)
- Scope floor violation (requesting a scope narrower than the key's floor)

```json
{
  "type": "https://spectron.dev/errors/validation-error",
  "title": "Validation error",
  "status": 400,
  "detail": "Field 'mode' must be one of: vector, bm25, hybrid, hybrid_graph. Got: 'fuzzy'.",
  "instance": "/api/v1/acme-prod/query"
}
```

### 401 Unauthorized

No `Authorization: Bearer` token was provided, or the key is malformed.

```json
{
  "type": "https://spectron.dev/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "API key missing. Include 'Authorization: Bearer <key>' in your request.",
  "instance": "/api/v1/acme-prod/sessions"
}
```

### 403 Forbidden

The API key is valid but does not have permission to perform this operation. Common causes:

- Agent key attempting a management operation
- The requested scope falls outside the key's granted region for the verb
- Agent key attempting to persist a reflection (requires supervisor principal)

```json
{
  "type": "https://spectron.dev/errors/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "This operation requires a management key. The provided key has principal 'agent'.",
  "instance": "/api/v1/contexts"
}
```

### 404 Not Found

The requested resource does not exist.

```json
{
  "type": "https://spectron.dev/errors/not-found",
  "title": "Not found",
  "status": 404,
  "detail": "No document with id 'document:0197d8f2...' exists in context 'acme-prod'.",
  "instance": "/api/v1/acme-prod/documents/0197d8f2"
}
```

### 409 Conflict

A resource with the same identifier already exists, or an idempotency conflict occurred:

- Duplicate Context id on create
- Duplicate **principal `display_name`** on create (when no matching `external_id` create-or-get applies)
- Same `Idempotency-Key` with a **different** request body on `/facts` or `/facts/batch`
- Duplicate idempotency request while the first is still **in flight**

```json
{
  "type": "https://spectron.dev/errors/conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "A context with id 'acme-prod' already exists.",
  "instance": "/api/v1/contexts/acme-prod"
}
```

### 413 Payload Too Large

The uploaded file exceeds the per-Context size limit.

```json
{
  "type": "https://spectron.dev/errors/payload-too-large",
  "title": "Payload too large",
  "status": 413,
  "detail": "Uploaded file size (52.4 MB) exceeds the per-context limit (50 MB).",
  "instance": "/api/v1/acme-prod/documents"
}
```

### 422 Unprocessable Entity

The request is syntactically valid but semantically invalid.

```json
{
  "type": "https://spectron.dev/errors/unprocessable",
  "title": "Unprocessable entity",
  "status": 422,
  "detail": "Cannot bind context to namespace 'spectron' - this namespace is reserved for internal use.",
  "instance": "/api/v1/contexts/test"
}
```

### 429 Too Many Requests

The Context is blocked from LLM-backed work, or a per-minute rate limit was hit.

**Token budget:** returns **`429`** when **`enforcement_blocked`** is `true` on the Context (org credit enforcement on Cloud, or an operator-set block). A soft **`token_limit` breach alone does not reject** while `enforcement_blocked` is `false` - usage continues under pay-as-you-go.

Applies to LLM-backed paths including **`/chat`**, **`/facts?infer=full`**, **`/reflect`**, and **`/consolidate`** - not to read-only cache hits or direct lookups.

```json
{
  "type": "https://spectron.dev/errors/rate-limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Token enforcement is blocked for context 'acme-prod'.",
  "instance": "/api/v1/acme-prod/sessions/sess_abc/turns",
  "retry_after": null
}
```

### 500 Internal Server Error

An unexpected error occurred server-side. The `detail` field contains a request ID for support escalation.

```json
{
  "type": "https://spectron.dev/errors/internal",
  "title": "Internal server error",
  "status": 500,
  "detail": "An unexpected error occurred. Request ID: req_01HF3X...",
  "instance": "/api/v1/acme-prod/context"
}
```

### 503 Service Unavailable

The server is temporarily unable to handle requests. This covers a SurrealDB connection issue or startup, and also **load shedding**: a serving pod above its in-flight request cap sheds new work rather than queueing it. Every end-user operation can answer `503`; `GET /health` is exempt so a shedding pod still reports its own state.

A shed response carries a **`Retry-After`** header in delta-seconds alongside the usual error body:

```http
HTTP/1.1 503 Service Unavailable
Retry-After: 4
```

Wait the **larger** of your own backoff and the advertised delay before retrying. Jittered backoff alone typically starts below `Retry-After`, so a client that ignores the header can exhaust its attempts inside the window the server asked it to sit out, and every one of those attempts lands on a pod that is still saturated.

Only the delta-seconds form is emitted. A client that encounters the HTTP-date form should fall back to plain backoff.

> [!NOTE]
> Browser clients can read this header because the CORS policy names it in `Access-Control-Expose-Headers`. `Retry-After` is not one of the CORS-safelisted response headers, so a caller reading it from script in a different origin without that exposure would see `null` and quietly lose the delay.

## SDK exceptions

### Python SDK

| Exception | HTTP status | When |
|---|---|---|
| `SpectronAuthError` | 401 | Missing or invalid API key |
| `SpectronScopeError` | 403 | Scope floor or principal rejects the call |
| `SpectronNotFoundError` | 404 | Resource does not exist |
| `SpectronAPIError` | Other non-2xx | Generic API failure (includes 400, 409, 429, 5xx) |

```python
from surrealdb.memory import MemoryNotFoundError, MemoryAPIError

try:
    doc = await client.documents.get("document:nonexistent")
except MemoryNotFoundError as e:
    print(f"Document not found: {e.message}")
except MemoryAPIError as e:
    if e.status_code == 429:
        print(f"Rate limit exceeded: {e.body}")
```

See the SDK error sections for [Python](/docs/agent-memory/integrations/sdks/python.md#errors-and-retries) and [JavaScript](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md#errors-and-retries) for the full hierarchy and retry behaviour.

### JavaScript SDK

| Error class | HTTP status | When |
|---|---|---|
| `AuthError` | 401 | Missing or invalid API key |
| `ScopeError` | 403 | Scope floor or principal rejection |
| `NotFoundError` | 404 | Resource does not exist |
| `ValidationError` | 400, 422 | Invalid request payload |
| `RateLimitError` | 429 | Token or rate limit exceeded |
| `ServerError` | 500, 503 | Server-side failure |
| `ConnectionError` | - | Network failure or timeout |

```javascript
import { NotFoundError, RateLimitError } from "@surrealdb/memory";

try {
    const doc = await client.documents.get("document:nonexistent");
} catch (e) {
    if (e instanceof NotFoundError) {
        console.error("Document not found:", e.detail);
    } else if (e instanceof RateLimitError) {
        console.error("Rate limit exceeded");
    }
}
```

## Ingestion pipeline errors

Documents that fail during async processing do not return HTTP errors - they set the document `status` to `"failed"` and populate the `error` field:

```json
{
  "id": "document:0197d8f2...",
  "status": "failed",
  "error": "PDF extraction failed: file is encrypted and no password was provided.",
  "processing_started_at": "2026-05-12T14:22:11Z",
  "processing_completed_at": "2026-05-12T14:22:14Z"
}
```

Poll `GET /api/v1/{context_id}/documents/{id}` to check document status. See [Uploading documents](/docs/agent-memory/ingest/authoritative/uploading-documents.md) for retry guidance.

## MCP error handling

MCP tool failures use a different envelope from REST, but carry the **same HTTP status semantics**:

- **Operation failures** → `isError: true` tool result with `structuredContent.error.status` (404, 403, 429, etc.). The JSON-RPC transport returns HTTP 200.
- **Protocol faults** → JSON-RPC `error` (bad params, unknown tool).

Auth failures and missing Contexts are masked as **401**, never 404, so unauthenticated callers cannot enumerate Context ids. See [MCP tools](/docs/agent-memory/reference/mcp-tools.md#error-handling).

---

Source: https://surrealdb.com/docs/agent-memory/reference/glossary

# Glossary

Terminology used across SurrealDB Agent Memory docs.

SurrealDB Agent Memory’s architecture is described by **eight pillars** of agent memory and by **six experiential memory categories** (episodic turns plus identity, knowledge, context, instructions, uncertainty). Curated documents and conversational memory are **Authoritative** versus **Experiential** *pillars* - both are records in the **same** SurrealDB graph. See [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md) and [Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md).

## Attribute

A key-value property on an entity, representing a learned or asserted fact. Attributes are versioned: when a value changes, the old attribute is superseded (not deleted) and a new one is created, forming a supersession chain. Attributes carry `valid_from` and `valid_until` timestamps for temporal queries.

## Authoritative precedence

The precedence rule: **Authoritative** (curated) content takes precedence over **Experiential** assertions when they conflict. Disagreements are surfaced via **`uncertainty`** records and conflict indicators in state/profile responses - curated records are not silently overwritten. See [Authority when pillars meet](/docs/agent-memory/reasoning/authority-hierarchy.md).

## BM25

A keyword-based full-text ranking algorithm used in SurrealDB Agent Memory's hybrid retrieval. BM25 complements vector search by finding exact-keyword matches that semantic search might miss.

## Context

The unit of memory storage and configuration isolation in SurrealDB Agent Memory. Each Context is bound to a SurrealDB `(namespace, database)` pair and has its own API keys, model configuration, and data. Contexts cannot see each other's data. See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md).

## Decision trace

A **`decision_trace`** node records reconciliation outcomes: candidates considered, records created or superseded, confidence and trust, and links to parent traces. Ranked reads emit **`retrieval_trace`**; `/chat` and `/reflect` emit **`response_trace`**. Together they form the graph-resident audit model described in [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md).

## Dimensional scope

Hierarchical scope paths (for example `org/acme/user/alice/`) that partition data within a Context. Records store visibility as **OR-of-AND clauses** (each clause lists scope nodes that must all apply together). Queries and grants resolve which clauses a caller can see. See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md).

## Entity

A thing being tracked in **experiential** memory - a person, company, product, location, or other named object. Entities are identified by a `(type, normalised_name)` pair and carry attributes and relations. Entities can resolve to **knowledge** nodes via `resolves_to`.

## Extraction pipeline

The multi-stage process that converts conversational turns into structured experiential records. It operates in three stages: pattern matching (Stage 0), fast LLM (Stage 1), and strong LLM (Stage 2). The pipeline produces entities, attributes, relations, instructions, uncertainties, and invalidations.

## Extraction vocabulary

The entity names, attribute keys, relation labels, and action verbs a Context has already used, fed back into the extraction prompt so later runs reuse them instead of inventing near-synonyms. It is emergent rather than configured: there is no per-Context ontology to declare. Entity types are a separate, fixed vocabulary. See [Extraction vocabulary](/docs/agent-memory/tuning/ontology-grounding.md).

## Hybrid retrieval

A query mode that combines vector similarity (semantic) and BM25 (keyword) results using Reciprocal Rank Fusion (RRF). The `hybrid_graph` mode adds keyword graph density and typed knowledge graph scores to the fusion for improved precision.

## Instruction

A behavioural directive for the agent, stored separately from factual memory. Instructions are first-class records with `active` status and can be created, updated, and deactivated. They are included in profile responses and injected into the agent's system context.

## Authoritative pillar (curated knowledge)

The **Authoritative** pillar covers curated artefacts (`source.kind = "document"`, operator `upsert`): documents, policies, product data, FAQs, structured exports. Higher default **trust**. Same SurrealDB substrate as experiential records.

## Experiential pillar (conversational memory)

The **Experiential** pillar covers conversational input and derived facts (`turn`, `reflect`, `elaboration`, `consolidation`, …). It includes the **six [memory categories](/docs/agent-memory/mental-model/memory-categories.md)**: episodic session/turn data plus identity, knowledge, context, instructions, and uncertainty.

## MCP (Model Context Protocol)

An open protocol for connecting AI agents to tools and data sources. SurrealDB Agent Memory exposes an MCP server at `/mcp` with seven high-level tools for memory and knowledge operations.

## Memory category

A classification for **experiential** data (plus the raw **episodic** record): `identity`, `knowledge`, `context`, `instructions`, and **`uncertainty`** records, alongside the session/turn stream itself. Instructions and uncertainty use dedicated tables/paths. See [Memory categories](/docs/agent-memory/mental-model/memory-categories.md).

## Principal

The identity an API key acts as. A principal has a `kind` (`human`, `agent`, `service`, or `unknown`) that describes what it is, and a per-verb `grants` map that decides what it may do and where. Kind never affects access. See [Permissions and delegation](/docs/agent-memory/mental-model/contexts-and-scope.md#permissions-and-delegation).

## Profile

A structured snapshot of the current memory state for a given scope. A profile aggregates identity facts, dynamic context, preferences, and active instructions into a format suitable for injection into an agent's system prompt.

## Provenance

The ability to trace every record back to its **source** (turn, document, or trace). Records carry a structured **`source`** object (`kind`, `ref`, `span`, `trust`, `derived_from`, …). Some examples use `source_turn` as shorthand for conversational `source.ref`. See [Provenance and traceability](/docs/agent-memory/mental-model/provenance-and-traceability.md).

## Reconciliation

The **Reconciliation** pillar: integrating newly extracted information with existing beliefs - deduplication, supersession, scope coexistence, and **Authoritative** precedence. See [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md).

## Reflection

The **Reflection** pillar: on-demand synthesis (`POST /reflect`). Insights can be transient or persisted with `source.kind = "reflect"`.

## Relation

A typed edge between two entities, representing a fact about their connection. Relations carry a label (e.g. `works_at`, `relocated_to`) and temporal validity timestamps.

## `resolves_to`

A link from an experiential-memory entity to a **knowledge** node under the **Authoritative** pillar. When set, the agent can combine conversational context with curated facts for the same real-world thing.

## ResultKind

The **`source`** field on each `/query` hit: `entity`, `attribute`, `memory_chunk`, `chunk`, or `section`. Literal `infer: none` writes surface as **`memory_chunk`**; structured extraction surfaces as **`entity`** / **`attribute`**.

## Scope floor

The minimum scope paths that an API key must include in its requests. A key scoped to `org/acme` cannot make requests at `org/beta`. The server enforces this at the query level. Register paths with `spectron scopes create` before use.

## Semantic response cache

A per-Context cache keyed on the embedding of the query string. Before falling through to full retrieval, SurrealDB Agent Memory checks whether a semantically similar query has been answered recently and returns the cached result if the similarity exceeds the threshold.

## Session

A first-class conversation record in SurrealDB Agent Memory. Sessions group turns together and provide the scope context for all memory extracted from those turns. Sessions are durable records - they persist after the conversation ends and can be recalled, diffed, and reflected upon.

## Supersession

The versioned replacement of an attribute value. When new information contradicts a stored attribute, the old record is marked with `superseded_by` and `valid_until`, and a new record is created with `supersedes` and `valid_from`. Both remain in the database, forming a correction history.

## Turn

A single message in a session, with a role (`user`, `assistant`, `system`, or `tool`) and content. Turns are the input to the extraction pipeline and the provenance anchor for all memory produced from them.

## Uncertainty

Explicit records for gaps and clashes - part of the **Experiential** pillar’s six categories and central to the **Reconciliation** pillar when confidence is insufficient or provenances disagree.

## `valid_from` / `valid_until`

Temporal validity fields on attributes and relations. `valid_from` records when a fact became true; `valid_until` records when it ceased to be true. These fields enable point-in-time queries and historical analysis without deleting old data.

---

Source: https://surrealdb.com/docs/agent-memory/reference/management-api

# Management API

Contexts, keys, and operator lifecycle.

The management API provides control-plane operations: creating Contexts, managing API keys, and operator lifecycle. All management endpoints are under `/api/v1/` and require a management key.

## Authentication

Management endpoints require a **management** API key as a Bearer token:

```http
Authorization: Bearer <management-key>
```

Keys are printed once by bootstrap or minted via `spectrond keys generate-key` / management REST. They are distinct from per-Context end-user keys.

## Contexts

### List Contexts

```http
GET /api/v1/contexts
```

Returns all Contexts registered on this SurrealDB Agent Memory deployment.

**Response:**
```json
{
  "contexts": [
    {
      "id": "acme-prod",
      "namespace": "acme",
      "database": "prod",
      "config": {
        "token_limit": 1000000,
        "models": { "extraction": "openai/gpt-4o-mini" },
        "providers_configured": ["openai"]
      },
      "created_at": "2026-01-15T10:00:00Z"
    }
  ]
}
```

### Create a Context

```http
POST /api/v1/contexts/{context_id}
Content-Type: application/json

{
  "namespace": "acme",
  "database": "prod",
  "admin_external_id": "surreal-cloud:usr_01HF…",
  "admin_display_name": "Alice Admin",
  "config": {
    "token_limit": 1000000,
    "models": {
      "extraction": "openai/gpt-4o-mini",
      "response": "openai/gpt-4o"
    },
    "providers": {
      "openai": "sk-..."
    }
  }
}
```

Creates the Context, provisions the SurrealDB namespace and database, and applies schema migrations. Returns `201 Created`.

If provisioning fails **after** the database was created by this request, SurrealDB Agent Memory **discards that database** so a retry starts from a clean migration state. Pre-existing databases (operator-provided or left over from a crash) are never removed - only a database this request created is eligible for discard.

When **`admin_external_id`** is set, SurrealDB Agent Memory seeds an initial **`principal:admin`** bound to that opaque, issuer-qualified identity (for example `surreal-cloud:<user_id>`) with full `/*` grants - so a Cloud proxy can create a Context and immediately resolve the org owner without a follow-up principal call. **`admin_display_name`** is an optional human-readable label. Omit both fields to keep the generic admin principal.

**Response:**
```json
{
  "id": "acme-prod",
  "namespace": "acme",
  "database": "prod",
  "config": {
    "token_limit": 1000000,
    "models": { "extraction": "openai/gpt-4o-mini" },
    "providers_configured": ["openai"]
  },
  "created_at": "2026-05-12T10:00:00Z"
}
```

### Migration status

```http
GET /api/v1/migrations
```

Returns a fleet-wide view of per-Context schema catch-up after a binary upgrade: version histogram, Contexts still behind the embedded latest, and DLQ entries.

### Get a Context

```http
GET /api/v1/contexts/{context_id}
```

Returns the Context record. Provider API key values are replaced with a `providers_configured` summary (per-Context keys only). For the full provider and model catalogue - including providers reachable via deployment-wide keys - use **`GET /api/v1/{context_id}/providers`** on the data-plane API.

### Update Context config

```http
PATCH /api/v1/contexts/{context_id}
Content-Type: application/json

{
  "config": {
    "token_limit": 2000000,
    "models": {
      "reflection": "anthropic/claude-opus-4-7"
    }
  }
}
```

Deep-merges the provided config into the existing config. Unset fields are left unchanged.

Top-level fields such as **`enforcement_blocked`** (pay-as-you-go vs hard block - see [Configuration](/docs/agent-memory/reference/configuration.md#top-level-context-fields-outside-config)) may be set on the same `PATCH` body outside `config`.

### Delete a Context

```http
DELETE /api/v1/contexts/{context_id}
```

Drops the bound SurrealDB database (removing all authoritative knowledge and experiential memory data), deletes the associated API keys, and removes the control-plane registry entry. This is irreversible. Returns `204 No Content`.

## Grant verbs

SurrealDB Agent Memory authorises data-plane and admin operations with seven scoped verbs in `<noun>:<verb>` form:

| Verb | Purpose |
| --- | --- |
| `memory:read` | Read facts and document chunks/keywords within the granted region (recall, query, chat, document GET/list) |
| `memory:write` | Write facts and ingest documents (chunks inherit the uploader’s write region) |
| `memory:forget` | Soft or hard forget, entity delete, scoped subtree erasure |
| `scope:read` | List scope names visible within the grant (does not imply data access) |
| `scope:create` | Register new scope paths |
| `scope:delete` | Remove scope paths |
| `grant:manage` | Grant or revoke access on principals |

Documents are governed by **`memory:*`** verbs - there is no separate `document:*` namespace. Upload and reprocess require `memory:write`; document reads are per-row under `memory:read`.

List the machine-readable catalog (for grant pickers and validation):

```http
GET /api/v1/verbs
Authorization: Bearer <management-key>
```

Grant verbs use namespaced forms (`memory:read`, `memory:write`, …). Flat names such as `read` and `write` are rejected.

## Principals and external identity

Principals are the data-plane identity SurrealDB Agent Memory authorises. Create one with an optional **`external_id`** so upstream systems (SurrealDB Cloud, OIDC, on-prem IdP) can resolve “which principal is this user?” without a side mapping table:

```http
POST /api/v1/contexts/{context_id}/principals
Content-Type: application/json

{
  "display_name": "Alice",
  "external_id": "surreal-cloud:usr_01HF…",
  "grants": {
    "memory:read": [{ "org": "acme", "user": "alice" }],
    "memory:write": [{ "org": "acme", "user": "alice" }]
  }
}
```

When **`external_id`** is present, create is **create-or-get**: a repeat call with the same issuer-qualified id returns the existing principal unchanged (even if `display_name` differs). Without `external_id`, each call mints a fresh principal. A duplicate **`display_name`** when no matching `external_id` applies returns **`409 Conflict`**. The value is opaque to SurrealDB Agent Memory - qualify it with your issuer prefix.

### Reserved built-in principals

Every Context seeds two reserved principals:

| Principal | Policy |
| --- | --- |
| **`principal:system`** | Fully **immutable** - update, grant change, and delete all return **`403`**. Background jobs stamp provenance against this identity. |
| **`principal:admin`** | **Undeletable** (delete returns **`403`**) but otherwise mutable - rename, re-kind, and grant changes are allowed. Recovery from a broken admin grant map uses the control-plane management key, which is independent of this data-plane principal. |

Grant changes and key minting for ordinary principals enforce **attenuation only** at the store layer: key grants must be a subset of the bound principal's grants on **every** mint path (management nested mint and self-service **`POST /keys`**).

## API keys

Data-plane keys are always **bound to a principal**. Mint them under that principal on every path (management nested mint, Cloud broker, or self-service `POST /{ctx}/keys`). Keys with no principal binding are rejected with **`401`**.

### Mint a key (principal-nested)

```http
POST /api/v1/contexts/{context_id}/principals/{principal_id}/keys/{key_name}?ttl_seconds=2592000
Content-Type: application/json

{
  "grants": {
    "memory:read": [{ "org": "acme", "agent": "planner" }]
  }
}
```

Optional **`grants`** in the body **attenuate** (narrow) the principal’s grants per verb (intersect only - widening returns `400`). Omit grants to inherit the principal’s full region. The secret is returned once.

Duplicate key names within a Context return **`409 Conflict`** (names are unique per Context, not per principal).

### List keys for a principal

```http
GET /api/v1/contexts/{context_id}/principals/{principal_id}/keys
```

Each entry includes **`id`**, **`name`**, and optional **`grants`** - the per-key attenuating grant map. When **`grants`** is omitted, the key inherits the principal's grants wholesale; when present, it only narrows per verb.

### Rotate or delete (principal-nested)

```http
POST   /api/v1/contexts/{context_id}/principals/{principal_id}/keys/{key_name}/rotate?ttl_seconds=2592000
DELETE /api/v1/contexts/{context_id}/principals/{principal_id}/keys/{key_name}
```

On nested rotate/delete, a key that does not exist, is unbound, or belongs to a **different** principal returns **`404`** (no cross-principal leakage).

### Context-wide operator views

Flat list endpoints remain for operators auditing all keys in a Context:

```http
GET    /api/v1/contexts/{context_id}/keys
DELETE /api/v1/contexts/{context_id}/keys/{key_name}
POST   /api/v1/contexts/{context_id}/keys/{key_name}/rotate?ttl_seconds=2592000
```

List responses include optional **`grants`** on each key (same semantics as the principal-nested list).

The secret is never returned after creation except on mint and rotate.

**Principal types:**

| Principal | Capabilities |
|---|---|
| `agent` | Create sessions, add turns, recall context, search knowledge. Cannot persist reflections or manage keys. |
| `supervisor` | All agent capabilities plus persisting reflections within the supervisor’s grant region. |

### Cloud-brokered access tokens

For SurrealDB Studio and other Cloud proxies that must mint a **short-lived, member-scoped** key in one round-trip without handing every member a management key:

```http
POST /api/v1/contexts/{context_id}/access-tokens
Content-Type: application/json

{
  "external_id": "surreal-cloud:usr_01HF…",
  "display_name": "Alice",
  "ttl_seconds": 3600,
  "grants": {
    "memory:read": [{ "org": "acme", "user": "alice" }],
    "memory:write": [{ "org": "acme", "user": "alice" }]
  }
}
```

Composes **create-or-get principal by `external_id`**, optional grant re-assertion, and **mint a bound key**. **`ttl_seconds` is required** - brokered tokens cannot be unbounded. Re-broker before expiry; there are no refresh tokens. The plaintext secret is returned once.

Member-facing **self-service** key management (after a brokered or admin-minted key is held) lives on the [REST API](/docs/agent-memory/reference/rest-api.md#self-service-keys-and-me) - not the management API. **`issue_access_token`** (Cloud broker path) emits a server warning when the caller sends no grants and the resulting principal is still entirely zero-grant - diagnostic only, behaviour unchanged.

## Knowledge upload (management-only)

Document ingestion requires a management key or an agent key with explicit upload capability. See [Uploading documents](/docs/agent-memory/ingest/authoritative/uploading-documents.md) for the full upload API.

```http
POST /api/v1/{context_id}/documents
Content-Type: multipart/form-data
Authorization: Bearer <key>

file=@document.pdf
title=Product Catalogue
scope[org]=acme
```

## Lifecycle operations

### Trigger decay pass

Runs the lifecycle decay pass immediately, expiring `context`-category memories past their TTL and pruning expired semantic cache entries:

```http
POST /api/v1/contexts/{context_id}/lifecycle/decay
```

### Force reindex

Triggers re-embedding and re-indexing of all chunks in a Context (e.g. after changing the embedding model):

```http
POST /api/v1/contexts/{context_id}/lifecycle/reindex
```

This is a long-running operation. Poll the returned job ID to track progress.

## Pagination

List endpoints page with `limit`, `cursor` and `count`, and return the collection beside a `page` block. The contract is identical to the end-user surface, so see [Pagination](/docs/agent-memory/reference/rest-api.md#pagination) for the parameters, the response shape, and the rule about not terminating on a short page.

```http
GET /api/v1/contexts/{context_id}/keys?limit=50
```

**Response:**
```json
{
  "keys": [
    { "id": "cak_01HF...", "name": "primary", "grants": ["memory:write"] }
  ],
  "page": { "hasMore": false }
}
```

---

Source: https://surrealdb.com/docs/agent-memory/reference/mcp-tools

# MCP tools

Tool payloads and ACL alignment.

SurrealDB Agent Memory's MCP server exposes **seven** tools at `/mcp`: `remember`, `recall`, `context`, `reflect`, `forget`, `upload`, and `inspect`. Each maps to one or more REST endpoints and uses the same authentication and scope semantics.

> [!NOTE]
> Older docs and some third-party snippets used names like `memory_store` or `knowledge_search`. Those prefixes are gone - use the short names above. Authoritative schemas live in the SurrealDB Agent Memory server (`spectron-user-api` MCP tools module).

> [!NOTE]
> REST responses use **camelCase** (`queryMs`, `traceId`, `trace.traceId`). **Scope selectors** on the wire use DNF (`ScopeSets`): writes take **`scope`** / **`scopes`**, reads take **`lens`** - an OR of conjunctive slash-path clauses. Register paths with `spectron scopes create` before use. See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md#wire-format-scopesets).

## Authentication

All tools use **`Authorization: Bearer`** authentication (same as REST). Pass the token in the MCP configuration:

```json
{
  "mcpServers": {
    "spectron": {
      "url": "https://spectron.example.com/mcp",
      "headers": {
        "Authorization": "Bearer sk-...",
        "X-Spectron-Context": "acme-prod"
      }
    }
  }
}
```

`X-Spectron-Context` in MCP client configs is a convenience for templating. Each API key is bound to exactly one Context, so **`context_id` is optional** on every tool: when omitted, the server resolves the Context from the bearer key. You may still pass `context_id` explicitly; a value that does not match the key's bound Context is rejected with **`401`** (masked, not `404`).

## Scope handling

Scope is layered on each tool call:

1. **Grant region** (from the API key's principal) - enforced server-side; cannot be widened past what the key allows.
2. **Per-call `scope` / `lens` argument** - DNF selector narrowing the operation within the grant (`scope`/`scopes` on writes, `lens` on `recall` / `context`).

Register paths with `spectron scopes create` before first use.

---

## `remember`

Persist a new fact from text. Reconciles against existing memory (supersession, uncertainty).

**Requires:** `memory:write` over the target scope.

**Input:**
```jsonc
{
  "text": "I just got promoted to CTO.",   // required
  "session_id": "sess_abc123",            // optional - creates a session if omitted
  "scope": [["org/acme/user/alice"]],     // optional DNF write selector
  "labels": ["source=onboarding"],        // optional
  "infer": "full"                         // full | preview | none (default full)
}
```

**Output:** Structured diff (entities, attributes, relations, instructions, uncertainties, corrections) plus a stand-in `trace_id` (the facts path returns the turn id today).

**Underlying REST endpoint:** `POST /api/v1/{context_id}/facts`

---

## `recall`

Search the unified substrate (experiential facts + document passages) and return ranked hits.

**Requires:** `memory:read`.

**Input:**
```jsonc
{
  "query": "What does Alice do at Acme?",  // required
  "k": 10,                                  // optional, default 10 (max 50)
  "mode": "hybrid",                         // vector | bm25 | graph | hybrid
  "lens": [["org/acme"]],                   // optional DNF read lens
  "labels": ["project=support"]             // optional key=value filter
}
```

**Output:** Ranked hits with scores and source kinds, plus `trace_id`.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/query`

---

## `context`

Assemble a markdown context block (profile + preferences + relevant facts) ready for system-prompt injection.

**Requires:** `memory:read`.

**Input:**
```jsonc
{
  "query": "What does Alice do at Acme?",
  "lens": [["org/acme"]],
  "labels": ["org=acme"]
}
```

**Output:** Markdown context string plus `trace_id`.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/context`

---

## `reflect`

Synthesise patterns across stored memory. With `persist: true`, writes the insight as new facts (`source.kind = "reflect"`).

**Requires:** `memory:read`; `persist: true` also needs write access in the caller's region.

**Input:**
```jsonc
{
  "query": "What recurring complaints have customers raised this month?",
  "persist": true   // optional, default false
}
```

**Output:** Reflection text, evidence refs, optional `persisted_attributes`, and `trace_id`.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/reflect`

---

## `forget`

Stop believing something. Soft-deletes matching attributes (`valid_until`). `purge: true` also removes supersession history (right-to-be-forgotten).

**Requires:** `memory:forget` (not the same as `memory:write`).

**Input:**
```jsonc
{
  "query": "anything about my previous role at the old company",
  "purge": false
}
```

**Output:** Soft-delete count. `trace_id` may be empty on this path.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/forget`

---

## `upload`

Upload a document (base64 bytes) into the knowledge layer. Processing is asynchronous.

**Requires:** `memory:write`.

**Input:**
```jsonc
{
  "bytes_base64": "<RFC 4648 bytes>",  // required
  "title": "Returns Policy",
  "source": "returns.pdf",
  "mime_type": "application/pdf",
  "filename": "returns.pdf",
  "scopes": [["org/acme/team/eng"]],   // optional - or `scope` alias
  "labels": ["team=eng"]
}
```

**Output:**
```jsonc
{
  "id": "doc:01hx9…",
  "status": "queued",
  "content_hash": "blake3:…",
  "deduplicated": false,
  "version": 1
}
```

Poll status with **`inspect`** (`document:<id>`) or REST `GET .../documents/{id}`.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/documents`

---

## `inspect`

Fetch a typed row by reference for explainability.

**Requires:** `memory:read`.

**Input:**
```jsonc
{
  "ref": "entity:Person/alice"   // entity:<Type>/<Name> | trace:<id> | document:<id>
}
```

**Output:** Entity (with attributes / supersession), flat trace record, or document metadata - matching the underlying GET.

**Underlying REST endpoints:**
- `GET /api/v1/{context_id}/entities/{type}/{name}`
- `GET /api/v1/{context_id}/traces/{id}`
- `GET /api/v1/{context_id}/documents/{id}`

---

## Error handling

Operation failures return an **`isError: true`** tool result (HTTP transport stays **200**), not a JSON-RPC protocol error. The result includes the same HTTP status the REST API would return:

```jsonc
{
  "isError": true,
  "structuredContent": {
    "error": {
      "status": 404,
      "message": "Document not found"
    }
  }
}
```

| Status | Meaning | Agent action |
| --- | --- | --- |
| `401` | Missing or invalid key | Fix credentials; missing Context is masked as 401 (not 404) |
| `403` | Grant denied | Narrow scope or request access |
| `404` | Resource not found | Adjust query or id |
| `429` | Rate limit or `enforcement_blocked` | Back off and retry |
| `500` | Server fault | Retry; details are redacted |

JSON-RPC **`error`** responses are reserved for **protocol faults** only - malformed params (`-32602`), unknown tool (`-32601`), oversized `k`, and similar.

Denied operations emit the same **`authz.denied`** audit events and error metrics as REST.

---

## Tool ACL summary

| Tool | Typical grant |
|---|---|
| `remember` | `memory:write` |
| `recall` | `memory:read` |
| `context` | `memory:read` |
| `reflect` (`persist: false`) | `memory:read` |
| `reflect` (`persist: true`) | `memory:read` + write region |
| `forget` | `memory:forget` |
| `upload` | `memory:write` |
| `inspect` | `memory:read` |

## Streaming

Most tools are synchronous - the full response is returned in a single payload.

`upload` returns immediately with `queued` status; ingestion continues on the worker. `reflect` with `persist: true` on a large corpus may take several seconds (no MCP progress notifications today).

---

Source: https://surrealdb.com/docs/agent-memory/reference/rest-api

# REST API

End-user HTTP endpoints on the unified substrate. For SurrealDB Agent Memory.

SurrealDB Agent Memory exposes two HTTP surfaces from the **api** role:

| Surface | Prefix | Authentication |
| --- | --- | --- |
| **End-user API** | `/api/v1/{context_id}/...` | Context API key as `Authorization: Bearer` |
| **Management API** | `/api/v1/...` (no context id in path) | Management key as `Authorization: Bearer` |

Both surfaces share the same host and port (default `http://localhost:9090`). **MCP** is served at `/mcp` on the same listener.

See [Management API](/docs/agent-memory/reference/management-api.md) for Context and key lifecycle.

## Authentication

Every request must include your API key as a Bearer token:

```http
Authorization: Bearer <your-key>
```

Optional idempotent writes accept:

```http
Idempotency-Key: <opaque-string>
```

Optional **delegated identity** on a single request (depth 1, intersected with the target principal’s grants):

```http
X-Spectron-On-Behalf-Of: <principal-id>
```

Use this when an agent acts for another principal - for example a supervisor tool calling on behalf of a user. The effective authority is always the **intersection** of the caller’s key, the caller’s grants, and the target’s grants; delegation cannot widen access.

The same `Idempotency-Key` (scoped to context + principal) within 24 hours returns the stored response. A different body with the same key, or a duplicate request while the first is still in flight, returns `409 Conflict`. Failed writes release the reservation so a retry can re-run.

## Base URL

```text
https://<host>/api/v1/{context_id}
```

Replace `{context_id}` with the identifier you chose at bootstrap (for example `dev` or `docs_test`).

## Scope selectors on the wire

Scope on requests is a **DNF selector** (`ScopeSets`): an OR of conjunctive clauses, each clause an AND of hierarchical slash paths (for example `org/acme/user/alice`; a trailing `/` is optional).

| Direction | Field | Shape |
| --- | --- | --- |
| Write (facts, sessions, uploads) | **`scopes`** | `[[\"org/acme/user/alice\"]]` - legacy alias **`scope`** still accepted |
| Read (`/query`, `/context`) | **`lens`** | Same DNF shape; filters by involvement within the caller’s grant |

A flat `["a", "b"]` means **a OR b**. For **a AND b** in one clause, nest: `[["a", "b"]]`. Single-path tagging is unchanged in practice - `[["org/acme/user/alice"]]` or a bare string.

Register paths before first use:

```bash
spectron scopes create org/acme
spectron scopes create org/acme/user/alice
```

See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md).

JSON responses use **camelCase** field names (`queryMs`, `sessionId`, `traceId`, …).

## Pagination

Every list surface pages the same way. Three optional query parameters, uniform on each listing:

| Parameter | Default | Meaning |
| --- | --- | --- |
| `limit` | `100` | Page size. A value above the cap of `500` is clamped rather than rejected. An explicit `limit=0` is a `400`, because it asks for no rows and clamping it up would answer a different request. |
| `cursor` | first page | Opaque continuation token from the previous response. |
| `count` | `false` | Also compute `totalSize`. |

The collection comes back beside a `page` block:

```json
{
  "entities": [ ... ],
  "page": {
    "nextCursor": "eyJ2IjoxLCJrIjp7ImsiOiJ0eXBlX25hbWUi…",
    "hasMore": true,
    "totalSize": 40312
  }
}
```

`nextCursor` and `totalSize` are omitted when absent, so the last page is `{"hasMore": false}`.

> [!IMPORTANT]
> Walk a listing by following `nextCursor` until it is absent. **Never terminate on a short page.** A listing that is bounded in the database and then filtered for visibility, such as `/scopes` or `/keys`, can return fewer rows than `limit` while further pages remain.

Paging is keyset rather than offset: a cursor names the last returned row's position in the listing's sort order, so any page costs the same as the first and the walk stays stable under concurrent writes. The record id is the last component of every ordering, which is what makes a position unambiguous within a run of equal sort values.

A cursor is unsigned and carries no identity. The scope predicate is resolved from the caller's credentials on every request, independently of the cursor, so replaying another caller's cursor grants nothing. What a cursor does carry is a fingerprint of the request's filters, so reusing one under changed filters returns `400` rather than silently resuming inside a different result set.

`count=true` costs a full count of the filtered set, which is the unbounded read that pagination exists to avoid, so it never rides along with a default page fetch. `totalSize` counts the whole listing, not the rows remaining after the cursor.

Over gRPC the same three inputs are `page_size`, `page_token` and `want_total`, and responses carry `next_page_token` and an optional `total_size`. There is no `has_more`, because an absent token already means the last page.

## Unified verbs

| Verb | Method and path | Purpose |
| --- | --- | --- |
| **Remember** | `POST /api/v1/{ctx}/facts` | Single fact or turn; `infer` controls extraction |
| **Remember (bulk)** | `POST /api/v1/{ctx}/facts/batch` | Whole conversation in one request |
| **Upload** | `POST /api/v1/{ctx}/documents` | Byte ingest; async parse → chunk → extract → embed |
| **Recall** | `POST /api/v1/{ctx}/query` | Unified four-tier router over facts and passages |
| **Chat** | `POST /api/v1/{ctx}/chat` | SurrealDB Agent Memory acting as the agent (composed over facts + query + LLM) |

Document-only retrieval remains under `POST /api/v1/{ctx}/documents/query`. Formatted prompt blocks use `POST /api/v1/{ctx}/context`.

---

## Facts (remember)

### Single fact

```http
POST /api/v1/{context_id}/facts
Authorization: Bearer <key>
Content-Type: application/json

{
  "text": "King Charles III became head of state of the United Kingdom in September 2022.",
  "infer": "full",
  "scopes": [["org/acme/user/alice"]],
  "observed_at": "2022-09-08T12:00:00Z"
}
```

`infer` values: `full` (LLM extraction, default), `triples` (caller-supplied structured triples), `preview` (dry run), `none` (literal store).

Optional **`observed_at`** (RFC 3339) sets the **known time** (`created_at`) of derived facts. Use when ingesting serial narrative - novels, episodes, franchise films - so **`asOf`** recall matches how far the user has read or watched, not when you bulk-imported the corpus. In the example above, this would be useful to replay a narrative in which a user or character is unaware of the fact until the date indicated. See [Narrative playback and spoiler safety](/docs/agent-memory/reasoning/temporal-validity.md#narrative-playback-and-spoiler-safety). Omitted means wall-clock ingest time.

**Response (`infer: full`):** nested **`extraction`** (entities, attributes, relations, …), plus `sessionId`, `turnId`, and `mode`. **`infer: none`** returns `chunkId`, `sessionId`, and `turnId` for the literal memory chunk.

When supplying triples or structured entities, `memory_category` on entities, attributes, and relations must be one of **`identity`**, **`knowledge`**, or **`context`**. Invalid values are rejected with `400 Bad Request`. Instructions and uncertainties use their own tables; episodic material lives on sessions and turns.

### Batch conversation

Preferred path for multi-turn ingest (replaces per-turn `POST .../sessions/{id}/turns` for new integrations):

```http
POST /api/v1/{context_id}/facts/batch
Authorization: Bearer <key>
Content-Type: application/json
Idempotency-Key: conv-01HF...

{
  "messages": [
    { "role": "user", "content": "I was just promoted to CTO.", "ts": "2026-05-12T10:00:00Z" },
    { "role": "assistant", "content": "Congratulations!", "ts": "2026-05-12T10:00:05Z" }
  ],
  "scopes": [["org/acme/user/alice"]],
  "session_id": "sess_01HF...",
  "extract": "whole_conversation"
}
```

`extract` controls batch extraction strategy: **`whole_conversation`** (default) runs one LLM pass over the full transcript; **`per_message`** runs extraction once per message.

Returns **`extractions`** (one extraction result per message or one for the whole conversation), **`sessionId`**, and **`turnIds`**.

---

## Sessions (episodic introspection)

Sessions remain for browsing transcripts and session-scoped state. **Writes** should use `/facts` or `/facts/batch`.

```http
POST   /api/v1/{context_id}/sessions
GET    /api/v1/{context_id}/sessions/{session_id}
DELETE /api/v1/{context_id}/sessions/{session_id}
GET    /api/v1/{context_id}/sessions/{session_id}/turns?limit=100&offset=0
GET    /api/v1/{context_id}/sessions/{session_id}/context
```

`list_turns` paginates: **`limit`** defaults to 100 (max 500); **`offset`** skips rows. The CLI `sessions show` / `transcript` commands page through turns automatically.

Create session body:

```json
{
  "scopes": [["org/acme/user/alice"]],
  "metadata": { "source": "chat-ui" }
}
```

---

## Recall and context

### Ranked hits (`/query`)

```http
POST /api/v1/{context_id}/query
Content-Type: application/json

{
  "query": "What is Alice's role?",
  "k": 10,
  "lens": [["org/acme/user/alice"]],
  "labels": ["subject=alice"],
  "scope_view": "strict",
  "asOf": "2025-02-01T00:00:00Z",
  "source": "ingest-cli"
}
```

| Field | Purpose |
| --- | --- |
| `lens` | DNF scope selector narrowing the read region (clamped to the key’s grant). |
| `labels` | Descriptive `key=value` tags that **filter** hits within the allowed region - labels never widen access on their own. |
| `scope_view` | How broadly to fold results within the grant: `strict` (default - caller’s region only), `crossTeam` (cross-principal shared reads), or `merged` (same-fact records at narrower scopes). **`crossTeam`** and **`merged`** resolve like **`strict`**. None widen past the caller’s grant. |
| `include` | Which result families to return: `facts` (entities and attributes) and/or `passages` (document chunks and sections). Default: both. Omitted or empty means no narrowing. Filters the **response** only - retrieval and trace reinforcement still see the full candidate set. |
| `mode` | Closed set: `hybrid`, `vector`, `bm25`, or `graph`. Invalid values return `400`. |
| `asOf` | Known-time filter - recall what the system would have believed at this instant (supersession walk by `created_at` for attributes; relations are gated by `created_at` and open validity). Omitted means current state. |
| `includeDuplicates` | When `false` (default), near-duplicate document passages and stored conversation text are excluded from fused recall so the same wording does not occupy multiple ranks. Set `true` to include them (parity with `/documents/query`). |
| `source` | Free-form label recorded on the retrieval trace for audit replay; does not affect ranking. |

**`k`** on `/query` and document query default to **10** and are capped at **50** (operator-tunable downward only - see [Configuration](/docs/agent-memory/reference/configuration.md#request-and-list-limits)). The internal retrieval pool is fixed separately from `k`.

Response includes `tier` (`direct`, `cache`, `hybrid`, or `full_context`), `hits` (each with **`source`** as a **`ResultKind`**: `entity`, `attribute`, `memory_chunk`, `chunk`, or `section`; optional **`occurredAt`** - known time of the underlying row for temporal resolution), optional **`contextHits`** (same-section siblings from section expansion - not counted against `k`), **`queryMs`**, and inline **`trace`** (with **`traceId`** for `GET .../traces/{id}`). See [Recalling memories](/docs/agent-memory/retrieve/recall.md#section-expansion).

### Formatted context block (`/context`)

```http
POST /api/v1/{context_id}/context
Content-Type: application/json

{
  "query": "What is Alice's role?",
  "k": 10,
  "lens": [["org/acme/user/alice"]]
}
```

Returns a string suitable for LLM system-prompt injection.

### Chat (SSE optional)

```http
POST /api/v1/{context_id}/chat
Content-Type: application/json

{
  "message": "Summarise what you know about me",
  "scopes": [["org/acme/user/alice"]],
  "session_id": "sess_01HF..."
}
```

Use `Accept: text/event-stream` for streaming replies.

The response (and the SSE **`done`** frame) includes a **`citations`** list when the model cites retrieved sources inline with `[S1]`-style markers. Each citation resolves a marker to its source row (`id`, `kind`, `snippet`, `score`, optional `occurredAt`, optional **`role`**). For document passages it also includes **`documentTitle`** and **`positionPercent`** - an approximate percentage through the document, not a raw chunk id. When the source is a prior assistant reply, **`role`** is `"assistant"`. Citations index both ranked `hits` and section-expansion `contextHits`. See [Chat sessions](/docs/agent-memory/sessions/chat-sessions.md) for transcript-window and ranking behaviour.

---

## State, profile, entities

```http
POST /api/v1/{context_id}/state
GET  /api/v1/{context_id}/profile
GET  /api/v1/{context_id}/entities
GET  /api/v1/{context_id}/entities/{entity_type}/{entity_name}
GET  /api/v1/{context_id}/entities/{entity_type}/{entity_name}/history/{key}
DELETE /api/v1/{context_id}/entities/{entity_type}/{entity_name}
```

Tri-temporal reads accept `asOf`, `atInstant`, `validFrom`, and `validUntil` query parameters on entity GETs.

---

## Reflection, lifecycle, maintenance

```http
POST /api/v1/{context_id}/reflect
POST /api/v1/{context_id}/forget
POST /api/v1/{context_id}/lifecycle/expire
POST /api/v1/{context_id}/lifecycle/decay
POST /api/v1/{context_id}/elaborate
POST /api/v1/{context_id}/consolidate
POST /api/v1/{context_id}/fsck
```

`reflect` runs on-demand synthesis and returns **`traceId`** (correlate with `GET .../traces/{id}`); `persist` on the body stores lower-trust derived facts when permitted by key type.

**`POST /consolidate`** is **caller-scoped** when invoked on the data plane: facts are pooled only from the caller's **`memory:read`** region, and persistence requires **`memory:write`** over each scope group. A readable-but-not-writable group returns a **dry-run preview** in the response but is not committed. The background scheduler pass is unchanged and pools every scope. An empty read region denies all pooling (fail-closed).

**`POST /forget`** accepts a natural-language **`query`**, optional **`purge: true`** for permanent erasure, and optional **`dryRun: true`** to return a match count without expiring anything (see [Forgetting memories](/docs/agent-memory/operations/forget.md)).

`consolidate` responses reference per-record **`DecisionKind`** values: `create`, `update`, or `supersede`.

---

## Documents (upload)

```http
POST   /api/v1/{context_id}/documents
GET    /api/v1/{context_id}/documents
GET    /api/v1/{context_id}/documents/{id}
GET    /api/v1/{context_id}/documents/{id}/raw
GET    /api/v1/{context_id}/documents/{id}/chunks
DELETE /api/v1/{context_id}/documents/{id}
POST   /api/v1/{context_id}/documents/query
GET    /api/v1/{context_id}/documents/keywords
POST   /api/v1/{context_id}/documents/recompute-links
```

Upload uses `multipart/form-data`; processing is asynchronous via the job queue.

The `metadata` part is JSON. Optional fields:

| Field | Purpose |
| --- | --- |
| `title`, `source`, `mimeType`, `filename` | Display and provenance |
| `scopes` | DNF scope selector **narrower than** the caller's `memory:write` region - same semantics as `remember`. Out-of-region scope returns **`403`**. Omit to tag the document with the caller's full write region. Legacy alias **`scope`**. |
| `labels` | Descriptive `key=value` tags stamped on the document and its chunks (not on reconciled graph rows). Keys must not start with `_` (`400`); count caps return **`409`**. |
| `observedAt` | Optional RFC 3339 known time for derived facts - stamp each page or episode on a narrative timeline so `asOf` queries stay spoiler-safe ([cookbook](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md)). Omitted means facts date to ingest time. |

```http
POST /api/v1/{context_id}/documents
Content-Type: multipart/form-data

file=<binary>
metadata={"title":"Returns Policy","scopes":[["org/acme/team/eng"]],"labels":["team=eng"],"observedAt":"2024-01-15T00:00:00Z"}
```

### List documents

```http
GET /api/v1/{context_id}/documents?pageSize=20&mimeType=application/pdf
```

Query parameters use **camelCase** (`mimeType`). Page with `limit` and `cursor` as described under [Pagination](#pagination); the older zero-indexed `page` and `pageSize` parameters are still accepted here for existing callers.

### Document query (`/documents/query`)

Document-only ranked retrieval accepts the same **`mode`** values as unified recall (`vector`, `bm25`, `hybrid`, `hybrid_graph`). Optional flags **`useHyde`**, **`decomposeQuery`**, and **`useReranker`** are honoured when configured: HyDE embeds a hypothetical answer for the vector leg; decomposition fans out sub-questions; reranking rescores top candidates with a cross-encoder when `SPECTRON_RERANKER_URL` is set (see [Configuration](/docs/agent-memory/reference/configuration.md#reranker-optional)). All degrade gracefully when the backing provider is absent.

When using graph-density reranking, optional **`graph_edges`** selects which structural signals contribute. Each value must be a recognised edge kind - unknown values return `400 Bad Request`:

| Value | Signal |
| --- | --- |
| `knowledge_has_keyword` | Keyword overlap between documents |
| `section_match` | Section-heading similarity |
| `document_link` | Cross-document link density |
| `document_summary` | Document-level summary similarity |

Responses may include `hybrid_graph` on individual hits when several signals combine; that label describes evidence in the result, not an input filter.

---

## Traces and audit

```http
GET /api/v1/{context_id}/traces
GET /api/v1/{context_id}/traces/stats
GET /api/v1/{context_id}/traces/{trace_id}
GET /api/v1/{context_id}/inspect
GET /api/v1/{context_id}/audit
```

**`/traces/stats`** accepts `scope[…]`, `since`, and **`windowHours`** (rolling window for operational counters). The response includes aggregate counts and health signals for retrieval, decision, and response traces in the window.

**Scope gate:** callers with **`grant:manage`** see Context-wide aggregates. Scope-enforcing keys receive a **zeroed** stats view for regions outside their grant, mirroring trace listing so that cross-principal usage metadata does not leak.

---

## Self-service keys and `/me`

Members who hold a brokered or admin-minted key can manage their own keys on the data-plane API without a management credential:

```http
GET    /api/v1/{context_id}/me
POST   /api/v1/{context_id}/keys
GET    /api/v1/{context_id}/keys
DELETE /api/v1/{context_id}/keys/{name}
POST   /api/v1/{context_id}/keys/{name}/rotate
```

**`GET /me`** returns caller introspection: principal identity, principal grants, the key's attenuating grants, effective grants, and `delegatedPrincipalId` when delegating. Ungated - members deliberately lack `grant:manage`, so admin-side principal endpoints are not sufficient.

**`GET /keys`** returns **only the caller's authenticating key**, unless the caller is a **context administrator** (`grant:manage` over `/`), in which case all keys are listed. **`DELETE /keys/{name}`** and **`POST /keys/{name}/rotate`** apply the same rule: ordinary keys may manage **only themselves**; a mismatched or sibling key returns **`404`** (no cross-key enumeration).

**`POST /keys`** mints a key for the caller's **own** principal. Optional **`grants`** only **attenuate** (narrow) per verb; widening returns `400`. Returns the plaintext secret once.

Per-Context policy (see [Configuration](/docs/agent-memory/reference/configuration.md)):

- **`allow_self_service_keys`** - set `false` to require Cloud-brokered keys only.
- **`max_token_ttl_seconds`** - server-side TTL clamp on every mint path (management, broker, self-service).

See [Key policy](/docs/agent-memory/reference/configuration.md#key-policy).

**`/audit`** returns **`rows`** with a **`kind`** filter (`decision`, `retrieval`, or `response`) and **`traceId`** linking back to graph-resident traces. Query with `?kind=decision&limit=50`.

---

## Scopes and principals

```http
GET  /api/v1/{context_id}/scopes
POST /api/v1/{context_id}/scopes
POST /api/v1/{context_id}/scopes/forget
POST /api/v1/{context_id}/scope-grants   # returns 501 - cross-principal sharing surface locked

GET  /api/v1/verbs                       # management API - grant verb catalog

GET  /api/v1/{context_id}/principals
POST /api/v1/{context_id}/principals
GET  /api/v1/{context_id}/principals/{principal_id}/effective
```

See [Permissions and delegation](/docs/agent-memory/mental-model/contexts-and-scope.md#permissions-and-delegation).

---

## Health

```http
GET /api/v1/health
```

Unauthenticated liveness check.

---

## Connectors

Not available in the current release.

---

## Errors

All errors follow [RFC 7807 problem details](/docs/agent-memory/reference/errors.md).

---

Source: https://surrealdb.com/docs/agent-memory/reference/sdk-javascript

# JavaScript SDK reference

Package layout and configuration for @surrealdb/memory.

The `@surrealdb/memory` package is the JavaScript and TypeScript client for SurrealDB Agent Memory. This page covers installing it and configuring a client.

| Item | Value |
| --- | --- |
| npm package | `@surrealdb/memory` |
| Source | [`surrealdb.js/packages/spectron`](https://github.com/surrealdb/surrealdb.js/tree/main/packages/spectron) |
| OpenAPI input | `packages/spectron/spec/openapi.json` |

## Install

```bash
npm install @surrealdb/memory
```

## Configuration

```typescript
import { AgentMemory } from "@surrealdb/memory";

const client = new AgentMemory({
  endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
  context: "acme-prod",
  apiKey: process.env.AGENT_MEMORY_API_KEY!,
  timeout: 30000,
  maxRetries: 3,
});
```

Uses **`Authorization: Bearer`** header authentication.

## Memory verbs (REST mapping)

| Method | HTTP |
| --- | --- |
| `remember(...)` | `POST /api/v1/{ctx}/facts` |
| `rememberMany(...)` | `POST /api/v1/{ctx}/facts/batch` |
| `recall(...)` | `POST /api/v1/{ctx}/query` |
| `forget(...)` | `POST /api/v1/{ctx}/forget` |
| `chat(...)` | `POST /api/v1/{ctx}/chat` (SSE when `stream: true`) |
| `consolidate(...)` | `POST /api/v1/{ctx}/consolidate` |
| `reflect(...)` | `POST /api/v1/{ctx}/reflect` |
| `elaborate(...)` | `POST /api/v1/{ctx}/elaborate` |
| `context(...)` | `POST /api/v1/{ctx}/context` |
| `inspect(...)` | `GET /api/v1/{ctx}/inspect` |
| `state()` | `GET /api/v1/{ctx}/state` |
| `profile()` | `GET /api/v1/{ctx}/profile` |
| `audit(...)` | `GET /api/v1/{ctx}/audit` |
| `fsck(...)` | `POST /api/v1/{ctx}/fsck` |
| `health()` | `GET /api/v1/health` |

Python names `query_context` as `context` here. Python exposes `whoami()` and `keys.*`; the JavaScript client does not include those methods - use REST for those endpoints.

## Namespaces

| Namespace | Highlights |
| --- | --- |
| `documents` | `upload`, `get`, `delete`, `list`, `query`, `raw`, `reprocess`, `recomputeLinks`, `chunks`, `keywords.*` |
| `sessions` | `create` → `Session` with `.turns()`, `.context()`, `.close()` |
| `entities` | `list`, `get`, `history`, `delete` |
| `scopes` | `register`, `list`, `delete`, `forget` |
| `principals` | `list`, `get`, `effective`, `grant`, `revoke` |
| `traces` | `list`, `get`, `stats` |
| `lifecycle` | `decay`, `expire` |

## Errors

| Class | Typical cause |
| --- | --- |
| `AgentMemoryError` | Base |
| `AuthError` | 401 |
| `ScopeError` | 403 |
| `NotFoundError` | 404 |
| `ValidationError` | 400 / 422 |
| `RateLimitError` | 429 (`retryAfter` when provided) |
| `ServerError` | 5xx |
| `ConnectionError` | Network / timeout |

→ [Errors and retries](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md#errors-and-retries)

## Harness adapter

```bash
npm install @surrealdb/spectron-vercel-ai
```

→ [Vercel AI SDK](/docs/agent-memory/integrations/ai-sdks/vercel-ai-sdk.md)

## User guide

→ [JavaScript and TypeScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md)

---

Source: https://surrealdb.com/docs/agent-memory/reference/sdk-kotlin

# Kotlin SDK reference

The SurrealDB Agent Memory Kotlin client. Package layout, configuration, and the error model.

| Item | Value |
| --- | --- |
| Package | `com.surrealdb.kotlin.memory` |
| Distribution | Bundled in `com.surrealdb:kotlin` |
| Models | `com.surrealdb.kotlin.memory.model` |
| Sub-clients | `com.surrealdb.kotlin.memory.ns` |

The SurrealDB Agent Memory client ships inside the [SurrealDB Kotlin SDK](/docs/reference/kotlin.md) - there is no separate artifact. It is independent of the SurrealDB RPC engine and speaks SurrealDB Agent Memory's HTTP API directly.

## Constructor

```kotlin
import com.surrealdb.kotlin.memory.AgentMemory
import kotlin.time.Duration.Companion.seconds

val memory = AgentMemory(
    contextId = "acme-prod",
    apiKey = "sk-spec-...",
    endpoint = "https://api.memory.example",
    timeout = 30.seconds,
    maxRetries = 3,
)
```

| Parameter | Type | Default | Notes |
| --- | --- | --- | --- |
| `contextId` | `String` | required | The context to operate in. |
| `apiKey` | `String` | required | Bearer token; mutable `var`, applied on next request. |
| `endpoint` | `String` | required | Base URL; mutable `var`. |
| `timeout` | `Duration` | `30s` | Per-request timeout. |
| `maxRetries` | `Int` | `3` | GET-only retries. |
| `httpClient` | `HttpClient?` | `null` | Inject a Ktor client. |
| `json` | `Json` | lenient | `ignoreUnknownKeys = true`, `explicitNulls = false`. |

Authentication uses the **`Authorization: Bearer`** header. Call `close()` to release the HTTP client.

## Surface

Top-level `suspend` verbs on `AgentMemory`: `remember`, `rememberMany`, `recall`, `queryContext`, `state`, `profile`, `reflect`, `forget`, `chat`, `consolidate`, `elaborate`, `inspect`, `audit`, `whoami`, `health`.

Namespaced sub-clients:

| Property | Type | Purpose |
| --- | --- | --- |
| `documents` | `DocumentsNamespace` | Upload, query, chunk, and manage documents (plus `documents.keywords`). |
| `sessions` | `SessionsNamespace` | Create and drive chat sessions. |
| `entities` | `EntitiesNamespace` | List, fetch, and delete extracted entities. |
| `lifecycle` | `LifecycleNamespace` | Expire, decay, and `fsck` maintenance. |
| `traces` | `TracesNamespace` | Inspect decision traces and stats. |
| `principals` | `PrincipalsNamespace` | List grants and grant/revoke access. |
| `scopes` | `ScopesNamespace` | Register and manage scope nodes. |
| `keys` | `KeysNamespace` | Self-service API key creation and rotation. |

## Scopes and delegation

Scopes are hierarchical `key/value` slash-paths passed as a `List<String>`; an empty list targets the caller's default write region. Build them with `scopePaths`:

```kotlin
import com.surrealdb.kotlin.memory.scopePaths

scopePaths(listOf("org/acme"))                 // ["org/acme"]
scopePaths(mapOf("org" to "acme"))             // ["org/acme"]
scopePaths("team" to "eng", "org" to "acme")   // ["team/eng", "org/acme"]
```

Every method takes an optional `onBehalfOf: String?`, sent as the `X-Spectron-On-Behalf-Of` header so a privileged caller acts as another principal.

## Retries

GET requests retry on connection errors and 5xx responses with 250 ms / 500 ms / 1 s backoff, capped at `maxRetries` (default 3). Writes are never retried.

## Errors

All failures throw a subclass of the sealed `AgentMemoryException`, modelled on RFC 7807 problem details. Each carries `status`, `title`, `detail`, `typeUri`, `instance`, and `extensions: Map<String, JsonElement>`.

| Exception | HTTP status |
| --- | --- |
| `AgentMemoryAuthException` | 401 |
| `AgentMemoryScopeException` | 403 |
| `AgentMemoryNotFoundException` | 404 |
| `AgentMemoryValidationException` | 400, 422 |
| `AgentMemoryRateLimitException` | 429 (with `retryAfter: Duration?`) |
| `AgentMemoryServerException` | 5xx and unmatched |
| `AgentMemoryTransportException` | 0 (connection or parse failure) |

## Models

Response and request types live under `com.surrealdb.kotlin.memory.model`, all `@Serializable`:

- **Enums** - `QueryMode`, `DocumentStatus`, `TurnRole`, `MemoryCategory`, `InferMode`, `GraphEdgeKind`, and others.
- **Facts** - `Triple`, `TripleEntity`, `BatchMessage`, `FactsResponseJson`, `FactsBatchResponseJson`.
- **Documents** - `DocumentJson`, `ChunkJson`, `KeywordJson`, `QueryFilter`, `UploadResponse`, `QueryResponseJson`.
- **Memory** - `ChatResponseJson`, `QueryMemoryResponseJson`, `ContextQueryResponseJson`, `StateResponseJson`, `ProfileResponseJson`.
- **Governance** - `PrincipalJson`, `EffectiveGrantsJson`, `WhoamiResponse`, `ScopeNodeJson`, `AuditRowJson`.
- **Maintenance** - `ConsolidateResponseJson`, `ElaborateResponseJson`, `InspectResponseJson`, `FsckReportJson`.
- **Keys** - `MintedKey` (secret returned once), `KeyDetail`.

## User guide

→ [Kotlin SDK](/docs/agent-memory/integrations/sdks/kotlin.md)

---

Source: https://surrealdb.com/docs/agent-memory/reference/sdk-python

# Python SDK reference

The SurrealDB Agent Memory client inside the surrealdb package. Package layout and configuration.

| Item | Value |
| --- | --- |
| PyPI package | [`surrealdb-memory`](https://pypi.org/project/surrealdb-memory/) (its own distribution, pulled in by the `surrealdb[memory]` extra) |
| Install | `pip install --pre 'surrealdb[memory]'` |
| Import | `from surrealdb.memory import Memory, AsyncMemory` |
| Submodule | `surrealdb.memory` (models, namespaces, errors) |

The client is versioned independently of the driver, so the extra pins `>=`, not `==`; `surrealdb` alone installs the driver without it.

## Constructor

```python
Memory(context: str, endpoint: str, api_key: str,
    timeout: float = 30.0, max_retries: int = 3)
AsyncMemory(...)  # same arguments; methods are async
```

Requests use `Authorization: Bearer <api_key>`. Optional `on_behalf_of` on every verb sets `X-Spectron-On-Behalf-Of` for delegation.

## Memory verbs (REST mapping)

| Method | HTTP |
| --- | --- |
| `remember(...)` | `POST /api/v1/{ctx}/facts` |
| `remember_many(...)` | `POST /api/v1/{ctx}/facts/batch` |
| `recall(...)` | `POST /api/v1/{ctx}/query` |
| `forget(...)` | `POST /api/v1/{ctx}/forget` |
| `chat(...)` | `POST /api/v1/{ctx}/chat` (SSE when `stream=True`) |
| `consolidate(...)` | `POST /api/v1/{ctx}/consolidate` |
| `reflect(...)` | `POST /api/v1/{ctx}/reflect` |
| `elaborate(...)` | `POST /api/v1/{ctx}/elaborate` |
| `query_context(...)` | `POST /api/v1/{ctx}/context` |
| `inspect(ref, ...)` | `GET /api/v1/{ctx}/inspect` |
| `state()` | `GET /api/v1/{ctx}/state` |
| `whoami()` | `GET /api/v1/{ctx}/me` |
| `profile()` | `GET /api/v1/{ctx}/profile` |
| `audit(...)` | `GET /api/v1/{ctx}/audit` |
| `health()` | `GET /api/v1/health` (not context-scoped) |

## Namespaces

| Namespace | Methods |
| --- | --- |
| `documents` | `upload`, `get`, `delete`, `list`, `query`, `fetch_raw`, `reprocess`, `recompute_links`, `chunks` |
| `documents.keywords` | `list`, `get`, `search`, `for_document` |
| `sessions` | `create`, `delete`, `context`, `turns` |
| `entities` | `list`, `get`, `delete`, `history` |
| `scopes` | `register`, `list`, `delete`, `forget` |
| `principals` | `list`, `get`, `grant`, `revoke`, `effective` |
| `keys` | `create`, `list`, `delete`, `rotate` |
| `traces` | `list`, `get`, `stats` |
| `lifecycle` | `decay`, `expire`, `fsck` |

Full REST tables: [REST API](/docs/agent-memory/reference/rest-api.md).

## Response models

Import from `surrealdb.memory`:

| Model | Used for |
| --- | --- |
| `RememberResponse`, `RememberBatchResponse`, `ExtractionResult` | Fact ingest |
| `RecallResponse`, `RecallHit` | Semantic recall |
| `ChatResponse`, `ChatChunk` | Chat (streaming chunks) |
| `ContextQueryResponse` | Composed context string |
| `StateResponse` | Working-memory snapshot |
| `UploadResponse`, `Document`, `DocumentPage`, `Chunk`, `ChunkPage` | Document corpus |
| `DocumentQueryResponse`, `DocumentQueryHit` | Document search |
| `Keyword`, `KeywordSearchResponse`, … | Keyword index |
| `Session`, `Turn`, `TurnListResponse` | Sessions |
| `EntityDetail`, `EntityListResponse`, … | Knowledge graph entities |
| `WhoamiResponse`, `ProfileResponse` | Caller identity |
| `TraceRecord`, `AuditResponse`, `FsckReport` | Observability / maintenance |

Nested graph and extraction payloads may remain `dict` values where the server shape evolves (“slim model” convention).

## Exceptions

| Class | HTTP |
| --- | --- |
| `MemoryServiceError` | Base |
| `MemoryAPIError` | Any non-2xx without a subclass (`status_code`, `message`, `trace_id`, `body`) |
| `MemoryAuthError` | 401 |
| `MemoryScopeError` | 403 |
| `MemoryNotFoundError` | 404 |

→ [Errors and retries](/docs/agent-memory/integrations/sdks/python.md#errors-and-retries)

## User guide

→ [Python SDK](/docs/agent-memory/integrations/sdks/python.md)

---

Source: https://surrealdb.com/docs/agent-memory/reference/sdk-swift

# Swift SDK reference

The SurrealDB Agent Memory Swift client. Package layout and configuration.

The `AgentMemory` product in `surrealdb.swift` is the Swift client for SurrealDB Agent Memory. This page covers adding the package and configuring a client.

| Item | Value |
| --- | --- |
| Package | `surrealdb.swift` |
| Product | `AgentMemory` |
| Install | `.product(name: "AgentMemory", package: "surrealdb.swift")` |
| Import | `import AgentMemory` |

## Configuration

```swift
let memory = try AgentMemory(
    context: "acme-prod",
    endpoint: "https://api.memory.example",
    apiKey: "sk-spec-..."
)
```

| Setting | Purpose |
| --- | --- |
| `context` | Path segment after `/api/v1/` |
| `endpoint` | Base URL (no trailing slash) |
| `apiKey` | Context API key |

## Auth header

```http
Authorization: Bearer <key>
```

## Core operations (REST mapping)

| SDK area | HTTP |
| --- | --- |
| Facts | `POST /api/v1/{ctx}/facts`, `POST .../facts/batch` |
| Query / context | `POST .../query`, `POST .../context` |
| Chat | `POST .../chat` |
| Documents | `POST/GET/DELETE .../documents` |
| Sessions | `POST/GET/DELETE .../sessions` |
| Entities | `GET/DELETE .../entities/{type}/{name}` |
| Governance | `.../scopes`, `.../principals`, `.../keys` |

Full tables: [REST API](/docs/agent-memory/reference/rest-api.md).

## Delegation and idempotency

- Pass `onBehalfOf:` to act as another principal. It is sent as the `X-Spectron-On-Behalf-Of` header.
- Writes carry an `Idempotency-Key` header for safe retry deduplication.

## Errors

All failures throw `AgentMemoryError` with fields `status`, `title`, `detail`, `retryAfter`, `typeURI`, `instance` and `extensions`. The `kind` maps to `.base`, `.auth`, `.scope`, `.notFound`, `.validation`, `.rateLimit` or `.server`. See [Errors](/docs/agent-memory/reference/errors.md).

## User guide

See [Swift SDK](/docs/agent-memory/integrations/sdks/swift.md) for usage patterns.

---

Source: https://surrealdb.com/docs/agent-memory

# Agent Memory

Memory and knowledge for AI agents on SurrealDB. Principles, architecture, quickstarts, and the mental model.

SurrealDB Agent Memory is a memory and knowledge layer for AI agents. It is an application tier in front of SurrealDB, and every durable record lives in that database: graph, vector, document, relational, and geospatial, written under ACID transactions. Provenance and trust sit on the records themselves. Traces of retrieval and decisions are graph nodes, not disposable logs. Belief history is tri-temporal, so the system can keep apart what was said, what is true now, and what used to be true.

Use this hub to go from principles to running code, then dive into the product sections (memory & knowledge, integrations, cookbooks, reference).

> [!NOTE]
> SurrealDB Agent Memory was developed under the project name **Spectron**, and that name is retained throughout the shipped interface: the `spectron` and `spectrond` binaries, the `SPECTRON_*` environment variables, the `spectron_*` MCP tool names, and the `X-Spectron-*` request headers. The client SDKs have already been renamed - the JavaScript package is `@surrealdb/memory`, the Python one `surrealdb-memory`, and so on. The rule of thumb is that prose and SDKs use the product name, while anything you type at a shell or set as configuration still uses `spectron`. Those remaining names will be renamed in a future release.

## Architecture

What SurrealDB Agent Memory is built to do, what it is not, and how retrieval, traces, and time work:

- [Principles and goals](/docs/agent-memory/architecture/principles-and-goals.md)
- [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md)
- [Coherence, retrieval, and cost tiers](/docs/agent-memory/architecture/coherence-retrieval-and-tiers.md)
- [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md)
- [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md)
- [Surface, models, and security](/docs/agent-memory/architecture/surface-security-and-models.md)
- [Glossary](/docs/agent-memory/architecture/glossary.md)

## Welcome and quickstarts

- **[What is SurrealDB Agent Memory?](/docs/agent-memory/welcome/what-is-surrealdb-agent-memory.md)** - product positioning in one pass.
- **[Why agentic memory?](/docs/agent-memory/welcome/why-agentic-memory.md)** - where naive context and pure-vector shortcuts fail.
- **[The accuracy promise](/docs/agent-memory/welcome/accuracy-promise.md)** - provenance, reconciliation, and auditability.
- **[How it works](/docs/agent-memory/welcome/how-it-works.md)** - end-to-end path from a turn to stored, retrievable state.

**Quickstarts**

- **[Agent Memory on SurrealDB Cloud](/docs/agent-memory/quickstarts/surrealdb-cloud.md)** - Cloud API vs data plane, organisation roles, and your first context in SurrealDB Studio.
- **[Hosted quickstart](/docs/agent-memory/quickstarts/hosted.md)** - SurrealDB Agent Memory Cloud, API key, first remember and recall.
- **[Embedded library](/docs/agent-memory/quickstarts/embedded.md)** - HTTP, MCP, and SDK integration surfaces.

**Building with AI coding tools?** Start with **[Agent guide (AGENTS.md)](/docs/agent-memory/reference/agents.md)** - copy it into Cursor rules or a project skill so your agent can learn SurrealDB Agent Memory without reading the full docs.

## Mental model

How isolation, sessions, categories, and provenance fit together:

- [Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md) - authoritative versus experiential **streams** in **one** graph.
- [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md)
- [Sessions and turns](/docs/agent-memory/mental-model/sessions-and-turns.md)
- [Memory categories](/docs/agent-memory/mental-model/memory-categories.md)
- [Provenance and traceability](/docs/agent-memory/mental-model/provenance-and-traceability.md)
- [Supersession, decay, and forget](/docs/agent-memory/mental-model/memory-lifecycle.md) - how beliefs change, fade, and are removed

## Product sections

- **[Memory & knowledge](/docs/agent-memory/memory-and-knowledge.md)** - authoritative and experiential ingest, unified retrieval, reasoning, operations, tuning.
- **[Integrations](/docs/agent-memory/integrations.md)** - SDKs, MCP, framework adapters.
- **[Cookbooks](/docs/agent-memory/cookbooks.md)** - end-to-end patterns.
- **[Reference](/docs/agent-memory/reference.md)** - REST, management API, CLI, configuration, errors.

## Sessions

- [State and diffs](/docs/agent-memory/sessions/state-and-diffs.md) - what a session holds, and how it changes between turns

---

Source: https://surrealdb.com/docs/agent-memory/architecture/coherence-retrieval-and-tiers

# Coherence, retrieval, and cost tiers

Five coherence dimensions and hybrid structural retrieval. Also covers the four-tier query ladder.

When you hear **cat**, you do not run one search string - you blend what it reminds you of (pets, lions, a team logo), exact words you once read, how things connect, and whether a fact is still true. Retrieval in SurrealDB Agent Memory works the same way: several signals fused together, not a single embedding score.

## Five coherence dimensions

Memory is coherent along five axes at once - SurrealDB Agent Memory stores enough metadata to answer questions on each, so retrieval stays auditable and trustworthy:

| Dimension | What it gives you |
| --- | --- |
| **Semantic** | Similarity before structure is explicit: embedding-based recall over entities and passages. |
| **Lexical** | What was actually said or shown, down to character positions in the source: extracted attributes carry `source.span` into the originating turn or document passage. Citations are a **stored field**, not best-effort prose. |
| **Relational** | Understanding as connections: one entity/relation graph so “cat” can reach a manual, a prior turn, and a related entity (lion, pet, breed) without treating them as unrelated chunks. |
| **Time** | What held when, and how beliefs evolved: `valid_from` / `valid_until`, `as_of`, and time-travel queries. See [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md). |
| **Space** | Where a fact was captured or applies - optional geometry; geo filters compose with semantic and graph signals in the same ranker. |

A vector-only index misses several of these at once, while an unstructured store misses them unless you add structure later. SurrealDB Agent Memory stores the metadata up front.

## Structural retrieval (beyond embeddings)

Retrieval is hybrid by design. Embeddings are one signal, fused with other precomputed structure so that top-k is not a black box.

Typical signals in the fused ranker include:

- **Vector recall** - dense embeddings on entities, attributes, chunks, and (when enabled) images and audio.
- **Lexical recall** - BM25 over chunk text and entity names for exact phrases and rare terms.
- **Graph traversal** - limited hops from seed entities when surface forms differ.
- **Keyword bridges** - RAKE keyphrases linked via `knowledge_has_keyword` edges from query-matched terms to document passages.
- **Section embeddings and document links** - related sections, not only the single nearest chunk.
- **Personalised PageRank** - graph-walk scoring biased toward query seeds. Relation edges used in graph hops are **scope-gated** on the edge itself, not only on destination entities.
- **Geographic recall** - radius, polygon, nearest‑k on stored geometry.
- **Trace-derived features** - prior retrieval outcomes boost what worked; demote what led to corrections.

Each `/query` emits a **`retrieval_trace`** recording candidates, per-signal contributions, and the returned set.

Hands-on retrieval modes are in [Hybrid search](/docs/agent-memory/retrieve/hybrid-search.md).

## Tiered query resolution

SurrealDB Agent Memory does not run the same expensive path on every request. Reads route through a four-tier ladder so simple questions use as few LLM tokens as possible. Structured lookup and cache hits skip a large prompt and a synthesis-model call when a cheaper path is enough.

| Tier | What happens | Token / cost profile |
| --- | --- | --- |
| **1 - Direct structured lookup** | Typed questions resolved from the entity/attribute graph by key - no embeddings, no LLM, no ranking pass. | **Minimal tokens** - often nothing sent to an LLM. |
| **2 - Response reuse** | Match against prior answers in the same Context and scope, with **entity-aware invalidation** (cited facts must still be current). Returns a prior answer when still valid. **Bypassed** for `/chat` when the session already has prior turns (the reply depends on the transcript window; windowed turns neither reuse nor seed the cache). | **No new generation** on a hit - reuses prior synthesis. |
| **3 - Hybrid retrieval and synthesis** | Fused retrieval over a **bounded internal pool** (default 256 candidates; independent of answer `k`), then LLM synthesis over a **bounded** context block. Default answer size **`k`** / **`limit`** is **10** (max **50**). | Moderate tokens - default for open questions. |
| **4 - Full-context fallback** | Broader sweep when tier 3 is thin or below the **confidence floor** (0.40 fused score): more candidates, deeper graph hops, optional query rewrite, larger context. Duplicate hits from tier 3 and tier 4 are merged by id, keeping the highest score. | **Highest token use** - explicit escalation, still traceable. |

Tiers cascade (miss on 2 falls to 3; thin or low-confidence 3 escalates to 4). Each tier writes **`retrieval_trace`** metadata describing which tier ran and why - so you can see where token spend goes and tune per Context.

In short: most “what is Alice’s role?”-style questions should resolve without stuffing the entire memory graph into the model context.

---

Source: https://surrealdb.com/docs/agent-memory/architecture/eight-pillars-and-categories

# Eight pillars and six categories

The primitives SurrealDB Agent Memory operationalises. Pillars of agent memory, and typed experiential sub-stores.

SurrealDB Agent Memory’s design is expressed as primitives the memory layer must support. They follow habits of human memory: trust some sources more than others (authoritative versus experiential), link ideas that belong together (elaboration), notice patterns over time (consolidation), and admit when you are not sure (calibration and uncertainty). You can read the pillars as engineering requirements; you can also read them as “what would we need if an agent were to remember like someone who prefers to err on the side of caution?”

## Eight pillars of agent memory

Each pillar is something the write path, read path, or storage layer must be able to represent. Together they are the minimum set for memory that holds up under scale, change, and multi-instance deployment.

1. **Authoritative** - vetted organisational truth: employee handbooks, return policies, product catalogues, API reference docs. Like the HR policy PDF everyone trusts more than hallway gossip. Encoded as `source.kind = "document"`.
2. **Experiential** - what was said in conversation: “I prefer morning meetings”, “Our Q3 target is 12% growth”. Like notes from a call, not the official record. Encoded as `source.kind = "turn"`.
3. **Reconciliation** - when those streams disagree, SurrealDB Agent Memory does **not** pick a silent winner or apply last-write-wins. If chat says “30-day returns” but the policy says “14 days”, you get an explicit **uncertainty** record instead of a blended guess. Same-source updates chain with `valid_until` on the prior assertion. **One reconciler** handles documents, turns, reflection, and elaboration alike.
4. **Elaboration** - connecting facts that were stored separately but belong together - e.g. linking “Project Atlas” in chat to the “Atlas” product in a uploaded spec. Mostly **background** work; output carries `source.kind = "elaboration"` and `derived_from` pointing at inputs.
5. **Reflection** - insights minted from the questions you ask the system (“What themes show up in support tickets this week?”). `POST /reflect` runs synthesis; persisted results use `source.kind = "reflect"` with lower default trust than primary sources.
6. **Consolidation** - turning repeated observations into stable long-term beliefs over time - e.g. noticing three weeks of “user asks for dark mode” and crystallising that as a durable preference pattern. Background job; encoded as `source.kind = "consolidation"`.
7. **Calibration** - every assertion carries how sure the source is and how confident the reconciler is after extraction. Below a configurable floor, SurrealDB Agent Memory refuses to overwrite and emits **uncertainty** instead - like a fact-checker declining to publish without evidence.
8. **Collective** - shared memory when **multiple people or agents** corroborate the same fact (e.g. three support agents all log the same outage). Independent assertions keep **separate provenance records** rather than being averaged into one anonymous fact; cross-provenance agreement is visible in the graph. Automated promotion to a wider scope when corroboration crosses a policy threshold is the Collective pillar’s design target.

## Six memory categories (experiential layer)

The experiential layer is **not** one bucket. It is six typed areas with distinct lifecycles and retrieval behaviour: one raw transcript plus five extracted categories.

| Category | Role |
| --- | --- |
| **Episodic** | The **verbatim** conversation: sessions and turns in order, including pronouns and references (“he”, “that project”) left as spoken (**anaphora** - words that point back to something said earlier). Source of truth for quotes (`source.span`). Written once; browsable as a transcript. See [Sessions and turns](/docs/agent-memory/mental-model/sessions-and-turns.md). |
| **Identity** | Who someone is - name, title, employer, locale. E.g. “Alice is Head of Platform at Acme” or “King Charles III is head of state of the United Kingdom”. Long retention; feeds profile summaries. |
| **Knowledge** | Facts the user shared in chat, distinct from uploaded manuals - side projects, opinions, anecdotes. E.g. “Alice mentioned she is learning Rust.” Medium retention; fades without reinforcement. |
| **Context** | What matters **right now** - active ticket, current sprint, today’s meeting. E.g. “Alice is debugging the checkout flow this afternoon.” Short retention; replaced quickly. |
| **Instructions** | **Behavioural** memory, not factual (“always British English”, “never use my first name”). Stored separately; applied at **prompt assembly**, not generic retrieval. |
| **Uncertainty** | Explicit “we do not know yet” records from conflicts, weak extraction, or missing evidence - so the agent can say “I’m not sure” instead of inventing. |

A parallel **trace layer** records SurrealDB Agent Memory’s own retrievals, decisions, and responses - memory **about** how memory was used. Traces feed the ranker, calibrator, and consolidator. See [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md).

A practitioner-oriented tour of the six extracted categories is in [Memory categories](/docs/agent-memory/mental-model/memory-categories.md).

---

Source: https://surrealdb.com/docs/agent-memory/architecture/glossary

# Agent Memory glossary

Alphabetical definitions of SurrealDB Agent Memory terms. Covers the architecture and memory documentation.

Short definitions for terms that appear throughout the SurrealDB Agent Memory docs. For deeper treatment, follow the links where provided.

SurrealDB Agent Memory’s vocabulary is precise, but the intent is everyday: memory that **associates** related ideas (like hearing “cat” and thinking of pets, lions, or a nickname) and keeps **time and scope** straight (“I have one” versus “I used to” versus “they weigh about four kilos in general”). The entries below name the machinery; [What is SurrealDB Agent Memory?](/docs/agent-memory/welcome/what-is-surrealdb-agent-memory.md) and [Memory categories](/docs/agent-memory/mental-model/memory-categories.md) walk through the human-shaped examples.

## A

**Anaphora** - Pronouns and phrases in conversation that refer back to something said earlier (“he”, “that project”, “the policy we discussed”). Episodic memory keeps the transcript intact; extraction uses context to resolve what they mean.

**Attribute** - A named property on an entity (for example `role = Head of Platform`). Attributes can be superseded over time with `valid_from` / `valid_until`.

**Authoritative (pillar)** - Curated, vetted knowledge: handbooks, policies, product catalogues. Usually ingested as **documents** with higher default **trust**.

**Authority** - The policy that curated sources win over casual chat when sources conflict, implemented by the **reconciler** (often via **uncertainty** rather than silent overwrite).

## C

**Calibration (pillar)** - Gating assertions by confidence and trust; below a floor, SurrealDB Agent Memory refuses to overwrite and records uncertainty instead.

**Collective (pillar)** - Shared memory when multiple principals corroborate the same fact; independent assertions keep separate provenance. Automated promotion to wider scope is policy-gated.

**Confidence** - The reconciler’s posterior certainty on an assertion (`attribute.confidence`), stored separately from **source trust**.

**Context (read parameters)** - Optional **`labels`**, **`lens`**, and **`scope_view`** on `/query` and `/context` to filter or fold results within what your grant already allows. See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md#labels-lens-and-scope-views-reads).

**Consolidation (pillar)** - Background process that crystallises recurring observations into longer-lived beliefs.

**Context (category)** - Short-lived “what matters right now” for the active conversation.

**Context (tenant)** - A **Context** is SurrealDB Agent Memory’s top-level isolation unit: its own database, keys, and configuration. Not the same as the memory category “context”.

## D

**Decision trace** - A graph record of what the reconciler considered, created, or superseded during a write.

**Delegation** - Acting on behalf of another principal via **`X-Spectron-On-Behalf-Of`**, intersected with both parties’ grants (depth 1).

## E

**Elaboration (pillar)** - Background linking of related facts that were stored separately but belong together.

**Entity** - A typed node in the memory graph (for example `Person/alice`, `Product/airpods_pro`).

**Episodic (category)** - The raw ordered transcript of sessions and turns before extraction.

**Experiential (pillar)** - Knowledge from conversation and derived passes (`turn`, `reflect`, `elaboration`, …), usually lower default trust than authoritative material.

**Extraction** - The pipeline that turns text into entities, attributes, relations, instructions, and uncertainties.

## F

**Forget** - Explicit soft-removal of matching memory (`valid_until`), distinct from aging.

## I

**Identity (category)** - Stable facts about who the principal is (name, role, employer).

**Instructions (category)** - Behavioural preferences (“use British English”), applied at prompt assembly, not generic retrieval.

## K

**Graph edge kind** - A named structural signal used in document **`hybrid_graph`** retrieval (for example `knowledge_has_keyword`, `document_link`). Request and response enums reject unknown values with `400 Bad Request`.

**Known time** - When SurrealDB Agent Memory first recorded a belief; queried with `as_of`. See [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md).

**Knowledge (category)** - Facts learnt in conversation, distinct from uploaded manuals.

**Memory category** - On extracted entities, attributes, and relations: `identity`, `knowledge`, or `context` (API-enforced enum).

## P

**Principal** - An actor (human, agent, service) that can be granted read/write permissions on scope paths.

**Provenance** - Structured metadata on every fact: source kind, reference, span, trust, derivation. See [Provenance and traceability](/docs/agent-memory/mental-model/provenance-and-traceability.md).

## R

**Reconciliation** - Comparing new extractions to existing memory: supersede, merge, or emit **uncertainty**.

**Reflection (pillar)** - On-demand synthesis from a question; results stored with lower default trust.

**Response trace** - Record of a composed answer (`/chat`, `/reflect`), used for tier-2 reuse when still valid.

**Retrieval trace** - Record of a `/query` (or related read): candidates, signals, tier, and results.

## S

**Scope** - Hierarchical slash paths (`org/acme/user/alice/`) that partition data within a Context. Register with `spectron scopes create` before use. Distinct from **permissions** and from **labels** (`key=value` descriptors).

**Session** - A conversation container: ordered **turns**, scope, and metadata.

**Source.kind** - Whether a fact came from a `document`, `turn`, `reflect`, `elaboration`, `consolidation`, and so on.

**Source.span** - Character positions in the originating message or passage for quotable citations.

**Source.trust** - Default weight reflecting how authoritative the source stream is (document versus casual turn, and so on).

**Supersession** - Replacing a prior assertion by setting `valid_until` on the old record while keeping history.

**System time** - Database MVCC history: what was stored at an instant. See [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md).

## T

**Trace layer** - Memory about how memory was used: retrieval, decision, and response traces.

**Trust** - See **source.trust** and **calibration**; central to why curated and conversational facts combine safely.

**Turn** - One message in a session (`user`, `assistant`, `system`, `tool`).

**Tri-temporal** - System, known, and valid time kept distinct. See [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md).

## U

**Uncertainty (category)** - Explicit “we do not know” or conflict records instead of guessed fill-ins.

**Unified graph** - One SurrealDB store for documents, turns, entities, relations, and traces. See [Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md).

## V

**Valid time** - When a fact held in the real world (`valid_from` / `valid_until`), independent of when SurrealDB Agent Memory learnt it.

---

Source: https://surrealdb.com/docs/agent-memory/architecture/principles-and-goals

# Principles and goals

What SurrealDB Agent Memory deliberately is and is not. The goals it is built to meet.

SurrealDB Agent Memory is a memory and knowledge layer for AI agents that allows you to trust what it remembers and retrieves. It sits as a horizontally scalable application tier in front of SurrealDB. Durable state - graph, vectors, documents, structured records - lives in the database, typically one ACID transaction per write.

## Principles

- **One substrate, many models.** Documents, conversations, entities, attributes, relations, traces, and embeddings live in one ACID-transactional database. Cross-store stitching is rejected as an architectural choice, not a feature gap.
- **Provenance and trust are first-class data.** Every fact-bearing record carries where it came from, who said it, when it was authored, which source produced it, and how trusted that source is. Trust and confidence are stored, queryable, and auditable.
- **Memory is tri-temporal and non-deleting.** Supersession and aging replace blind overwrite. **System time** (MVCC record history), **known time** (when SurrealDB Agent Memory first learnt a fact), and **valid time** (when the fact held in the world) are queryable independently. See [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md).
- **One reconciler, all provenance.** Document-extracted facts, turn-extracted facts, reflections, elaborations, and consolidations all flow through the same supersession-and-uncertainty machinery - identical guarantees regardless of how a fact arrived.
- **Traces are memory, not logs.** Retrieval, decision, and response traces are first-class graph nodes that feed back into ranking, calibration, consolidation, and reinforcement.
- **Cost is tiered and visible.** Cheap questions are answered cheaply; expensive paths are explicit, not buried in averages. See [Coherence, retrieval, and cost tiers](/docs/agent-memory/architecture/coherence-retrieval-and-tiers.md).
- **Models are pluggable.** Extraction, embedding, reconciliation, synthesis, and chat models are configurable per Context and per call.
- **Deterministic by design.** Change the data, and the agent’s answer changes. Calibration, tri-temporality, supersession, and response reuse (with invalidation) anchor answers in **queryable substrate state** rather than in model variance alone.
- **The HTTP contract is explicit.** SurrealDB Agent Memory publishes an OpenAPI specification for its REST surface. Official SDKs are generated from that spec so request and response types stay aligned with the server across releases.

## What SurrealDB Agent Memory is not

- **Not a vector database.** Vectors are one signal among many. Embeddings-only retrieval has known failure modes; SurrealDB Agent Memory treats them as an input to a **fused ranker**, not as the whole substrate.
- **Not a chat-history blob.** Turns are reconciled into typed entities, attributes, and relations - not stored as opaque text that is re-stuffed into prompts at query time.
- **Not a hand-rolled knowledge graph.** Structure emerges from extraction, elaboration, and consolidation. Users do not hand-author a schema to “get a graph”; the graph is a **product** of the pipeline.
- **Not a context-window manager.** SurrealDB Agent Memory is the durable memory and knowledge layer **behind** an agent - not the in-prompt scratchpad. The agent decides what goes in the window; SurrealDB Agent Memory decides what is **remembered**.
- **Not an agent runtime.** `/chat` is a convenience composition over ingest and read paths for callers who want SurrealDB Agent Memory to *behave* like an agent. SurrealDB Agent Memory does not own model orchestration, tool calling, or planning loops.
- **Not an observability vendor.** Traces are stored substrate state used by the system itself. They yield derivable operational signals, but SurrealDB Agent Memory is not “metrics SaaS by default”.

## Where to read next

- [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md) - the operational model in primitives.
- [Architecture glossary](/docs/agent-memory/architecture/glossary.md) - short definitions of key terms.
- [Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md) - how authoritative and experiential material meet in one graph.

---

Source: https://surrealdb.com/docs/agent-memory/architecture/surface-security-and-models

# Surface, models, and security

HTTP ingest and read verbs, integrations, and model hooks. Security properties are covered too.

How to call SurrealDB Agent Memory, how models plug in, and how security properties attach to stored state.

## Ingest and read (HTTP)

Two ingest families and one read family over a **unified** graph (same reconciler, same retrieval stack):

| Verb | Purpose |
| --- | --- |
| `POST /api/v1/{ctx}/facts` | Text or triples, with `infer: "full" \| "triples" \| "preview" \| "none"`. |
| `POST /api/v1/{ctx}/facts/batch` | Bulk conversation ingest (`messages: [{ role, content, ts }, …]`). Optional **`extract`**: `whole_conversation` (default) or `per_message`. Idempotent with `Idempotency-Key`. |
| `POST /api/v1/{ctx}/documents` | Byte ingest into the multi-modal pipeline (async). |
| `POST /api/v1/{ctx}/query` | Unified read over facts and passages. Accepts optional **`labels`**, **`lens`** (filters by scope-path **involvement** within the grant - does not shrink access to exact clause matches), and **`scope_view`** (`strict`, `merged`, or `crossTeam`) to control how broadly results are folded within what the caller is already allowed to see. **`merged`** and **`crossTeam`** resolve like **`strict`**. |
| `POST /api/v1/{ctx}/chat` | Recall + LLM synthesis; SSE when streaming. |

`POST /api/v1/{ctx}/context` and the MCP **`recall`** / **`context`** tools accept the same read parameters as `/query`.

Trace listing: `GET /api/v1/{ctx}/traces`, `GET /api/v1/{ctx}/traces/stats` (supports **`windowHours`** for rolling operational aggregates), and `GET /api/v1/{ctx}/traces/{id}`. See [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md) and [REST API](/docs/agent-memory/reference/rest-api.md).

## Integrations

- **Generated SDKs** - Python (`surrealdb`) and TypeScript (`@surrealdb/memory`), both produced from SurrealDB Agent Memory’s OpenAPI specification so types track the server surface.
- **MCP over HTTP** - tools wrapping the same handlers; shared Bearer auth. See [MCP server](/docs/agent-memory/integrations/mcp-server/install.md).
- **Harness adapters** - LangChain, Claude Code hook, OpenAI Agents, Vercel AI SDK, n8n - flush conversations into `/facts/batch`.

## Model configuration (five hooks)

LLMs and embedders appear in five pipeline roles - each configurable **per Context** and **per call**, with model and cost recorded on traces:

1. **Extraction** - turns and chunks → entities, attributes, relations.
2. **Embedding** - vectors for entities, attributes, chunks.
3. **Reconciliation** - optional disambiguation when signals conflict.
4. **Synthesis** - `/chat` and `/reflect` generation.
5. **Elaboration / consolidation** - background passes.

SurrealDB Agent Memory supports **OpenAI-compatible endpoints** plus **first-class Anthropic and Google clients**. **The embedding model at write time must match read time** within a Context (or schedule re-embed).

## Security and privacy

- **Encryption** - **In transit:** terminate TLS at your reverse proxy or load balancer (typical deployments run SurrealDB Agent Memory on HTTP behind the proxy). **At rest:** configure encryption on your SurrealDB storage backend and object-store bucket (for example KMS on S3 or GCS); SurrealDB Agent Memory inherits whatever those backends provide.
- **Graph-resident audit** - writes emit `decision_trace`; reads emit `retrieval_trace`; `/chat` and `/reflect` emit `response_trace`. Together these record which caller asked what, against which scope, with which key, and which records were considered or returned.
- **Operational audit events** - alongside the trace graph, SurrealDB Agent Memory emits structured audit events for reads, destructive operations (`forget`), scope changes, background jobs, and **denied** authorisation attempts so refused access is on the record even when no trace record is written.
- **Prompt-injection handling** - ingest-time scanning on uploads and batched turns; untrusted text is sanitised before it is interpolated into extraction, chat, and consolidation prompts (see [Extraction pipeline](/docs/agent-memory/reasoning/extraction-pipeline.md#untrusted-metadata-in-prompts)).
- **Chat transparency vs leakage** - `/chat` may explain *that* it retrieved documents or memory and *how* it weighed them when asked. That is intentional for debugging in SurrealDB Studio. Production agents should not expose raw trace payloads, internal hostnames, or management URLs to end users; scope traces and audit events to operator roles instead. Adversarial "ignore previous instructions" prompts are mitigated on **ingest**; **synthesis** still depends on your model and system-prompt policy - treat `/chat` like any other LLM surface facing untrusted users.
- **Right to be forgotten** - `forget` soft-deletes or purges per policy; supersession history optional for audit.

## Inspectability and operational signals

Operators can derive:

- **Cost** - tokens, model, latency from `response_trace`.
- **Cache hit rate** - responses with `reused_from` set (tier 2 - fewer tokens spent).
- **Contradiction rate** - `uncertainty` records per volume of writes, by source kind.
- **Source distribution** - attributes by `source.kind` over time.
- **Supersession churn** - `decision_trace.superseded` per entity.
- **Retrieval quality** - candidate sizes and per-signal agreement from `retrieval_trace`.

See [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md) for more details.

---

Source: https://surrealdb.com/docs/agent-memory/architecture/traces-and-evolution

# Traces and memory evolution

Graph-resident traces, reflection, elaboration, consolidation. Also semantic response reuse.

SurrealDB Agent Memory keeps a record of what it retrieved, what it decided, and what it answered, and treats those records as memory rather than as logs. This page covers the three trace kinds, the three mechanisms that build new memory out of them, and how a prior answer can be reused while the facts behind it still hold.

## Tracing as graph-resident memory

SurrealDB Agent Memory stores three trace kinds as **first-class nodes** in the substrate, linked by real edges to entities, attributes, passages, and model calls. Traces are **inputs** to future ranking and consolidation - not only external telemetry.

| Trace | Emitted by | Holds (conceptually) | Typical edges |
| --- | --- | --- | --- |
| **`retrieval_trace`** | Every `/query` and ranked read | Query text, candidate sets per index, fused scores, returned subset, model metadata | `considered`, `returned`, `parent_trace` |
| **`decision_trace`** | Every reconciliation | Input extraction, confidence, trust, per-record outcome (created, updated, superseded, flagged), plus the acting principal, any **on-behalf-of** delegation target, and the session scope under which the write ran | `considered`, `created`, `superseded`, `flagged`, `parent_trace` |
| **`response_trace`** | `/chat` and `/reflect` | User message, assembled prompt, model response, tokens, cost, latency | `used_retrieval`, `produced_decision`, `wrote`, `parent_session` (session id for `/chat`; also populated on the unified query path) |

Traces are queryable (for example `GET /api/v1/{ctx}/traces` and `GET /api/v1/{ctx}/traces/{id}`) and inspectable from the CLI (`spectron inspect trace:…`). Trace listing respects the same access model as memory: principals with **`grant:manage`** or appropriate **`memory:read`** grants see traces in their region; others see only traces whose session scope falls within their read grant. See [Surface, models, and security](/docs/agent-memory/architecture/surface-security-and-models.md) for HTTP and CLI detail.

**Feedback loops** (examples):

- The ranker uses aggregated `retrieval_trace.returned` similarity - entities that helped similar queries get boosted; records tied to corrections get demoted. **Trace-feedback signals are scope-gated** - only traces visible under the caller’s read grant contribute to ranking for that query.
- Elaboration uses `decision_trace.considered` co-occurrence to propose links between entities that were examined together but never connected.
- Consolidation reinforces facts that `response_trace` shows were **actually used** in successful answers; untouched records can age out.
- Calibration uses repeated `decision_trace.superseded` lineage as a negative trust signal on the original source.

That is the difference between logging what happened and remembering it so the next ranking pass can use it.

## Reflection, elaboration, and consolidation

Three mechanisms create new memory; they answer different questions and run at different times.

| Mechanism | Trigger | When | Output |
| --- | --- | --- | --- |
| **Reflection** | Caller asks a synthesis question | On-demand (`POST /reflect`) | Answer plus optional new facts (`source.kind = "reflect"`) |
| **Elaboration** | Background sweep (default); also ingest-time or targeted | Async / periodic | New relations and attributes (`source.kind = "elaboration"`) linking previously disconnected material |
| **Consolidation** | Async job; on-demand via `POST /consolidate` | Cron-style, triggered, or caller-initiated | Observation records (`source.kind = "consolidation"`) with cumulative `history`, `derived_from`, and `proof_count` across updates |

On-demand **`POST /consolidate`** is **caller-scoped** (pools only within the caller's `memory:read` region; persists only where `memory:write` applies). The background scheduler pass pools every scope and owns the consolidation watermark.

- **Reflection** is the *active* path: it runs when something **asks**.
- **Elaboration** and **consolidation** are *passive*: they evolve memory **between** user interactions.

All write through the **same reconciler** as turns and documents, so supersession and uncertainty rules stay identical.

## Semantic response reuse (tier 2)

Prior **`response_trace`** records can answer new questions when:

- The new question is the same or very similar (embedding similarity) **within** the same Context and scope, and
- Every entity and attribute cited via `used_retrieval → retrieval_trace.returned` is still **current** (no superseding writes, no new contradicting `uncertainty`).

Then SurrealDB Agent Memory can return the stored answer and link a fresh `response_trace` with **`reused_from`** pointing at the original trace.

This is **semantic response caching with entity-aware invalidation** - the cache key is not a raw string hash; it is the query plus the **set of facts** the prior answer depended on. When any cited fact moves, dependent responses invalidate automatically.

Operational notes:

- Reuse is **conservative** by default; similarity, freshness, and trust thresholds are tuneable per Context.
- A reuse still emits a **new** `response_trace` so the graph distinguishes “answered from cache” from “fresh LLM call”, and reinforcement continues to attribute to the original.

The [REST API](/docs/agent-memory/reference/rest-api.md) and [MCP tools reference](/docs/agent-memory/integrations/mcp-server/tools-reference.md) exercise these paths.

---

Source: https://surrealdb.com/docs/agent-memory/architecture/tri-temporal-model

# Tri-temporal model

System and valid time, supersession, ageing, and explicit forget. How known time fits alongside them.

SurrealDB Agent Memory does not treat “delete record” as the primary way to retire a belief. Memories are **superseded** or **aged**; three clocks answer three different questions.

To put this in concrete terms, take the same sentences about a **cat**:

| Sentence | What you need to remember | Clocks and fields |
| --- | --- | --- |
| “I have a cat.” | True **for this person now** | **Valid time** includes today; scoped to the speaker |
| “I saw a cat last night.” | A **past** encounter, still tied to the speaker | **Valid time** on the night in question; **episodic** source turn |
| “House cats weigh about 4 kg.” | A **general** fact, not “about me” | Broader **scope**; may come from a document (**known time** = when you ingested it) |
| “I used to have a cat.” | Was true, **is not** now | **Supersession**: prior “has cat” gets `valid_until`; history stays queryable |

People do this without thinking. SurrealDB Agent Memory makes it explicit so agents - and auditors - can answer “what did we believe, when, and on what evidence?” without guessing from similar-looking chunks.

## The three clocks

| Clock | What it is | Answers | Example |
| --- | --- | --- | --- |
| **System time** | SurrealDB **MVCC** history - every write keeps a versioned record; you can reconstruct database state at an instant. | “What did the **database** contain at instant T?” | “What did we store about UK head of state on 8 September 2022?” (still “Prince Charles” in a snapshot taken that morning.) |
| **Known time** | When SurrealDB Agent Memory **first recorded** a belief; `as_of` walks supersession chains. | “When did **we** first believe this?” | “When did we first store that Charles became King Charles III?” (likely 8 September 2022, even if the user mentioned it days later.) |
| **Valid time** | When the fact held **in the real world**, via `valid_from` / `valid_until`. | “When was this **true**?” | Napoleon: valid time roughly 15 August 1769-5 May 1821; your assistant might only learn his birth year in a school chat years later - that learning moment is **known time**, not valid time. |

Document facts may carry a `valid_from` long before upload (e.g. policy effective date). Conversational facts usually anchor to turn time unless you supply otherwise.

## What you get in practice

- **Time-travel queries on previous database states** - without stitching together your own application tables.
- **`as_of` queries** - belief-level history over supersession chains, indexed for retrieval.
- **Supersession chains** - prior beliefs stay addressable with `valid_until` set; you can see how “Prince of Wales” became “King Charles III”.
- **Aging, not deletion** - stale observations can be marked superseded with a reason in provenance rather than erased.
- **`forget` as a first-class verb** - explicit user-driven removal, distinct from aging.

Together with [provenance](/docs/agent-memory/mental-model/provenance-and-traceability.md) and the single reconciler ([Eight pillars](/docs/agent-memory/architecture/eight-pillars-and-categories.md)), you can unwind any current fact to what it replaced, to the source that produced it, and to the exact quote in that source - and rewind the database by system time while still querying belief history on known time.

Field-level detail: [Temporal validity](/docs/agent-memory/reasoning/temporal-validity.md) and [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md).

---

Source: https://surrealdb.com/docs/agent-memory/ingest/authoritative/bulk-import

# Bulk import

Importing large quantities of documents or knowledge nodes into SurrealDB Agent Memory.

When populating a new Context or migrating from an existing system, you typically need to ingest many documents or knowledge nodes at once. SurrealDB Agent Memory's ingestion pipeline is designed for concurrent usage, and both the document upload endpoint and the triple-write path on `/facts` support high-throughput ingestion patterns.

## Uploading many documents

`POST /api/v1/{context_id}/documents` accepts one document per request. For bulk uploads, issue multiple requests concurrently and track their status independently.

### Concurrent upload pattern

```python
import asyncio
from surrealdb.memory import Memory

memory = Memory(context="acme-prod",
    api_key=os.environ["AGENT_MEMORY_API_KEY"])

files = [
    ("returns-policy.pdf", "Returns Policy"),
    ("shipping-guide.pdf", "Shipping Guide"),
    ("warranty-terms.pdf", "Warranty Terms"),
    ("product-manual.pdf", "Product Manual"),
]

async def upload_file(path, title):
    return await memory.documents.upload(
        path,
        title=title,
        scopes=["org/acme"],
    )

docs = await asyncio.gather(*[upload_file(path, title) for path,
    title in files])
doc_ids = [doc.id for doc in docs]
print(f"Queued {len(doc_ids)} documents")
```

```javascript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "acme-prod",
    apiKey: process.env.AGENT_MEMORY_API_KEY });

const files = [
    { path: "returns-policy.pdf", title: "Returns Policy" },
    { path: "shipping-guide.pdf", title: "Shipping Guide" },
];

const uploads = files.map(({ path, title }) =>
        memory.documents.upload(path, { title, scopes: ["org/acme"] })
);

const docs = await Promise.all(uploads);
const docIds = docs.map(d => d.id);
```

### Polling for completion

After queuing a batch, poll all document IDs until every document is `ready` or `failed`:

```python
async def wait_for_all(doc_ids):
    pending = set(doc_ids)
    failed = []

    while pending:
        await asyncio.sleep(5)
        for doc_id in list(pending):
            doc = await memory.documents.get(doc_id)
            if doc.status == "ready":
                pending.discard(doc_id)
                print(f"{doc_id}: ready")
            elif doc.status == "failed":
                pending.discard(doc_id)
                failed.append((doc_id, doc.error))
                print(f"{doc_id}: failed - {doc.error}")

    return failed

failed = await wait_for_all(doc_ids)
if failed:
    print(f"{len(failed)} documents failed processing")
```

### Rate limiting

SurrealDB Agent Memory applies rate limits per API key. If you are ingesting thousands of documents, introduce a semaphore to limit concurrency:

```python
semaphore = asyncio.Semaphore(10)  # max 10 concurrent uploads

async def upload_with_limit(path, title):
    async with semaphore:
        return await upload_file(path, title)

docs = await asyncio.gather(*[upload_with_limit(path, title) for path,
    title in files])
```

The recommended concurrency ceiling for standard deployments is 10-20 concurrent uploads. Self-hosted deployments can be tuned according to your infrastructure capacity.

## Bulk structured facts

For structured catalogue or policy data you already trust, use **`POST /api/v1/{context_id}/facts`** with `infer: "triples"` (or batch multiple utterances via **`/facts/batch`**). The reconciler persists entities, attributes, and relations in the unified graph with `source.kind = "document"` or operator-provided provenance.

```http
POST /api/v1/{context_id}/facts
Content-Type: application/json
Authorization: Bearer <key>

{
  "infer": "triples",
  "scopes": [["org/acme"]],
  "triples": [
    { "entity": { "type": "product", "name": "sku_001" },
      "key": "price", "value": "29.99" },
    { "entity": { "type": "product", "name": "sku_001" },
      "key": "belongs_to",
      "target": { "type": "category", "name": "widgets" } }
  ]
}
```

Response:

```json
{
  "mode": "triples",
  "sessionId": "sess_01hy…",
  "turnId": "turn_01hy…",
  "extraction": {
    "turnId": "turn_01hy…",
    "entities": [ { "id": "…", "name": "sku_001", "entityType": "product",
                    "memoryCategory": "knowledge", "isNew": true } ],
    "attributes": [ { "id": "…", "entityId": "…", "key": "price",
                      "value": "29.99", "memoryCategory": "knowledge" } ],
    "relations": [ { "subject": "product/sku_001", "label": "belongs_to",
                     "object": "category/widgets", "memoryCategory": "knowledge" } ],
    "instructions": [],
    "uncertainties": [],
    "corrections": []
  }
}
```

`infer: "triples"` takes the `triples` array directly and runs no LLM. `text` is
optional in this mode.

### Python

```python
# Build nodes and relations from your data source
nodes = [
        {"kind": "product", "slug": record["sku"],
        "title": record["name"], "content": record}
    for record in product_catalogue
]

relations = [
        {"in": ("product", record["sku"]), "out": ("category",
        record["category_slug"]), "label": "belongs_to"}
    for record in product_catalogue
    if record.get("category_slug")
]

# Upsert in batches of 1000
BATCH_SIZE = 1000
for i in range(0, len(nodes), BATCH_SIZE):
    batch_nodes = nodes[i:i + BATCH_SIZE]
    batch_relations = [r for r in relations if any(
        r["in"][1] == n["slug"] for n in batch_nodes
    )]
    triples = [
        {"entity": {"type": "concept", "name": n["slug"]},
         "key": "title", "value": n["title"]}
        for n in batch_nodes
    ] + [
        {"entity": {"type": "concept", "name": r["in"][1]},
         "key": r["label"],
         "target": {"type": "concept", "name": r["out"][1]}}
        for r in batch_relations
    ]
    result = await memory.remember(infer="triples", triples=triples)
    extraction = result["extraction"]
    print(
        f"Batch {i // BATCH_SIZE + 1}: "
        f"entities={len(extraction['entities'])} "
        f"attributes={len(extraction['attributes'])} "
        f"relations={len(extraction['relations'])}"
    )
```

### JavaScript

```javascript
const BATCH_SIZE = 1000;

for (let i = 0; i < nodes.length; i += BATCH_SIZE) {
    const batchNodes = nodes.slice(i, i + BATCH_SIZE);
    const triples = batchNodes.map((n) => ({
        entity: { type: "concept", name: n.slug },
        key: "title",
        value: n.title,
    }));
    const result = await memory.remember(null, { infer: "triples", triples });
    console.log(
        `Batch ${i / BATCH_SIZE + 1}: `
        + `entities=${result.extraction.entities.length} `
        + `relations=${result.extraction.relations.length}`,
    );
}
```

## Deduplication

Document uploads are automatically deduplicated by content hash. If the same file is submitted multiple times during a bulk import - for example, because a script is re-run after a partial failure - each duplicate returns the existing document ID with `deduplicated: true` and no reprocessing occurs.

Triple writes reconcile by entity identity: resubmitting a triple for the same `(type, name)` entity and key supersedes the previous value rather than creating a duplicate row.

These properties make bulk imports safe to re-run. A failed or interrupted import can be restarted from the beginning without creating duplicate records.

## Scope assignment on bulk imports

All documents uploaded in a bulk import share the same scope unless you specify it per-document. For mixed-scope imports - for example, some documents are org-level and others are user-level - structure your upload loop to set scope per file:

```python
async def upload_with_scope(item):
    return await memory.documents.upload(
        item["path"],
        title=item["title"],
        scopes=item["scopes"],
    )

items = [
        {"path": "handbook.pdf", "title": "Employee Handbook",
        "scopes": ["org/acme"]},
        {"path": "preferences.json", "title": "User Prefs",
        "scopes": ["org/acme/user/alice"]},
]

docs = await asyncio.gather(*[upload_with_scope(item) for item in items])
```

## Monitoring a large import

For imports of tens of thousands of documents, track overall progress by listing documents with a status filter:

```python
async def import_progress():
    ready = await memory.documents.list(status="ready")
    queued = await memory.documents.list(status="queued")
    failed = await memory.documents.list(status="failed")

    print(f"Ready: {len(ready)}  Queued: {len(queued)}  Failed: {len(failed)}")
```

`GET /documents` filters on `status` and `mimeType` and pages with `page` /
`pageSize`. It has no scope filter - the caller's read region already bounds what
comes back.

Failed documents should be inspected individually to determine whether the failure is transient (pipeline overload) or permanent (corrupt file, unsupported format):

```python
failed_docs = await memory.documents.list(status="failed")
for doc in failed_docs:
    full = await memory.documents.get(doc.id)
    print(f"{doc.id}  {full.title}  {full.error}")
```

---

Source: https://surrealdb.com/docs/agent-memory/ingest/authoritative/knowledge-nodes

# Structured fact ingest

Uploading structured facts alongside documents.

Use these paths when your data is already structured:

| Pattern | Approach |
| --- | --- |
| Product catalogue, policy fields | Upload JSON/CSV as **documents**, or `POST /facts` with `infer: "triples"` |
| Stable external keys | `entity` type + name in the unified graph; scope paths for tenancy |
| Graph edges between records | `relation` records between entities |

## Documents vs structured facts

- **Unstructured text** (PDF, Markdown, HTML): `POST /api/v1/{ctx}/documents` - async chunking and extraction.
- **Structured triples you already trust**: `POST /api/v1/{ctx}/facts` with `infer: "triples"`.
- **Conversational extraction**: `POST /api/v1/{ctx}/facts/batch` or `spectron remember`.

## Cross-layer linking

See [Cross-layer linking](/docs/agent-memory/reasoning/cross-layer-linking.md).

---

Source: https://surrealdb.com/docs/agent-memory/ingest/authoritative/multimodal-content

# Multimodal content

Ingesting images, audio, and video into the SurrealDB Agent Memory knowledge layer.

SurrealDB Agent Memory can ingest more than text. Images, audio recordings, and video files are all first-class document types in authoritative knowledge. Depending on the ingestion profile you select, it will apply OCR, speech-to-text transcription, and visual embedding to extract retrievable content from non-textual sources.

## Ingestion profiles

`Context.config.ingestion_profile` selects which provider-bearing stages run. Profiles are monotonic (each richer profile is a strict superset). Stages whose provider is absent are skipped, not errored.

| Profile | Text + chunks + embeds | Keywords + sections + doc links | OCR + speech-to-text | CLIP + vision caption + audio/video |
|---|---|---|---|---|
| `TextOnly` | Yes | No | No | No |
| `TextPlusKeyword` | Yes | Yes | No | No |
| `StandardMultimodal` | Yes | Yes | Yes | No |
| `MultimodalFull` | Yes | Yes | Yes | Yes |

**Default: `MultimodalFull`.** Uploaded images and audio get content understanding (vision caption, CLIP, audio embeddings) when the backing providers are configured. Dial down to `StandardMultimodal`, `TextPlusKeyword`, or `TextOnly` to reduce cost.

Wire names are PascalCase as in the table (`MultimodalFull`, not `MultimodalFull`).

Specify the profile on the Context configuration (or at upload when your client supports it):

```python
doc = await memory.documents.upload(
    file=open("product-diagram.png", "rb"),
    title="Product Architecture Diagram",
    profile="StandardMultimodal",
    scope=["org/acme"],
)
```

```javascript
const doc = await memory.documents.upload({
    file: imageFile,
    title: "Product Architecture Diagram",
    profile: "StandardMultimodal",
    scope: ["org/acme"],
});
```

## Supported formats

### Images

| Format | MIME type | Notes |
|---|---|---|
| PNG | `image/png` | Lossless; recommended for diagrams and screenshots |
| JPEG | `image/jpeg` | Suitable for photographs |
| WebP | `image/webp` | Modern format; lossless or lossy |
| GIF | `image/gif` | Static and animated; first frame used for embedding |

### Audio

| Format | MIME type |
|---|---|
| WAV | `audio/wav` |
| MP3 | `audio/mpeg` |
| OGG | `audio/ogg` |
| FLAC | `audio/flac` |
| AAC | `audio/aac` |

### Video

| Format | MIME type |
|---|---|
| MP4 | `video/mp4` |
| WebM | `video/webm` |
| MOV | `video/quicktime` |

## OCR

OCR (optical character recognition) recognises printed and handwritten text in images and scanned PDFs. The recognised text is extracted, split into chunks, and embedded in exactly the same way as text from a native PDF or HTML document. Chunks derived from OCR carry a `source: "ocr"` annotation in their metadata.

OCR is available with `StandardMultimodal` and `MultimodalFull` profiles.

### Scanned and low-text PDFs

When a PDF has **no extractable text layer** (image-only / scanned pages), SurrealDB Agent Memory rasterises pages and runs OCR when the **`local-ml-providers`** feature and an OCR provider are configured. Without OCR, ingest **fails loudly** with an actionable error - the document never lands in **`ready`** with an empty body.

For **partially scanned** PDFs (a sparse text layer on some pages), a low-text trigger rasterises and OCRs all pages when the recovered text exceeds what the native layer provided - all-or-nothing per document, not per-page splicing.

### Embedded PDF images

PDFs with embedded JPEG images (including common filter chains such as ASCII85/Hex + DCTDecode) extract image bytes and run the same OCR, vision, and CLIP path as standalone image uploads. OCR and vision text is folded into the chunk spine before embedding; visual embeddings land on **`image_chunk`** rows linked to the page's **`knowledge_section`**.

```python
doc = await memory.documents.upload(
    file=open("scanned-invoice.pdf", "rb"),
    title="Invoice 1042",
    profile="StandardMultimodal",
    scope=["org/acme"],
)
```

Pipeline stages during OCR processing include `extracting` and `rendering`. Once the document is `ready`, its chunks are searchable by text query.

## Speech-to-text

For audio and video documents, SurrealDB Agent Memory generates a full transcript and appends it to the chunk body so the spoken content is retrievable by semantic and keyword search. In addition to the full transcript, the pipeline produces time-coded `audio_chunk` segments that record approximately which part of the recording a retrieved chunk came from.

An `audio_chunk` segment looks like:

```json
{
  "chunk_id": "chunk:01hy2…",
  "start_ms": 14200,
  "end_ms": 28700,
  "text": "The return window for unopened items is thirty days from purchase."
}
```

This lets your application link a retrieved fact back to the precise moment in a recording - useful for surfacing relevant clips or timestamped citations.

Transcription is available with `StandardMultimodal` and `MultimodalFull` profiles.

Transcript segments participate in **vector recall** alongside text chunks (same embedding model and ranker leg). A query can return an `audio_chunk` hit with the segment text and timestamps even when the parent document chunk spine phrased the content differently.

When a document is **reprocessed**, previous `audio_chunk` and `image_chunk` rows are cleared at the start of the run before modality stages execute, so stale transcript or frame rows cannot survive a profile or content change.

```python
doc = await memory.documents.upload(
    file=open("support-call.mp3", "rb"),
    title="Support Call 2026-05-12",
    profile="StandardMultimodal",
    scope=["org/acme/user/alice"],
)
```

The pipeline status will pass through `transcribing` before reaching `ready`.

## CLIP visual embeddings

For image content processed under `MultimodalFull`, SurrealDB Agent Memory generates a CLIP visual embedding alongside any OCR text. CLIP embeddings capture the semantic content of the image independently of its textual labels, enabling retrieval by visual similarity.

When a query is issued against a context that includes images, the CLIP embeddings participate in the vector search alongside text embeddings. A query such as "product dimension diagram" can retrieve a relevant engineering drawing even if that drawing contains no recognisable text.

## Video captions

Under `MultimodalFull`, SurrealDB Agent Memory generates per-frame captions for video documents. Captions are indexed as additional text chunks associated with the video document. This allows text-based queries to retrieve video content described by the visual scene, not only by the spoken transcript.

Video caption extraction is the most resource-intensive stage and is therefore opt-in, available only with `MultimodalFull`.

## HTTP provider integration

By default, SurrealDB Agent Memory uses built-in or feature-gated local OCR and speech-to-text providers. For deployments with existing multimodal infrastructure, configure **deployment-level** HTTP endpoints via environment variables (same pattern as `SPECTRON_RERANKER_*`):

| Modality | Variables |
| --- | --- |
| OCR | `SPECTRON_OCR_URL`, `SPECTRON_OCR_MODEL`, `SPECTRON_OCR_API_KEY` (optional) |
| CLIP (visual embeddings) | `SPECTRON_CLIP_URL`, `SPECTRON_CLIP_MODEL`, `SPECTRON_CLIP_API_KEY` (optional) |
| Speech-to-text | `SPECTRON_STT_URL`, `SPECTRON_STT_MODEL`, `SPECTRON_STT_API_KEY` (optional) |

When a URL is set, its **`_MODEL`** is required and SurrealDB Agent Memory calls the endpoint at ingest time (worker role). HTTP providers take precedence over local fallbacks. CLIP output must match the **512-dim** image embedding width - dimension mismatch is rejected at embed time.

**Vision LLMs:** under **`MultimodalFull`**, image understanding runs when the worker resolves an extraction LLM for the Context - per-Context **`models.extraction`** and provider keys apply even without a deployment-wide default LLM. Vision descriptions are appended to the document body **before chunking** (same spine as OCR text). OpenAI and Anthropic providers send image content blocks when media is attached.

**Per-Context ingest LLM:** document jobs resolve the extraction LLM from the Context config on each run (shared budget-enforced resolver with maintenance jobs). A Context with its own provider key but no deployment-wide `SPECTRON_LLM_PROVIDER` gets ingest-time extraction and vision when configured.

See [Configuration](/docs/agent-memory/reference/configuration.md#multimodal-providers-optional) for the full variable list.

## Checking pipeline progress

Multimodal documents take longer to process than text-only uploads. The `status` field reflects the current pipeline stage precisely, so you can surface progress information in your application:

```python
import asyncio

stages = []

while True:
    doc = await memory.documents.get(doc.id)
    if doc.status not in stages:
        stages.append(doc.status)
        print(f"Stage: {doc.status}")

    if doc.status in ("ready", "failed"):
        break

    await asyncio.sleep(3)
```

Example output for a video document processed with `MultimodalFull`:

```text
Stage: queued
Stage: extracting
Stage: transcribing
Stage: captioning
Stage: chunking
Stage: embedding
Stage: keywording
Stage: rendering
Stage: ready
```

## PII redaction

PII redaction can be enabled per-Context and applies to all ingested content, including OCR-recognised text and speech transcripts, before chunking. See [Extraction tuning](/docs/agent-memory/reference/configuration.md#extraction-tuning) for the **`pii_redaction_enabled`** field.

---

Source: https://surrealdb.com/docs/agent-memory/ingest/authoritative/uploading-documents

# Uploading documents

How to ingest documents into the SurrealDB Agent Memory knowledge layer.

The **knowledge** layer holds **authoritative** material - manuals, policies, product data, and files your agents should treat as curated sources. Documents enter through an asynchronous upload pipeline: bytes land in object storage, then SurrealDB Agent Memory extracts, chunks, embeds, and indexes structured state in SurrealDB.

Document extraction uses the same structured schema and reconciler as conversational ingest ([Storing memories](/docs/agent-memory/ingest/experiential/remember.md)). Facts from a PDF and facts from a turn land in one substrate; prefer documents for long curated sources and `/facts` for short lived moments.

## Supported formats

### Text-first formats

These MIME types are accepted on **`POST /api/v1/{context_id}/documents`** under every ingestion profile:

| Format | MIME type |
| --- | --- |
| Plain text | `text/plain` |
| Markdown | `text/markdown` |
| JSON | `application/json` |
| HTML | `text/html` |
| PDF | `application/pdf` |

### Multimodal formats

The upload endpoint also accepts image, audio, and video MIME types. The default Context profile is **`MultimodalFull`**, so OCR, transcription, captioning, and modality-native embeddings run when providers are configured. Dial the Context down to **`StandardMultimodal`**, **`TextPlusKeyword`**, or **`TextOnly`** to skip richer stages:

| Format | MIME types (examples) |
| --- | --- |
| Images | `image/png`, `image/jpeg`, `image/webp`, `image/gif` |
| Audio | `audio/wav`, `audio/mpeg`, `audio/ogg`, `audio/flac`, `audio/aac` |
| Video | `video/mp4`, `video/webm`, `video/quicktime` |

Under **`TextOnly`**, multimodal uploads may be accepted but image/audio/video processing stages are skipped. See [Multimodal content](/docs/agent-memory/ingest/authoritative/multimodal-content.md) for profile details.

## Uploading a document

The upload endpoint is asynchronous. It returns **`202 Accepted`** with a document id and initial status; processing continues on the worker tier.

### REST

```http
POST /api/v1/{context_id}/documents
Content-Type: multipart/form-data

file=<binary>
metadata={"title":"Returns Policy","scopes":[["org/acme/team/eng"]],"labels":["team=eng"]}
```

The `metadata` part is JSON. **`scopes`** is a DNF selector (OR of conjunctive slash-path clauses). **`labels`** are descriptive `key=value` tags (same validation as fact ingest - keys must not start with `_`; count caps return **`409`**). Optional **`observedAt`** (RFC 3339) sets the known time of facts derived from this document - essential for **page-by-page or episode-by-episode ingest** where later plot points must stay hidden until the reader reaches them ([spoiler-safe narrative memory](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md)). Omit **`scopes`** to tag the document with the caller's full `memory:write` region.

> [!NOTE]
> **`observedAt` is caller-supplied metadata** - SurrealDB Agent Memory does not scan document headers or body text to infer a narrative timeline automatically. For serial fiction or journals, split uploads per chapter or episode and set **`observedAt`** (or ingest turns with **`observed_at`**) explicitly. Bulk single-file upload stamps derived facts at ingest time unless you provide the field.

Response:

```json
{
  "id": "doc:01hx9…",
  "status": "queued",
  "content_hash": "blake3:4f3c…",
  "deduplicated": false
}
```

### CLI

```bash
spectron documents upload ./returns-policy.pdf \
  --scope org/acme/team/eng \
  --label team=eng \
  --url "$SPECTRON_URL" \
  --api-key "$SPECTRON_API_KEY" \
  --context-id "$SPECTRON_CONTEXT_ID"
```

Use the generated OpenAPI clients ([Python](/docs/agent-memory/integrations/sdks/python.md), [TypeScript](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md)) for application code - method names follow the spec.

## Polling for status

Poll **`GET /api/v1/{context_id}/documents/{id}`** until **`status`** is **`ready`** or **`failed`**.

## Pipeline stages

| Status | Description |
| --- | --- |
| `queued` | Waiting to enter the pipeline |
| `extracting` | Reading content from the uploaded bytes |
| `chunking` | Splitting content into overlapping segments |
| `embedding` | Generating dense vectors for chunks |
| `rendering` | Building document summaries and section metadata |
| `transcribing` | Transcribing audio or video (multimodal profiles) |
| `captioning` | Generating captions for images (multimodal profiles) |
| `keywording` | RAKE keyword extraction |
| `ready` | Fully indexed and available for retrieval |
| `failed` | Pipeline error; inspect `error` on the document record |

Oversized chunk persists (SurrealDB transaction write-set or WebSocket message caps) are classified as permanent size errors and dead-letter without burning multi-minute re-parse retries. Operators tuning large corpora should keep the client WS cap (`SPECTRON_DB_WS_MAX_MESSAGE_BYTES`) aligned with the SurrealDB server's `SURREAL_WEBSOCKET_MAX_MESSAGE_SIZE` - see [Configuration](/docs/agent-memory/reference/configuration.md#request-and-list-limits).

## Content addressing and deduplication

Every document is identified by a [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) hash of its raw bytes. Re-uploading identical content returns the existing document with **`deduplicated: true`** and skips reprocessing.

When a **second uploader in a different scope** hits the same hash, SurrealDB Agent Memory **unions their scope clause** onto the existing document (and related index records) instead of trapping them with a deduplicated id they cannot read. Each union emits a **`document.scope_widen`** audit event; operators can watch the **`documents.scope_clause_count`** histogram (values above ~32 clauses on one document warrant investigation).

## Outbound links

During ingest, SurrealDB Agent Memory extracts outbound hyperlinks from the raw bytes and stores them as typed **`knowledge_links_to`** edges:

| Source | Link kind |
| --- | --- |
| Markdown `[]()` syntax | `markdown_link` |
| HTML `<a href="…">` attributes | `html_link` |
| PDF page link annotations | `pdf_annotation` |

These edges feed **`hybrid_graph`** reranking (document-link density) and citation-style navigation between corpus documents.

## Scope and labels

Documents and their chunks inherit the caller's **resolved write scope** from the API key when you omit explicit scope on upload - required for scoped keys to recall their own uploads.

You can **narrow** tagging with **`scopes`** on **`POST /documents`**, **`spectron documents upload --scope …`**, or MCP **`upload`** - the path must lie within the caller's `memory:write` region (out-of-region scope returns **`403`**). A document's scope is **fixed at upload** - reprocess rejects a non-empty `scopes` field with **`400`**.

Optional **`labels`** (`key=value` strings) are stamped on the document, chunks, and sections. They follow the same validation rules as fact ingest and are **not** copied onto reconciled graph rows.

> [!NOTE]
> **MCP `upload`** accepts optional **`scope`** and **`labels`** arguments with the same semantics as REST - scoped keys produce scope-tagged documents and chunks visible under `memory:read` within that region.

## Document management

- **`GET /documents/{id}`** - status and metadata
- **`GET /documents`** - list with filters
- **`GET /documents/{id}/chunks`** - parsed segments
- **`GET /documents/{id}/raw`** - original bytes
- **`DELETE /documents/{id}`** - remove document, chunks, graph edges, and object-store bytes

See [REST API](/docs/agent-memory/reference/rest-api.md) for request shapes.

---

Source: https://surrealdb.com/docs/agent-memory/ingest/experiential/remember

# Storing memories

Ingesting facts and conversations into the unified substrate.

SurrealDB Agent Memory stores memory by **extracting** structured entities, attributes, and relations from text - not by accepting opaque key-value blobs. Every write runs through the reconciler (calibration, supersession, uncertainty on conflict).

Conversation turns (`/facts`, `/facts/batch`, `/chat`) and document uploads share one extraction contract: the same JSON schema, confidence and temporal fields, and reconciler. Adapters supply the input kind and trust posture (spoken turn vs curated document); they do not invent parallel entity-type vocabularies. That keeps a person named in a policy PDF and the same person named in chat as one node when normalisation matches.

Register scope paths before first write:

```bash
spectron scopes create org/acme
spectron scopes create org/acme/user/alice
```

## Primary write paths

| Path | Use when |
| --- | --- |
| `POST /api/v1/{ctx}/facts` | Single utterance, raw triples (`infer: "triples"`), or literal (`infer: "none"`) |
| `POST /api/v1/{ctx}/facts/batch` | Full conversation transcript in one request (preferred for harnesses) |

`source.kind` on persisted records is typically `"turn"` for conversational ingest and `"document"` for upload pipeline output.

## CLI

**Bash**

```bash
export SPECTRON_URL=http://localhost:9090
export SPECTRON_API_KEY=...
export SPECTRON_CONTEXT_ID=dev

spectron remember "Alice was promoted to CTO." --scope org/acme/user/alice

spectron remember "The reveal happened on page 42." --scope org/acme --as-of 1886-01-01T00:00:00Z

spectron remember --from-file ./transcript.jsonl --scope org/acme/user/alice --extract per_message
```

**PowerShell**

```powershell
$env:SPECTRON_URL = "http://localhost:9090"
$env:SPECTRON_API_KEY = "..."
$env:SPECTRON_CONTEXT_ID = "dev"

spectron remember "Alice was promoted to CTO." --scope org/acme/user/alice

spectron remember "The reveal happened on page 42." --scope org/acme --as-of 1886-01-01T00:00:00Z

spectron remember --from-file ./transcript.jsonl --scope org/acme/user/alice --extract per_message
```

Flags: `--infer full|triples|preview|none`, `--session`, `--transcript`, `--extract whole_conversation|per_message` (batch only).

> [!NOTE]
> The CLI does **not** accept `--confidence`, `--trust`, or `--location` on `remember` - calibration and trust come from the reconciler and source metadata.

## HTTP - single fact

```http
POST /api/v1/{context_id}/facts
Authorization: Bearer <key>
Content-Type: application/json

{
  "text": "Alice was promoted to CTO.",
  "infer": "full",
  "scopes": [["org/acme/user/alice"]],
  "observed_at": "2026-05-12T10:00:00Z"
}
```

Optional **`observed_at`** (RFC 3339) stamps derived facts at a caller-supplied **known time** instead of wall-clock ingest. Use this for **narrative playback** - ingest an entire canon but answer only as far as the user has read or watched (see [Spoiler-safe narrative memory](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md)). Omitted keeps the default (ingest time).

**Response (`infer: full`):** nested **`extraction`** with entities, attributes, relations, instructions, and uncertainties, plus `sessionId` and `turnId`.

**Response (`infer: none`):** `chunkId`, `sessionId`, and `turnId` for the literal memory chunk. Long text is split into multiple **`memory_chunk`** rows (one embedding per sub-chunk) so vector recall covers the full passage - `chunkId` returns the first row’s id.

## HTTP - batch

```http
POST /api/v1/{context_id}/facts/batch
Authorization: Bearer <key>
Idempotency-Key: stable-conversation-id
Content-Type: application/json

{
  "messages": [
    { "role": "user", "content": "I was promoted to CTO." },
    { "role": "assistant", "content": "Congratulations!" }
  ],
  "scopes": [["org/acme/user/alice"]],
  "extract": "whole_conversation"
}
```

`extract`: **`whole_conversation`** (default) sends the full transcript in one LLM pass; **`per_message`** runs extraction once per message.

Returns **`extractions`**, **`sessionId`**, and **`turnIds`**.

## SDK (Python)

```python
# from surrealdb.memory import Memory - see Python SDK guide
await client.remember(
    text="Alice was promoted to CTO.",
    infer="full",
    scope=[["org/acme/user/alice"]],
)
```

## Sessions

Sessions group episodic **turns** for transcript browsing and session-scoped context. Creating a session is optional when using `/facts/batch` with an explicit `session_id`.

```http
POST /api/v1/{context_id}/sessions
{ "scopes": [["org/acme/user/alice"]] }
```

See [Sessions and turns](/docs/agent-memory/mental-model/sessions-and-turns.md).

## Authoritative content

Manuals, policies, and files use the **document** path, not `/facts`:

```bash
spectron documents upload ./policy.pdf
```

See [Knowledge hub](/docs/agent-memory/memory-and-knowledge.md).

## What you do not do

- Write arbitrary JSON blobs to a “memory table”
- Use removed `/knowledge/nodes` APIs

Extraction, reconciliation, and provenance are described in [Extraction pipeline](/docs/agent-memory/reasoning/extraction-pipeline.md) and [Provenance and traceability](/docs/agent-memory/mental-model/provenance-and-traceability.md).

---

Source: https://surrealdb.com/docs/agent-memory/memory-and-knowledge

# Memory & knowledge

Ingest, retrieve, reason, and tune agent memory. All on SurrealDB Agent Memory's unified substrate.

SurrealDB Agent Memory stores authoritative and experiential material in one SurrealDB graph - the Authoritative and Experiential pillars from [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md). This section is organised by what you do (ingest, retrieve, reason, tune), not by two separate products.

- **Authoritative** - manuals, policies, product data, structured uploads (`source.kind = "document"`, higher default trust).
- **Experiential** - conversation, sessions, and derived facts (`source.kind = "turn"`, reflections, elaborations, consolidations).

Start with [Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md) and [Memory categories](/docs/agent-memory/mental-model/memory-categories.md) (the six experiential types, including chat-extracted **knowledge** - distinct from authoritative uploads).

## Ingest

**Authoritative**

- [Uploading documents](/docs/agent-memory/ingest/authoritative/uploading-documents.md), [Bulk import](/docs/agent-memory/ingest/authoritative/bulk-import.md)
- [Multi-modal content](/docs/agent-memory/ingest/authoritative/multimodal-content.md), [Knowledge nodes](/docs/agent-memory/ingest/authoritative/knowledge-nodes.md)

**Experiential**

- [Remember](/docs/agent-memory/ingest/experiential/remember.md) - `POST /facts` and `/facts/batch`
- [Sessions](/docs/agent-memory/sessions/chat-sessions.md) - containers for turns ([creating](/docs/agent-memory/sessions/creating-sessions.md), [adding turns](/docs/agent-memory/sessions/adding-turns.md))

## Retrieve

Unified read path over facts **and** document passages:

- [Recalling memories](/docs/agent-memory/retrieve/recall.md) - `/query`, `/context`, `/chat`
- [Hybrid search](/docs/agent-memory/retrieve/hybrid-search.md), [Keywords and BM25](/docs/agent-memory/retrieve/keywords-and-bm25.md), [Graph traversal](/docs/agent-memory/retrieve/graph-traversal.md)

HTTP tables: [REST API](/docs/agent-memory/reference/rest-api.md) and [Surface, models, and security](/docs/agent-memory/architecture/surface-security-and-models.md).

## Reasoning

How writes are extracted, reconciled, and time-stamped - for **both** documents and turns:

- [Extraction pipeline](/docs/agent-memory/reasoning/extraction-pipeline.md)
- [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md)
- [Authority when pillars meet](/docs/agent-memory/reasoning/authority-hierarchy.md), [Cross-layer linking](/docs/agent-memory/reasoning/cross-layer-linking.md)
- [Temporal validity](/docs/agent-memory/reasoning/temporal-validity.md), [Instructions and uncertainties](/docs/agent-memory/reasoning/instructions-and-uncertainties.md)

## Operations & tuning

- [Reflect](/docs/agent-memory/operations/reflect.md), [Forget](/docs/agent-memory/operations/forget.md), [Profiles](/docs/agent-memory/operations/profiles.md)
- [Models per stage](/docs/agent-memory/tuning/models-per-stage.md), [Caching and invalidation](/docs/agent-memory/tuning/caching-and-invalidation.md), [Extraction vocabulary](/docs/agent-memory/tuning/ontology-grounding.md)

---

Source: https://surrealdb.com/docs/agent-memory/mental-model/contexts-and-scope

# Contexts and scope

How scope tags partition memory, and how Contexts isolate tenants. Both operate within a Context.

SurrealDB Agent Memory uses two complementary mechanisms:

- **Contexts** - hard isolation between separate products or deployments (each with its own database and keys).
- **Scope** - tags on memory **records** that partition data within a Context (org, user, project, and so on).

Scope is not the same as permissions. Scope says which bucket a fact belongs to. [Principals and grants](#permissions-and-delegation) say who may read or write at which scope paths. Together they define what each caller is allowed to see. A principal may delegate grants to another principal, but only up to what they already hold - you cannot grant access you do not have.

## Contexts

A **Context** is the fundamental unit of isolation. Each Context maps to its own SurrealDB `(namespace, database)` pair holding documents, turns, entities, traces, and configuration. Nothing crosses Context boundaries.

Typical reasons for separate Contexts:

- Separate products with no shared memory
- Distinct business units
- Test versus production environments

### Context or scope?

Use **scope** when records belong to the same product world and you only need to *partition* them (by user, project, tenant path, time folder, and so on). Grants then decide who sees which paths.

Use a **new Context** when isolation must be absolute, namely when nothing from A should ever appear in B’s `/query`, `/chat`, or profile, even by accident.

| Prefer… | When… |
| ------- | ----- |
| **Scopes** | Same app, same “who is speaking,” shared models and config; you need folders (org/user/project) or temporary lenses |
| **New Context** | Different products, environments (staging vs prod), or **personas** that must not share a profile |
| **New Context** | Memory has become polluted (operator chat mixed with character memory, eval probes drowning testimony) and forgetting individual paths is harder than starting clean |
| **New Context** | You need a clean eval / demo run with reproducible prior-memory seeds |

Scopes do **not** give you a second profile or a second response-cache. Chat turns, instructions, and consolidation still live in one Context-wide substrate. If Playground answers as the wrong persona, or retrieval is dominated by earlier probe denials, registering another scope path will not fix that - create a fresh Context (and new API keys), re-seed what you still want, and leave the old Context as a regression artefact if useful.

A practical rule: **scope partitions data; a Context partitions worlds.** When in doubt for demos and product testing, prefer a new Context over trying to “scope away” cross-talk.

Contexts are created through the management API:

```http
POST /api/v1/contexts/{context_id}
Content-Type: application/json

{
  "namespace": "acme",
  "database": "prod",
  "description": "Customer support agent for Acme Corp"
}
```

API keys are issued **per Context** - a key for `acme-prod` cannot access another Context.

## Scope tags (data partitioning)

Within a Context, memory records carry **scope** as an **OR of conjunctive clauses** - each clause names one or more hierarchical scope paths that must **all** apply together (AND within the clause, OR across clauses). Typical single-owner facts use one clause; **co-ownership** uses multiple clauses so more than one principal can read the same record.

You still choose dimensions that match your app: `org`, `user`, `team`, `project`, `region`, `env`, and so on - expressed as paths like `org/acme/user/alice/`.

### How visibility works

On each read, SurrealDB Agent Memory resolves your **grant** into the scope nodes you may cover, then finds which **scope-set clauses** on each record are fully satisfied. A record is visible when **at least one** clause matches (fail-closed: empty or unmatched scope sees nothing).

For the common case - a single clause with one or two tags - behaviour matches intuitive **subset** semantics:

| Record clause (simplified) | Query / grant covers | Visible? |
| --- | --- | --- |
| `{org/acme}` only | `{org/acme, user/alice}` | Yes - org-wide fact visible in user queries |
| `{org/acme, user/alice}` | `{org/acme}` only | No - user-specific fact hidden from org-only reads |
| `{org/acme, user/alice}` | `{org/acme, user/alice}` | Yes |
| `{org/other}` | `{org/acme, user/alice}` | No |

**Co-ownership:** a document deduplicated across two uploaders may carry **two clauses** - one per owner - so both can read the same content-addressed file without a separate copy.

Org-wide facts appear in user-level queries; user-specific facts do not appear in org-only queries.

### Wire format (`ScopeSets`)

On the wire, scope selectors use **disjunctive normal form** - an OR of conjunctive clauses:

| Intent | `scopes` / `lens` value |
| --- | --- |
| Tag or read at one path | `[["org/acme/user/alice"]]` - or a bare string `"org/acme/user/alice"` (accepted as a singleton clause) |
| Co-own across two owners (OR) | `[["org/acme/user/alice"], ["org/acme/user/bob"]]` |
| Require two paths together (AND) | `[["org/acme", "org/acme/team/eng"]]` |

- **Writes** use **`scopes`** on facts, sessions, uploads, and MCP tools. The legacy field name **`scope`** is still accepted as an alias.
- **Reads** use **`lens`** on `/query`, `/context`, and MCP recall - same DNF shape, but the lens **filters by involvement** within your grant; it never widens access.

> [!WARNING]
> **Breaking change (pre-GA):** a flat two-element array such as `["org/acme", "project/support"]` now means **OR** (`org/acme` **or** `project/support`), not AND. To express AND, nest the paths in one inner array: `[["org/acme", "project/support"]]`.

### Example queries

**As a specific user** (read lens):

```json
{ "lens": [["org/acme/user/alice"]] }
```

Matches org-wide facts at `org/acme` and user-specific facts at `org/acme/user/alice` when your grant covers both.

**At org level:**

```json
{ "lens": [["org/acme"]] }
```

Matches org-wide memory only - not Alice’s private rows unless your grant includes her path.

Register paths before first use: `spectron scopes create org/acme/user/alice`.

### Default write region

When a write omits an explicit **`scopes`** selector - Playground chat, a document upload without metadata scopes, or `remember` without `--scope` - SurrealDB Agent Memory tags the new records with the caller's resolved **`memory:write`** region (the scope paths your key or brokered token already covers).

Those facts are real and queryable immediately. The **Scopes** UI lists registered vocabulary paths; an empty tree does **not** mean no memory exists yet - it means you have not registered named paths for navigation. Use **Memory** or **`GET /profile`** to inspect what was stored under your principal's region, then register paths such as `org/acme/user/alice` when you want hierarchical partitioning.

On SurrealDB Studio, a new context's Overview count of **one scope** is usually that root write region. Register additional paths (for example `surrealdb/employee_1` and `surrealdb/employee_2`) when you want explicit folders for grants and uploads - see [SurrealDB Agent Memory on SurrealDB Cloud](/docs/agent-memory/quickstarts/surrealdb-cloud.md).

### Tombstone vs erase

Two destructive operations sound similar but differ:

| Operation | What it affects | Reversible? |
| --- | --- | --- |
| **Tombstone scope node** (`scope:delete`, Scopes UI) | The **folder entry** in the scope vocabulary | Soft-delete - node marked tombstoned; facts may still exist until erased separately |
| **Scoped forget** (`POST /scopes/forget`) | **All facts** tagged under a scope subtree | **No** - permanent erasure for compliance |

See [Forgetting memories](/docs/agent-memory/operations/forget.md#scoped-forget---erase-a-whole-subtree).

## Permissions and delegation

Scope tags partition **data**. **Grants** partition **access**. Four things sit
between a request and a stored fact, and each answers a different question:

| Term | Answers | Changes when |
| --- | --- | --- |
| **API key** | Which credential is calling, and how narrow is it? | You rotate, revoke, or attenuate a credential |
| **Principal** | Which identity does that key act as? | Rarely - it is the identity recorded on every write |
| **Grants** | Which verbs may that identity use, and where? | You widen or narrow access |
| **Scope** | Where does the knowledge live? | You register new paths |

Every key is bound to exactly one principal - there are no unbound keys. The key
authenticates, the principal supplies the grants, and the grants name scope
paths. So a write resolves as **key → principal → grants → scope**, and the fact
is tagged with the scope region that chain lands on.

A key can carry **less** access than the principal it acts as, never more. Key
grants must be a subset of the bound principal's on every mint path, and a key's
[scope floor](/docs/agent-memory/reference/glossary.md#scope-floor) sets the minimum
paths its requests must include: a key floored at `org/acme` cannot ask about
`org/beta`, whatever the principal is allowed.

Keys and principals are separate for a practical reason: they change at different
rates. One principal can hold several keys - a laptop, a CI runner, a service.
Revoke one key and the others keep working, with no change to access. Widen a
principal's grants and every key it holds follows, with no re-issue.

- A principal receives grant verbs (`memory:read`, `memory:write`, `grant:manage`, …) on scope paths.
- Granting to someone else can only convey **part or all of what you already have** - not broader access.
- API keys bind a principal to a scope **floor**; the server clamps requests that try to escalate.
- The principal - not the key - is what provenance records, which is why `principal:` ids appear throughout stored facts and request logs.

### Exact node or subtree

A grant names either **one node** or **a node and everything under it**. The
trailing `/*` is what separates them, and paths are hierarchical only where a
pattern says so:

| Pattern | Covers | Does not cover |
| --- | --- | --- |
| `org/acme` | that node alone | `org/acme/team/eng` |
| `org/acme/*` | that node and every descendant | `org/beta` |
| `*` or `/*` | every scope in the Context | - |

A `*` anywhere other than the end is rejected.

> [!WARNING]
> A grant on a parent alone does **not** cover its children. `memory:read=org/acme`
> plus `memory:write=org/acme/team/eng` writes rows the same principal cannot
> read back, and the symptom is quiet: scoped reads return **empty**, not
> refused, so a client that polls for its own writes waits instead of failing.
> Use `org/acme/*` when you mean the region.

> [!NOTE]
> Register a scope path with `spectron scopes create` before a write targets it.
> Creating paths is its own verb (`scope:create`), so the identity that registers
> the vocabulary is often not the one that writes into it.

See [Surface, models, and security](/docs/agent-memory/architecture/surface-security-and-models.md#security-and-privacy) for how keys and grants are enforced at the request surface.

## Geographic scope

Scope dimensions can include **geometry** (points, polygons) for territory-based partitioning - e.g. a service region polygon. Spatial predicates compose with semantic and graph signals in the same [ranker](/docs/agent-memory/architecture/coherence-retrieval-and-tiers.md).

## Use cases

**Multi-tenancy:** One Context, one API key per customer org scoped to `org/<id>/`.

**Per-user isolation:** Tag interactions with `user/<id>/` so personal memory stays private unless the query includes that user path.

**Shared org knowledge:** Tag policies with `org/acme/` only; tag personal notes with `org/acme/user/alice/`.

**Project context:** Add a `project/…` segment so project-specific instructions surface only in that project’s scope.

## Labels, lens, and scope views (reads)

Recall and context endpoints accept optional parameters beyond flat scope tags:

- **`labels`** - descriptive `key=value` tags (for example `subject=alice`) that **filter** results within what scope and permissions already allow. Labels help you find facts *about* something; they do not grant access by themselves.
- **`lens`** - optional hierarchical scope paths that **filter by involvement** within your grant. A lens of `region/eu` returns every readable clause that **includes** `eu` - for example both `{eu}` and `{eu, macbook}` - rather than shrinking your access region to exact `{eu}` matches only.
- **`scope_view`** - controls how broadly results are folded **within** the caller’s grant:
  - **`strict`** (default) - the caller’s own read region only.
  - **`crossTeam`** - cross-principal shared reads where grants allow; resolves like **`strict`**.
  - **`merged`** - same-fact records at narrower scopes with provenance preserved; resolves like **`strict`**.

None of these widen past a principal’s grant. See [REST API](/docs/agent-memory/reference/rest-api.md) for `/query` and `/context` request fields.

## Related reading

- [Surface, models, and security](/docs/agent-memory/architecture/surface-security-and-models.md)
- [Principal](/docs/agent-memory/reference/glossary.md#principal)
- [REST API](/docs/agent-memory/reference/rest-api.md)

---

Source: https://surrealdb.com/docs/agent-memory/mental-model/memory-categories

# Memory categories

Episodic raw turns plus five extracted experiential categories. Identity, knowledge, context, instructions, and uncertainty.

SurrealDB Agent Memory splits the **experiential** side of memory into **six** typed areas. Each has its own lifecycle, decay posture, and retrieval weighting. A parallel **[trace layer](/docs/agent-memory/architecture/traces-and-evolution.md)** records how memory was *used*.

The eight **pillars** are summarised in [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md).

## An example: four sentences about cats

The six categories are easier to remember if you map them to how you already classify speech:

| Sentence | Category in play |
| --- | --- |
| “I have a cat.” | **Identity** / **knowledge** about the speaker (present) |
| “I saw a cat last night.” | **Episodic** (the story) plus extracted **knowledge** with a past **valid time** |
| “House cats weigh about 4 kg.” | **Knowledge** at general scope - like a textbook fact, not “about Alice” |
| “I used to have a cat.” | **Knowledge** superseded in time - still stored, no longer current |

**Instructions** and **uncertainty** sit beside these: “always use cute nicknames for pets” is not a zoology fact; “Alice says 5 kg but the manual says 4 kg” becomes **uncertainty**, not a silent average. Together with [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md), this is how SurrealDB Agent Memory keeps personal, general, and time-bound memories from blurring together.

## 1. Episodic - the raw conversational record

The ordered **session / turn** stream: what was said, in order, including **anaphora** (pronouns and references like “he” or “that project” that point back to something said earlier).

- Written **once**; not reconciled like extracted facts.
- May **age out** faster than derived beliefs.
- Browsed with [Sessions and turns](/docs/agent-memory/mental-model/sessions-and-turns.md) tooling.

## 2. Identity - who the principal is

Stable facts about the person or agent: “Alice is Head of Platform at Acme”, “King Charles III is head of state of the United Kingdom”. **Long retention**, high weight in profile summaries.

## 3. Knowledge - what the principal knows

Facts shared in conversation, distinct from uploaded manuals: “Alice is learning Rust”, “The Atlas launch is in Q3”. **Medium retention** - fades without reinforcement unless consolidated.

## 4. Context - what is happening now

The working set for **this** conversation: “Alice is debugging checkout today”, “We are reviewing the EU pricing page”. **Short retention**, replaced quickly.

## 5. Instructions - how to behave

Behavioural directives, not world facts: “always British English”, “never use my first name”, “keep answers under three bullet points”. Applied at **prompt assembly**, not generic retrieval.

## 6. Uncertainty - explicit gaps

Records when SurrealDB Agent Memory is not confident enough to commit: conflicting sources, weak extraction, or open questions. Surfaces “I’m not sure” instead of invented fill-ins.

## Reading them back

Every extracted attribute carries its `memory_category`, so anything that
assembles a profile should filter on it rather than treating all attributes
alike:

- **`identity`** and **`knowledge`** for anything that should stay true - a user profile, a character sheet, a summary someone will read next week.
- **`context`** for what is happening now - valuable for the current turn, misleading in a durable summary.

The split can be stark. Ingesting a novel chapter by chapter will tend to produce far more `context` attributes for protagonists against compared with `identity` and `knowledge`: things like `state_of_mind`, `physical_state` and `feeling`
against `occupation`, `eye_color` and `personality_trait`. When read as one undifferentiated set, that describes a mood; filtered to identity and knowledge, it describes a person.

Note that a `context` key can hold several live values at once. Transient states are written as they are observed, and a later value does not necessarily supersede an earlier one. As such, the "the current state" is not a single row. You can take the most recent by `created_at` (which is *known* time - the chapter, not the upload), or read the whole chain from `GET /entities/{type}/{name}/history/{key}` and pick from it.

## At a glance

| Category | Holds | Typical lifetime |
| --- | --- | --- |
| Episodic | Raw turns / transcripts | Short-to-medium |
| Identity | Stable facts about the principal | Long |
| Knowledge | Learned / shared factual context | Medium |
| Context | Current working state | Short |
| Instructions | Behaviour preferences | Until revoked |
| Uncertainty | Deliberate “we do not know” | Until resolved or superseded |

## Related reading

- [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md)
- [Sessions and turns](/docs/agent-memory/mental-model/sessions-and-turns.md)

---

Source: https://surrealdb.com/docs/agent-memory/mental-model/memory-lifecycle

# Supersession, decay, and forget

Three mechanisms for how beliefs change, fade, and are removed.

Most memory products fold three different questions into a single "delete" or "rank lower" behaviour. A correction can disappear, a stale preference can still win at retrieval, or an erasure request can leave records in a backup table nobody queries.

SurrealDB Agent Memory keeps the three mechanisms separate:

| Question | Mechanism | Example |
| --- | --- | --- |
| **“This replaced that.”** | **Supersession** | You preferred formal tone in March; you prefer casual now. The old instruction stays in history with a bounded validity interval; the new one is current. |
| **“This stopped being relevant.”** | **Decay with reinforcement** | A sprint goal from three weeks ago fades unless retrieval keeps using it. |
| **“This should not exist for the agent.”** | **Forget** (soft by default) | “Forget everything about my old job.” Matched beliefs expire from current retrieval; see [Forgetting memories](/docs/agent-memory/operations/forget.md). |

If these share one overwrite or one ranking pool, you either lose the audit trail or you let two "current" beliefs fight at query time.

## Supersession - replacement with history

When an updated preference or corrected fact arrives from the **same kind of source** (for example two statements from the same user over time), SurrealDB Agent Memory treats it as **you changing your mind**, not a contradiction to flag.

- The prior belief is **closed in time**, not erased.
- The new belief becomes the **single current answer** for “what is true now?”
- You can still ask what the value was last month and when it changed.

Instructions behave the same way: “always use formal tone” superseded by “prefer casual” means the agent follows casual **now**, but the shift remains auditable.

Cross-provenance clashes are different - an uploaded policy versus a user assertion - and surface as **`uncertainty`** instead of a clean supersession. See [Authority when pillars meet](/docs/agent-memory/reasoning/authority-hierarchy.md).

## Decay - relevance without destruction

Not everything should live at full strength forever. **Context** fades quickly; **knowledge** fades more slowly; **identity** is comparatively stable. SurrealDB Agent Memory applies category-aware **importance decay** on a schedule.

Decay is not blind deletion: when a memory contributes to a successful retrieval, its importance can be **reinforced** so useful facts stay sharp while untouched noise quietly leaves the active set.

Background **consolidation** can also crystallise repeated observations into durable beliefs (`source.kind = "consolidation"`) - the “between conversations” evolution described in [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md).

## Forget - explicit removal

When something must stop influencing the agent - privacy, compliance, or a hard reset - use **`POST /forget`** or entity delete. Default behaviour **expires** matched records from current retrieval while keeping audit history; **`purge: true`** and scoped forget exist for erasure requirements.

Forget does not block future extraction: if a later turn mentions the same topic again, new memory can be created. Combine product policy with forget when a topic must stay gone.

## Long sessions and compaction

Existing model context windows encourage **compaction**, namely the process of squashing a long thread into a summary. Summaries are inherently lossy, often discarding details you did not know you would need later.

SurrealDB Agent Memory’s approach is complementary: important facts from turns are **promoted into durable, queryable memory** (entities, attributes, instructions) while the **full episodic transcript** remains addressable via sessions and provenance. Consolidation and reflection run **between** interactions so you are not dependent on remembering to compact at the right moment for memory to survive the next session.

## Who owns the decision?

The **reconciler** owns the bookkeeping guarantees - supersession chains, confidence floors, decay schedules, uncertainty when sources disagree. **Signals** come from agent actions: turns, corrections, document ingest, reflection, and traces of what was retrieved and used.

When the system genuinely cannot decide (cross-provenance conflict, confidence below the calibration floor), it records **`uncertainty`** for the agent or a higher-level policy to resolve rather than silently picking a winner.

## Related reading

- [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md)
- [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md)
- [Forgetting memories](/docs/agent-memory/operations/forget.md)
- [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md)

---

Source: https://surrealdb.com/docs/agent-memory/mental-model/provenance-and-traceability

# Provenance and traceability

The source object carried on every record. Kinds, spans, trust, derivation, and traces.

SurrealDB Agent Memory’s accuracy story depends on one invariant: no fact-bearing record is anonymous. Provenance is a structured field, not an afterthought in application logs.

## The `source` object (conceptual)

Every fact-bearing record (`entity`, `attribute`, `relation`, `instruction`, `uncertainty`, …) carries a **`source`** object:

| Field | Role |
| --- | --- |
| `source.kind` | `turn`, `document`, `upsert`, `reflect`, `elaboration`, `consolidation`, … |
| `source.ref` | Originating turn, document, or trace identifier |
| `source.session_id`, `source.turn_at` | When the record came from conversation |
| `source.valid_from`, `source.span: { start, end }` | Valid-time anchor and **quote position** in the originating message or passage |
| `source.location` | Optional geometry for where the fact was **captured** |
| `source.trust` | Source prior - admin documents rank above casual turns |
| `source.derived_from` | Lineage for reflections, elaborations, consolidations |

Some JSON examples in the docs show a flat `source_turn` field - that is the conversational shorthand for `source.ref`.

## Why spans matter

`source.span` stores character positions in the originating turn or document passage. Citations are stored data, not best-effort model prose. That is what “jump to quote” in UIs is built on; tiered reads are described in [Coherence, retrieval, and cost tiers](/docs/agent-memory/architecture/coherence-retrieval-and-tiers.md).

## Multiple sources, one audit trail

When several sources support or contradict the same topic, SurrealDB Agent Memory **does not flatten** them into a single anonymous record. Each assertion keeps its own provenance. Cross-provenance disagreement becomes **`uncertainty`**; same-stream updates **supersede** with a chain you can replay.

**Retrieval defaults** return the **current** view with sources attached, traceable to originating bytes. **`as_of`** and entity history endpoints return what the system would have answered at an earlier instant - a correction appears as a **transition** in history, not a missing gap. See [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md) and [Supersession, decay, and forget](/docs/agent-memory/mental-model/memory-lifecycle.md).

## Traces complete the story

Extraction and reconciliation emit **`decision_trace`** nodes. Ranked reads emit **`retrieval_trace`**; `/chat` and `/reflect` emit **`response_trace`**. Together they answer “**which source produced this belief?**” and “**which retrieval path led to this answer?**” Full detail: [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md).

## Practical surfaces

- HTTP: `GET /api/v1/{ctx}/traces`, `GET /api/v1/{ctx}/traces/{id}` ([REST API](/docs/agent-memory/reference/rest-api.md)).
- CLI: `spectron inspect trace:…`, `spectron entities history …` ([Surface, models, and security](/docs/agent-memory/architecture/surface-security-and-models.md)).

## Related topics

- [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md)
- [Supersession, decay, and forget](/docs/agent-memory/mental-model/memory-lifecycle.md)
- [Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md)

---

Source: https://surrealdb.com/docs/agent-memory/mental-model/sessions-and-turns

# Sessions and turns

How conversations map to sessions, turns, and provenance.

**Episodic memory** is the raw, ordered conversational record: sessions and turns as authored, including **anaphora** (pronouns and phrases like “he” or “that project” that refer back to something said earlier). Extracted identity, knowledge, context, instructions, and uncertainty records cite this layer through provenance (`source.ref`, `source.span`, `source.trust`). See [Memory categories](/docs/agent-memory/mental-model/memory-categories.md).

## What is a session?

A session is a conversation container with a stable id, scope paths, and metadata. Memory extracted from the conversation references the session for audit and transcript views.

```http
POST /api/v1/{context_id}/sessions
Authorization: Bearer <key>
Content-Type: application/json

{
  "scope": ["org/acme/user/alice", "agent/support"],
  "metadata": { "channel": "web" }
}
```

```bash
spectron sessions list
spectron sessions show <session_id>
```

## What is a turn?

A turn is one message with a **role**:

| Role | Description |
| --- | --- |
| `user` | Human participant |
| `assistant` | Model output |
| `system` | Injected system prompt |
| `tool` | Tool result |

Turns are ordered within a session. The episodic record is the source of truth for lexical attribution.

## Recommended ingest: `/facts/batch`

For new integrations, send the full message list in one call instead of appending turns one HTTP request at a time:

```http
POST /api/v1/{context_id}/facts/batch
Content-Type: application/json

{
  "session_id": "sess_01hw…",
  "messages": [
    { "role": "user", "content": "My name is Alice and I work at Acme Corp." },
    { "role": "assistant", "content": "Hello Alice! How can I help?" }
  ],
  "scope": ["org/acme/user/alice"]
}
```

Harness adapters (LangChain, Vercel AI, OpenAI Agents) use this path with platform-derived `Idempotency-Key` values. Default **`extract`** is **`whole_conversation`**; set **`per_message`** for one extraction pass per message.

## Extraction pipeline

Each ingest path runs the same reconciler:

1. Extract entities, attributes, and relations from new text
2. Reconcile against existing records (authority, temporal, calibration)
3. Persist with provenance
4. Return **`extractions`** (batch) or nested **`extraction`** (single fact), plus `sessionId` and `turnIds`

You do not call a separate “process” endpoint.

## Introspection

```http
GET /api/v1/{context_id}/sessions/{session_id}
GET /api/v1/{context_id}/sessions/{session_id}/turns
GET /api/v1/{context_id}/sessions/{session_id}/context
POST /api/v1/{context_id}/state
```

Session-scoped **state** and **diff** endpoints support debugging what changed across turns.

## CLI transcript tooling

```bash
spectron sessions list
spectron sessions show sess_01hw…
```

See [Creating sessions](/docs/agent-memory/sessions/creating-sessions.md) and [Adding turns](/docs/agent-memory/sessions/adding-turns.md) for operational detail aligned with the current API.

---

Source: https://surrealdb.com/docs/agent-memory/mental-model/two-layer-architecture

# Unified substrate and authority

One SurrealDB graph for authoritative and experiential knowledge. Provenance and reconciliation in one place, rather than two silos.

SurrealDB Agent Memory stores authoritative and experiential knowledge in one SurrealDB graph. The eight pillars, especially Authoritative and Experiential, show up as records and edges, not as two databases.

> One multi-model SurrealDB substrate - documents, turns, entities, attributes, relations, embeddings, traces, and (where enabled) geometry - with provenance explaining which stream produced a record and reconciliation deciding how streams combine.

"Layers" are still a useful picture for authority: curated organisational truth versus conversational input. Both streams are records and edges in the same database, updated under ACID transactions.

## Two streams of truth (not two databases)

| Stream | Typical `source.kind` | What it holds | Default trust |
| --- | --- | --- | --- |
| **Authoritative** | `document` (and operator `upsert`) | Manuals, policies, product data, repos, structured exports | High - vetted sources |
| **Experiential** | `turn`, plus `reflect`, `elaboration`, `consolidation`, … | What users and agents said; synthesised or background-minted facts | Lower - must earn promotion |

Retrieval, elaboration, and consolidation see one `entity` / `relation` graph. Hybrid rankers fuse vectors, BM25, graph structure, keyword bridges, geo predicates, and trace-derived features without cross-store joins. A question like "semantically close to X, mentioning Y, linked to entity Z, valid as of last March" is one query over one engine.

## How authority is enforced

When an experiential assertion disagrees with authoritative material, the reconciler:

1. **Does not** silently overwrite curated records.
2. Records the experiential assertion with provenance intact.
3. Surfaces **`uncertainty`** (and/or conflict metadata) so you can see the gap.

That is how the **Authoritative** pillar wins over casual assertions: **authority is a reconciliation policy**, not a second copy of the universe hidden in another engine.

## Why this matters

- **Transactions** across “what the user said” and “what the handbook says” can be reasoned about **together**.
- **Contradictions** become first-class records you can query, not cosine-distance accidents.
- **[Traces](/docs/agent-memory/architecture/traces-and-evolution.md)** link decisions back to the sources considered.

## Related reading

- [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md)
- [Principles and goals](/docs/agent-memory/architecture/principles-and-goals.md)
- [Authority when pillars meet](/docs/agent-memory/reasoning/authority-hierarchy.md) - how curated and experiential streams interact in APIs today

---

Source: https://surrealdb.com/docs/agent-memory/operations/forget

# Forgetting memories

Default forget, purge, and scoped erasure. How to retire or permanently erase memories.

SurrealDB Agent Memory distinguishes **retiring** memory (the agent stops using it) from **erasing** memory (the content is removed for compliance). Most day-to-day use is retirement; erasure is explicit and grant-gated.

All forget operations require the **`memory:forget`** grant on the caller’s API key for the relevant scope. Keys with only read/write access cannot forget.

## Semantic forget (default - retire, do not erase)

Describe what to remove in natural language. SurrealDB Agent Memory finds matching entities, facts, relations, and conversational chunks by semantic similarity, then **expires** them. Expired memory:

- Does **not** appear in **`/query`**, **`/profile`**, **`/state`**, or chat context going forward.
- Does **not** come back through a point-in-time read. A query anchored at a moment before the forget still excludes it, so forgetting holds across time travel rather than only from now on.
- **Does** remain in storage so you can audit what was retired and when.

Forgetting is separate from expiry. A fact that simply ended, was superseded by a newer value, or aged out is still part of the record: point-in-time reads continue to serve it, which is what makes supersession history auditable. Only an explicit forget is withheld from those reads.

```http
POST /api/v1/{context_id}/forget
Content-Type: application/json
Authorization: Bearer sk-...

{
  "query": "anything about my old job",
  "dryRun": true
}
```

```json
{
  "deleted": 3
}
```

**`dryRun: true`** scores matching records and returns the count **without writing anything** - use this to preview before applying. A successful dry run emits no forget audit event. Omit **`dryRun`** or set it to **`false`** to expire records.

To apply after preview:

```http
POST /api/v1/{context_id}/forget
Content-Type: application/json
Authorization: Bearer sk-...

{
  "query": "anything about my old job"
}
```

The response counts how many records were expired. Scope is enforced by the API key’s grants - you cannot forget outside the scopes your key is allowed to write (and forget) within.

```bash
spectron forget "anything about my old job" \
  --url "$SPECTRON_URL" --api-key "$SPECTRON_API_KEY" --context-id "$SPECTRON_CONTEXT_ID"

# Preview matches without expiring anything
spectron forget "anything about my old job" --dry-run \
  --url "$SPECTRON_URL" --api-key "$SPECTRON_API_KEY" --context-id "$SPECTRON_CONTEXT_ID"
```

## Entity delete (default - retire one entity)

To target a specific entity by type and name:

```http
DELETE /api/v1/{context_id}/entities/{entity_type}/{entity_name}
```

This expires the entity and its active facts and relations. Like semantic forget, it is a **soft** retirement by default - the entity no longer surfaces in retrieval, but the retired records remain stored.

## Purge - permanent erasure (compliance)

When you must guarantee that content no longer exists in the memory substrate - for example a **right-to-erasure** request - use the same **`POST /forget`** endpoint with **`purge: true`**, or the CLI **`--purge`** flag.

```http
POST /api/v1/{context_id}/forget
Content-Type: application/json
Authorization: Bearer sk-...

{
  "query": "anything about my old job",
  "purge": true
}
```

With **`purge: true`**, SurrealDB Agent Memory permanently removes the matched entities, their fact history (including prior corrections), related relations, and linked conversational chunks. After a successful purge, that content is not recoverable from memory storage.

**Audit note:** the trace graph may still record that a forget call occurred (who, when, which operation) without retaining the deleted content itself. Plan your compliance story around both memory erasure and trace retention policies.

Purging is **opt-in**. Omitting **`purge`** (or setting it to **`false`**) keeps supersession history even after the current values are expired - the default path for ordinary agent memory management.

## Scoped forget - erase a whole subtree

To remove **everything** tagged under a scope path (a user branch, team, or project subtree), use:

```http
POST /api/v1/{context_id}/scopes/forget
Content-Type: application/json
Authorization: Bearer sk-...

{
  "path": "user/alice/"
}
```

This **hard-deletes** all memory and knowledge records whose scope touches that subtree - entities, facts, sessions, documents, chunks, and related index state. It requires the **`memory:forget`** grant over that subtree. The response reports how many records were erased.

Use scoped forget for GDPR-style deletion when a principal’s entire branch must go, not just a single fact.

## Choosing the right operation

| Goal | Operation | Erases from storage? |
| --- | --- | --- |
| Preview what would be retired | **`POST /forget`** with **`dryRun: true`** or **`spectron forget --dry-run`** | No - read-only count |
| Agent should stop mentioning something | **`POST /forget`** (default) or **`DELETE /entities/...`** | No - expired, still auditable |
| User’s erasure request for specific topics | **`POST /forget`** with **`purge: true`** | Yes - matched content removed |
| Delete everything for a user/team scope | **`POST /scopes/forget`** | Yes - entire subtree removed |
| Retire a scope folder in the vocabulary only | **`scope:delete`** (tombstone the scope node) | Tombstone is soft - distinct from erasing facts; see [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md#tombstone-vs-erase) |

## After forget - extraction can bring facts back

Forget operates on what is stored **now**. If a later conversation turn mentions the same information again, extraction may create **new** memory for it. If you need a topic to stay gone, combine forget with product-level controls (do not re-ingest, block the source, or purge again).

## Related reading

- [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md) - why corrections keep history by default
- [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md) - time-travel and belief history
- [Permissions and delegation](/docs/agent-memory/mental-model/contexts-and-scope.md#permissions-and-delegation) - **`memory:forget`** grants
- [REST API](/docs/agent-memory/reference/rest-api.md)

---

Source: https://surrealdb.com/docs/agent-memory/operations/profiles

# Profiles

Auto-maintained entity profiles aggregated from memory and knowledge.

A profile is a computed view over the memory store for a given entity - typically a user. It aggregates the entity's most relevant attributes, preferences, and active instructions into a structured object that can be injected directly into an LLM system prompt.

Profiles are not a separate store. They are assembled on demand from the same attribute, instruction, and knowledge tables that back all other SurrealDB Agent Memory operations. There is no synchronisation lag and no risk of a profile drifting out of step with the underlying data.

## Sections

A profile is divided into four sections, each drawing from a different category of memory.

**`static`** contains the entity's stable biographical and organisational facts - identity-category attributes and any resolved authoritative knowledge. These change rarely and carry no decay.

**`dynamic`** contains the entity's recent situational context - context-category attributes from the current and recent sessions. This section reflects what the entity is doing right now.

**`preferences`** contains knowledge-category attributes that describe how the entity likes things to work. Editor preferences, communication style, tool choices, and opinion-based attributes appear here.

**`instructions`** contains all active instruction records scoped to this entity. These are directives the entity or a managing principal has given about how the agent should behave.

| Section | Source | Volatility |
|---|---|---|
| `static` | Identity attributes + authoritative knowledge | Low - no decay |
| `dynamic` | Context attributes | High - decays at 0.95/day |
| `preferences` | Knowledge attributes | Medium - decays at 0.995/day |
| `instructions` | Instruction table (`active = true`) | Varies |

## Fetching a profile

```python
profile = await memory.profile()

print(profile.static)        # list of {"key": ..., "value": ...}
print(profile.dynamic)       # list of {"key": ..., "value": ...}
print(profile.preferences)   # list of {"key": ..., "value": ...}
print(profile.instructions)  # list of {"id":…, "label":…, "description":…}
```

```typescript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "ctx_01jt4kx...", apiKey: "sk-..." });

const profile = await memory.profile();

console.log(profile.static);
console.log(profile.dynamic);
console.log(profile.preferences);
console.log(profile.instructions);
```

### REST

```http
GET /api/v1/{context_id}/profile
```

```json
{
  "static": [
    { "key": "name", "value": "Alice Chen" },
    { "key": "role", "value": "CTO" },
    { "key": "organisation", "value": "Acme Corp" },
    { "key": "timezone", "value": "Europe/London" }
  ],
  "dynamic": [
        { "key": "current_focus",
        "value": "Platform reliability roadmap" },
    { "key": "recent_decision", "value": "Chose a relational database for the new analytics service" }
  ],
  "preferences": [
    { "key": "editor", "value": "Cursor" },
    { "key": "language", "value": "TypeScript" },
    { "key": "response_style", "value": "Concise, no filler phrases" }
  ],
  "instructions": [
        { "label": "Language",
        "description": "Always respond in British English" },
        { "label": "Code examples",
        "description": "Show TypeScript first, Python second" }
  ]
}
```

## Injecting profiles into system prompts

The profile object is designed to be formatted directly into an LLM system prompt. A typical integration looks like this:

```python
profile = await memory.profile()

def format_profile(profile) -> str:
    lines = ["## User profile"]

    if profile.static:
        lines.append("\n### About")
        for item in profile.static:
            lines.append(f"- {item['key'].replace('_', ' ').title()}: {item['value']}")

    if profile.dynamic:
        lines.append("\n### Current context")
        for item in profile.dynamic:
            lines.append(f"- {item['key'].replace('_', ' ').title()}: {item['value']}")

    if profile.preferences:
        lines.append("\n### Preferences")
        for item in profile.preferences:
            lines.append(f"- {item['value']}")

    if profile.instructions:
        lines.append("\n### Instructions")
        for inst in profile.instructions:
            lines.append(f"- {inst['description']}")

    return "\n".join(lines)

system_prompt = f"You are a helpful assistant.\n\n{format_profile(profile)}"
```

```typescript
function formatProfile(profile: Profile): string {
    const lines = ["## User profile"];

    if (profile.static.length > 0) {
        lines.push("\n### About");
        for (const item of profile.static) {
            lines.push(`- ${item.key}: ${item.value}`);
        }
    }

    if (profile.dynamic.length > 0) {
        lines.push("\n### Current context");
        for (const item of profile.dynamic) {
            lines.push(`- ${item.key}: ${item.value}`);
        }
    }

    if (profile.preferences.length > 0) {
        lines.push("\n### Preferences");
        for (const item of profile.preferences) {
            lines.push(`- ${item.value}`);
        }
    }

    if (profile.instructions.length > 0) {
        lines.push("\n### Instructions");
        for (const inst of profile.instructions) {
            lines.push(`- ${inst.description}`);
        }
    }

    return lines.join("\n");
}

const systemPrompt = `You are a helpful assistant.\n\n${formatProfile(profile)}`;
```

## Category grouping, not entity grouping

A profile is organised by **memory category** (`static`, `dynamic`, `preferences`, `instructions`) - not by which **entity** each attribute belongs to. When you mention yourself, your school, and your cat in one conversation, all extracted attributes that match the requested scope can appear in the same section. For example, `date_of_birth` (you), `school_level` (your school), and `species` (your cat) may sit side by side under **`static`** or **`preferences`**.

That flat shape is deliberate for **prompt injection** - one dense briefing block for the model. For per-subject inspection, use the **Entities** view in SurrealDB Studio, **`GET /entities`**, or entity-scoped reads rather than the profile alone.

## Scope filtering

`GET /profile` takes **no scope parameter**. The profile always covers everything
the calling key can read, so the shape of a profile is determined by the key's
`memory:read` grants, not by a per-request argument.

```python
# Whatever this key can read
profile = await memory.profile()
```

To build different profile shapes for different agent roles, give each role a key
whose read region matches it. A customer-facing agent holding a key granted
`org/acme/user/alice` receives that user's full profile; an administrative agent
holding a key granted only `org/acme` receives org-scoped context without any
individual user's personal data.

## Profile versus context

Profiles and context queries serve overlapping but distinct purposes.

`memory.context()` is query-driven: you describe what you need to know and SurrealDB Agent Memory retrieves the most relevant attributes for that query. Use it when the agent's question determines what memory is relevant.

`memory.profile()` is entity-driven: you request everything SurrealDB Agent Memory knows about an entity, organised by category. Use it when you want a comprehensive briefing on a user at the start of a session, before any specific query has been issued.

In practice, many integrations combine both: inject the profile at the start of the system prompt for background context, then call `memory.context()` at query time to surface specific relevant memories.

---

Source: https://surrealdb.com/docs/agent-memory/operations/reflect

# Reflection

Synthesise insights from patterns across stored memories.

Reflection is a reasoning operation over the memory store. Unlike retrieval, which surfaces existing facts that match a query, reflection asks SurrealDB Agent Memory to examine a set of memories and draw conclusions from them - to reason about patterns, identify trends, and synthesise insights that do not exist as explicit stored attributes.

The result is a synthesised text insight backed by evidence citations. If `persist` is enabled, the synthesised insights are also stored as new experiential memory attributes, making them available to future queries.

## How it works

When you call `memory.reflect()`, SurrealDB Agent Memory:

1. Retrieves the memories most relevant to your query using hybrid retrieval, respecting scope.
2. Sends the retrieved evidence to an LLM with a synthesis prompt instructing it to reason over the material and surface patterns, risks, or insights.
3. Returns the synthesised text alongside the supporting memories.
4. If `persist=True`, writes the synthesised insights as new knowledge-category attributes inside the caller's `memory:write` region.

Reflection is more expensive than retrieval - it always involves an LLM call - but it produces conclusions that no single stored attribute contains.

## Basic usage

```python
out = await memory.reflect(
    query="What patterns do you see in customer complaints this month?",
    persist=False)

print(out.reflection)   # synthesised insight text
print(out.evidence)     # list of supporting memory hits
```

```typescript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "ctx_01jt4kx...", apiKey: "sk-..." });

const out = await memory.reflect({
    query: "What patterns do you see in customer complaints this month?",
    persist: false });

console.log(out.reflection);
console.log(out.evidence);
```

### REST

```http
POST /api/v1/{context_id}/reflect
Content-Type: application/json

{
  "query": "What patterns do you see in customer complaints this month?",
  "scope": ["org/acme/user/alice"],
  "persist": false
}
```

```json
{
  "reflection": "Three recurring patterns emerged across 47 complaint sessions this month…",
  "evidence": [
    "billing proration came up in 18 sessions",
    "search accuracy complaints clustered after the 3rd deployment"
  ],
  "persistedAttributes": [],
  "traceId": "mZrlXhKPuV1H9S1l"
}
```

## Persisting synthesised insights

Setting `persist=True` instructs SurrealDB Agent Memory to write the synthesised insights back to memory as new attributes. This makes the reflection available to future retrieval queries without repeating the synthesis LLM call.

```python
out = await memory.reflect(
    query="What does this user value most in a development tool?",
    persist=True)

print(out.reflection)
print(out.persisted_attributes)  # list of newly created attribute records
```

```typescript
const out = await memory.reflect({
    query: "What does this user value most in a development tool?",
    persist: true });

console.log(out.reflection);
console.log(out.persistedAttributes);
```

When `persist=True`, the response includes **`persistedAttributes`** listing the attribute records that were written:

```json
{
  "reflection": "Alice consistently prioritises fast feedback loops…",
  "evidence": ["Alice mentioned keyboard shortcuts three times", "…"],
  "persistedAttributes": [
    {
      "entityId": "['person', 'alice']",
      "key": "core_tool_values",
      "value": "Fast feedback loops, keyboard-driven workflows"
    }
  ],
  "traceId": "…"
}
```

Persisted insights are stored as knowledge-category attributes, which carry a decay rate of 0.995 per day. They are available immediately for retrieval and appear in future profile requests.

## Permissions for persist

`persist=True` writes attributes, so it requires **`memory:write`** - and the write
lands inside the caller's write region, never outside it. There is no persist-scope
argument: the region the key holds is the region reflect can write to.

A key granted `memory:write` on `org/acme/*` can persist org-level insights drawn
from across the whole org. A key granted only `org/acme/user/alice` persists at
that path and below, however wide the evidence it reasoned over.

This prevents agents from writing to scopes they do not own. An agent operating for user A cannot persist insights visible to user B, even if the reflection was informed by shared org-level memory.

## Use cases

**Project risk analysis**: A project management agent reflects on all task and decision records for a project to identify risks the team has not explicitly surfaced.

```python
risks = await memory.reflect(
    query="What risks are present in this project that we haven't explicitly discussed?",
    persist=True)
```

**Sales pattern recognition**: A sales coaching agent reflects on call notes and outcome records to identify what approaches correlate with successful closes.

```python
patterns = await memory.reflect(
    query="What conversational patterns appear most often in deals that closed this quarter?",
    persist=True)
```

**Support gap identification**: A support agent reflects on unresolved queries to identify topics where the knowledge base is insufficient.

```python
gaps = await memory.reflect(
    query="Which questions did I fail to answer confidently this week, and what knowledge would have helped?",
    persist=True)
```

## Reflection versus retrieval

| | Retrieval (`memory.query`) | Reflection (`memory.reflect`) |
|---|---|---|
| Operation | Finds existing attributes matching a query | Reasons over retrieved attributes to produce new insights |
| LLM involvement | Only at the `full_context` tier | Always |
| Output | Ranked list of stored facts | Synthesised insight text + evidence |
| Persist option | N/A - retrieval is read-only | Optional - writes new attributes |
| Cost | Low to medium | Higher - always incurs an LLM call |

Use retrieval when you need to surface known facts. Use reflection when you need to reason about patterns, draw conclusions, or produce summaries that span many individual memory records.

---

Source: https://surrealdb.com/docs/agent-memory/quickstarts/embedded

# Embedded library quickstart

Integration surfaces for SurrealDB Agent Memory.

SurrealDB Agent Memory runs as a **horizontally scalable service** in front of SurrealDB. Integrate through:

- **HTTP** - `/api/v1/{context_id}/...` ([REST API](/docs/agent-memory/integrations/surfaces/rest.md))
- **MCP** - `/mcp` on the same port
- **Generated SDKs** - Python (`surrealdb`) and TypeScript (`@surrealdb/memory`)
- **Harness adapters** - LangChain, Vercel AI SDK, OpenAI Agents, n8n, Claude Code hook ([Integrations](/docs/agent-memory/integrations.md))

## In-process library

There is **no** supported in-process API that runs extraction and recall inside your application binary without the SurrealDB Agent Memory server. Rust, Python, and TypeScript agents call the HTTP API or SDK against a deployed SurrealDB Agent Memory instance.

## What to use

| Goal | Use |
| --- | --- |
| Python / TypeScript agent | [Python](/docs/agent-memory/integrations/sdks/python.md) or [JavaScript SDK](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md) |
| Coding assistant | [MCP install](/docs/agent-memory/integrations/mcp-server/install.md) |
| LangChain / Vercel AI / OpenAI Agents | [Framework adapters](/docs/agent-memory/integrations/frameworks/langchain.md) |
| Hosted SurrealDB Agent Memory | [Hosted quickstart](/docs/agent-memory/quickstarts/hosted.md) |

See also [Embedded library](/docs/agent-memory/integrations/surfaces/embedded-library.md) for Rust-specific notes.

---

Source: https://surrealdb.com/docs/agent-memory/quickstarts/hosted

# Hosted quickstart

Get SurrealDB Agent Memory running in five minutes. Runs on SurrealDB Cloud, with no server to install.

This guide takes you from a blank terminal to your first **remember** and **recall** calls against **SurrealDB Agent Memory on SurrealDB Cloud**. Cloud runs the SurrealDB Agent Memory data plane, SurrealDB, and object store per context - you do not provision infrastructure yourself.

> [!NOTE]
> [!NOTE]
> SurrealDB Agent Memory contexts are created in **[SurrealDB Studio](https://studio.surrealdb.com)** (organisation → **Contexts**). See [SurrealDB Agent Memory on SurrealDB Cloud](/docs/agent-memory/quickstarts/surrealdb-cloud.md) for context setup and API keys. This page assumes you already have a **context host**, **context id**, and **API key** from the **API keys** view.

## Step 1 - Create a Context and API key (SurrealDB Studio)

1. Sign in to SurrealDB Cloud in SurrealDB Studio.
2. Open your organisation → **Contexts** (requires SurrealDB Agent Memory access on your profile).
3. Subscribe to a SurrealDB Agent Memory plan (owner) if needed, then **Create context** - name and region.
4. Open the context → **API keys** → create a key. Copy the secret immediately.

For a click-by-click version of this step, plus the Playground, Documents, and Memory views, see [Create your first context](/docs/agent-memory/quickstarts/surrealdb-cloud.md#create-your-first-context).

You will use:

- **Host** - shown as the endpoint, e.g. `https://abc123.spectron.cloud…` (per context, not a single global URL)
- **Context ID** - the context identifier
- **API key** - `sk-ctx-…` shown once at creation

## Step 2 - Set environment variables

**Bash**

```bash
export SPECTRON_URL="https://<your-context-host>"
export SPECTRON_CONTEXT_ID="<your-context-id>"
export SPECTRON_API_KEY="sk-ctx-..."
```

**PowerShell**

```powershell
$env:SPECTRON_URL = "https://<your-context-host>"
$env:SPECTRON_CONTEXT_ID = "<your-context-id>"
$env:SPECTRON_API_KEY = "sk-ctx-..."
```

Use the exact **host** from SurrealDB Studio settings or API keys - not a generic placeholder domain.

## Step 2b - Register scope paths

Scoped writes require registered paths. With the CLI pointed at your context:

```bash
spectron scopes create org/acme \
  --url "$SPECTRON_URL" --api-key "$SPECTRON_API_KEY" --context-id "$SPECTRON_CONTEXT_ID"

spectron scopes create org/acme/user/alice \
  --url "$SPECTRON_URL" --api-key "$SPECTRON_API_KEY" --context-id "$SPECTRON_CONTEXT_ID"
```

Org admins can also register scopes through the SurrealDB Cloud API admin proxy when building custom tooling.

## Step 3 - Remember a fact

```bash
curl -sS "$SPECTRON_URL/api/v1/$SPECTRON_CONTEXT_ID/facts" \
  -H "Authorization: Bearer $SPECTRON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "I was just promoted to CTO.",
    "infer": "full",
    "scope": ["org/acme/user/alice"]
  }'
```

The response includes a nested **`extraction`** object (entities, attributes, relations, …) plus `sessionId` and `turnId`.

## Step 4 - Recall

```bash
curl -sS "$SPECTRON_URL/api/v1/$SPECTRON_CONTEXT_ID/query" \
  -H "Authorization: Bearer $SPECTRON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "What is Alice'\''s role?",
    "scope": ["org/acme/user/alice"]
  }'
```

Check **`tier`** in the response - tier 1 or 2 hits avoid a full LLM synthesis pass. Use **`trace.traceId`** to fetch the full retrieval trace.

## Step 5 - Optional chat

```bash
curl -sS "$SPECTRON_URL/api/v1/$SPECTRON_CONTEXT_ID/chat" \
  -H "Authorization: Bearer $SPECTRON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Summarise what you know about me",
    "scope": ["org/acme/user/alice"]
  }'
```

## SDKs

Official clients: **`surrealdb[memory]`** (Python, provides `Memory` / `AsyncMemory`) and **`@surrealdb/memory`** (TypeScript). Point them at `endpoint: process.env.SPECTRON_URL` (your context host). See [Integrations](/docs/agent-memory/integrations.md).

## Next steps

- [SurrealDB Agent Memory on SurrealDB Cloud](/docs/agent-memory/quickstarts/surrealdb-cloud.md) - Cloud API vs data plane
- [Sessions and turns](/docs/agent-memory/mental-model/sessions-and-turns.md)
- [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md)
- [Uploading documents](/docs/agent-memory/ingest/authoritative/uploading-documents.md)

---

Source: https://surrealdb.com/docs/agent-memory/quickstarts/surrealdb-cloud

# Agent Memory on SurrealDB Cloud

How contexts, authentication and the APIs work on SurrealDB Cloud. For SurrealDB Agent Memory.

SurrealDB Agent Memory on **SurrealDB Cloud** is a managed data plane per **context**, orchestrated by the **SurrealDB Cloud API** and surfaced in **[SurrealDB Studio](https://studio.surrealdb.com)**.

## Two planes

| Plane | Base URL | Who uses it | Purpose |
| --- | --- | --- | --- |
| **Cloud API** | `https://api.surrealdb.com/v1/…` | SurrealDB Studio, your automation | Context lifecycle, billing, API keys, admin proxies |
| **SurrealDB Agent Memory data API** | `https://{context.host}/api/v1/…` | Your app, SDKs, MCP | Memory, knowledge, chat, documents |

Customers integrate against the **context host** and **`sk-ctx-…` API keys** from SurrealDB Studio. They do **not** receive SurrealDB Agent Memory root management keys or `SPECTRON_*` node configuration.

## Organisation roles

| Role | Typical Cloud capabilities |
| --- | --- |
| **Owner** | Subscribe to SurrealDB Agent Memory plans, billing, delete contexts |
| **Admin** | Create contexts, mint API keys, call admin proxy routes (principals, scopes, grants, usage) |
| **Member** | Use Playground, Memory, Documents, and Scopes in SurrealDB Studio; broker a short-lived access token for SDK use |

Admin proxy routes return **403** for members. See [Key policy](/docs/agent-memory/reference/configuration.md#key-policy) for how self-service and Cloud-brokered keys are governed.

## What SurrealDB Studio exposes

| Feature | In SurrealDB Studio |
| --- | --- |
| Context list, deploy, billing, delete | Yes |
| API keys + per-context endpoint | Yes |
| Playground (live chat + memory updates) | Yes |
| Memory explorer (entities, relations, traces) | Yes |
| Documents (upload, browse, search) | Yes |
| Scopes (register and browse paths) | Yes |
| Integration snippets (SDK, REST, MCP, frameworks) | Yes |
| Settings - users, configuration, usage | Admins and owners |

For terminal-first **remember** / **recall**, see [Hosted quickstart](/docs/agent-memory/quickstarts/hosted.md).

## Create your first context

This walkthrough uses SurrealDB Studio only - no terminal and no API key. When you finish, you will have a live context that stores and recalls memory.

You need the **Owner** or **Admin** role to deploy a context. Members can use the Playground, Memory, and Documents in a context that already exists.

### 1. Deploy a context

1. In [SurrealDB Studio](https://studio.surrealdb.com), open **Contexts** from the left sidebar.
2. Click **Deploy new context** at the top of the screen.
3. Give the context a name. Any name works.
4. Select a region. **US West** is the region available today.
5. Click **Create context**. Provisioning takes a minute or two.

When provisioning finishes, Studio opens the context screen. The context is ready to use.

### 2. Explore in the Playground

The Playground is a chat surface that writes to memory as you use it.

1. Open **Playground** from the left sidebar.
2. Describe facts, entities, and relationships in plain language. SurrealDB Agent Memory extracts them and stores them.
3. Ask SurrealDB Agent Memory a question. It answers from what it has learned.
4. Watch the **Graph** panel on the right. It fills out as SurrealDB Agent Memory ingests your conversations and documents.
5. Open the **Activity** panel on the right. It shows what SurrealDB Agent Memory recalls and learns from one message to the next.

> [!NOTE]
> Playground turns are durable memory in this context, and they compete with your own data at retrieval time. For a clean evaluation run, use a separate context. See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md).

### 3. Add documents and inspect memory

1. Open **Documents** from the left sidebar. Upload files for SurrealDB Agent Memory to ingest.
2. Open **Memory** from the left sidebar. Review the entities SurrealDB Agent Memory stores and the traces behind your Playground messages.

Your context is now live and learning. To move from Studio to code, create an API key in the context's **API keys** view, then follow the [Hosted quickstart](/docs/agent-memory/quickstarts/hosted.md).

## Scope on Cloud

Data-plane **scope** uses hierarchical slash paths - for example `[["org/acme/user/alice"]]` on writes and **`lens`** on reads. Register paths in SurrealDB Studio **Scopes**, via the CLI, or through the Cloud admin scopes proxy.

## Related reading

- [Hosted quickstart](/docs/agent-memory/quickstarts/hosted.md)
- [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md)
- [REST API](/docs/agent-memory/reference/rest-api.md)

---

Source: https://surrealdb.com/docs/agent-memory/reasoning/authority-hierarchy

# Authority when pillars meet

How the Authoritative and Experiential pillars interact. Reconciliation, uncertainty, and resolves_to.

SurrealDB Agent Memory’s memory model is built around eight pillars. Two matter most for day-to-day behaviour:

- **[Authoritative](/docs/agent-memory/architecture/eight-pillars-and-categories.md)** - curated artefacts: manuals, policies, product data, structured uploads. Higher default **trust**.
- **[Experiential](/docs/agent-memory/architecture/eight-pillars-and-categories.md)** - conversation and everything derived from it (reflection, elaboration, consolidation, …), including identity, knowledge, context, instructions, and uncertainties.

**Reconciliation** merges both streams with the same rules: supersession, **`uncertainty`** records, and trust logic apply whether facts arrived from a document or a turn. Physically, both are records in the **same** [unified substrate](/docs/agent-memory/mental-model/two-layer-architecture.md).

## Curated truth is never silently overwritten

**Authoritative** content does not change because a user said something different. When an experiential assertion conflicts with curated knowledge, SurrealDB Agent Memory:

1. Keeps the curated record unchanged.
2. Stores the user’s belief in experiential memory with provenance intact.
3. Surfaces the clash - for example via **`uncertainty`** records and conflict indicators in **`/state`** or **`/profile`**.

The user’s assertion remains valuable conversational context; it does **not** become canonical truth. That prevents incorrect chat from silently rewriting product, policy, or compliance data.

## Worked example

Your knowledge base states a **30-day return window**. A customer says: “Your return policy is 60 days.”

SurrealDB Agent Memory stores the customer’s belief under their scope in experiential memory and flags the disagreement. The curated policy record is unchanged. When the agent answers, it can acknowledge what the customer said while citing the policy that actually applies.

## Attribute resolution order

When both curated and experiential values exist for the same topic:

1. **Authoritative** values ground factual answers (price, policy, specification).
2. **Experiential** values personalise the interaction; conflicts are visible, not hidden.

Agents should **trust curated answers for compliance** and use experiential memory for preferences, history, and stated beliefs.

### Preferences over time

Two statements from the **same user** about the same topic - “always use formal tone” later replaced by “prefer casual” - are treated as **supersession**, not a contradiction. The older instruction stays in history with a bounded validity interval; the newer one is current. The agent behaves on what is valid **now**, and you can still ask what the preference was in March and when it changed. See [Supersession, decay, and forget](/docs/agent-memory/mental-model/memory-lifecycle.md).

## Linking chat to catalogue facts

Experiential mentions can link to authoritative **knowledge** records (for example resolving “AirPods Pro” in chat to the same product as your catalogue) via **`resolves_to`**. See [Cross-layer linking](/docs/agent-memory/reasoning/cross-layer-linking.md).

## Unreliable or satirical uploads

**Authoritative** means *curated by you for retrieval* - not *guaranteed true*. When you upload humour, parody, or deliberately false material (for example a satirical encyclopedia article), SurrealDB Agent Memory still indexes and retrieves it like any other document. At synthesis time the response model can recognise obvious parody and **refuse to treat it as factual**, especially when you ask what the **documents** say versus a general-knowledge question.

`source.kind = "document"` carries higher default **trust** for reconciliation against experiential chat - it does not disable the model's judgement at answer time. For compliance-sensitive deployments, curate uploads and use scope/labels so agents only retrieve vetted corpora.

## Practical guidance

- **Facts with regulatory weight** - ground in **Authoritative** content.
- **Personalisation** - lives in the **Experiential** pillar.
- **Contradictions** - surface them honestly; never pick silently.
- **Scope** - catalogue updates propagate across the Context; experiential records stay scoped per user, team, or project as you configure.

The full pillar list is in [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md).

---

Source: https://surrealdb.com/docs/agent-memory/reasoning/cross-layer-linking

# Cross-layer linking

How authoritative documents and experiential facts relate. Both live in the unified graph.

Authoritative and experiential memory live in **one graph**. Document extraction and turn extraction both produce `entity`, `attribute`, and `relation` records. Provenance (`source.kind`, `source.ref`, `source.span`) records whether a fact came from a **document** or a **turn**.

## How linking works

**Same entity, multiple sources.** When a user mentions “AirPods Pro” in chat and the product manual is uploaded as a document, extraction may create or update the same `entity` (for example `Product/airpods_pro`) with:

- Turn-sourced attributes (`source.kind = "turn"`) - “purchased last week”
- Document-sourced attributes (`source.kind = "document"`) - “return_policy = 30 days”

The **reconciler** applies authority and calibration rules: cross-provenance conflicts emit **`uncertainty`** records instead of silent overwrites; same-provenance updates form supersession chains (`valid_until` on the prior assertion).

**Unified recall.** `POST /api/v1/{ctx}/query` ranks across facts and document passages in one router (structured lookup → response cache → hybrid retrieval → full-context fallback). Filter with `include` (facts vs passages) when you need a narrower slice.

**Elaboration.** Background **elaboration** can add `relation` edges between entities ingested from different sources but sharing context.

## Operational implications

| Task | Approach |
| --- | --- |
| Ground an agent on manuals | Upload documents; recall with `/query` |
| Capture conversation | `POST /facts` or `/facts/batch` |
| Prefer curated policy over chat hearsay | Trust and confidence floors; higher default trust for documents |
| Debug a conflict | Inspect `uncertainty` records and entity history |

## Manual entity maintenance

```http
GET /api/v1/{context_id}/entities/{entity_type}/{entity_name}
DELETE /api/v1/{context_id}/entities/{entity_type}/{entity_name}
```

Two **streams** in one store are described in [Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md).

---

Source: https://surrealdb.com/docs/agent-memory/reasoning/extraction-pipeline

# Extraction pipeline

How structured memory is extracted from conversation turns. Classification and extraction in SurrealDB Agent Memory.

Every conversation turn SurrealDB Agent Memory receives passes through an extraction pipeline that turns raw text into structured memory: entities, attributes, relations, instructions, and uncertainties. The pipeline balances **latency** and **accuracy** - lightweight heuristics run first; language models are invoked only when the turn needs deeper interpretation.

## How stages escalate

1. **Heuristics** - Pattern matching for known entities, temporal phrases (“since January”, “until next quarter”), and instruction-like language (“always”, “never”, “from now on”). Simple corrections to known facts can finish here without calling a model.

2. **Fast model** - Handles most turns: new entities, preferences, straightforward assertions, and routine corrections.

3. **Stronger model** - Reserved for harder cases: contradictions within one turn, ambiguous references, or output that fails structural validation.

You do not choose a stage. SurrealDB Agent Memory escalates automatically when the current stage cannot produce a confident result.

## What extraction produces

The pipeline returns a structured diff nested under **`extraction`** on **`POST /facts`**, or under each entry in **`extractions`** on **`POST /facts/batch`**:

```json
{
  "entities": [
    { "name": "Alice Chen", "type": "Person", "memory_category": "identity" }
  ],
  "attributes": [
    { "entity": "Alice Chen", "key": "role", "value": "CTO", "memory_category": "identity" }
  ],
  "relations": [
    { "subject": "Alice Chen", "verb": "works_at", "object": "Acme", "memory_category": "identity" }
  ],
  "instructions": [
    { "label": "Bullet-point responses", "description": "Always respond using bullet points" }
  ],
  "uncertainties": [
    { "about": "job title", "reason": "User joked about being CEO, then appeared to correct themselves" }
  ]
}
```

Each extracted **entity**, **attribute**, and **relation** carries a **`memory_category`** - one of `identity`, `knowledge`, or `context`. Labels the model emits outside that set (for example `emotion`, `group`) are **bucketed as `context`** during extraction rather than failing the turn. Episodic transcript material stays on **sessions** and **turns**; **instructions** and **uncertainties** are stored separately. See [Memory categories](/docs/agent-memory/mental-model/memory-categories.md).

Nothing is written blindly: extractions pass through [reconciliation](/docs/agent-memory/reasoning/reconciliation-and-supersession.md) before they become durable memory.

**Chat synthesis is separate.** `/chat` and Playground replies are generated *after* extraction and recall. The response model may add narrative colour, continue a well-known story, or answer from general knowledge - only the structured **`extraction`** diff (and reconciled graph rows) are durable memory attributed to your turns. If you paste prose from a published book, extracted **entities** and **relations** reflect what you sent; any additional storytelling in the chat reply is not automatically stored unless the extractor captures it as fact.

**Resilience:** if extraction fails for a single turn (model error, parse failure), SurrealDB Agent Memory still stores the conversational chunk and returns a chunk-only diff - the session is not aborted. Token budget exhaustion (`429`) still propagates.

## When extraction runs

For interactive agents, extraction on a turn completes **before the API returns**, so the next **`/query`**, **`/state`**, or **`/profile`** call reflects what was just said.

```bash
curl -sS "$SPECTRON_URL/api/v1/$SPECTRON_CONTEXT_ID/facts" \
  -H "Authorization: Bearer $SPECTRON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "I'\''m Alice, CTO at Acme. Always respond in bullet points.",
    "infer": "full",
    "scope": ["org/acme/user/alice"]
  }'
```

The response includes nested **`extraction`** plus `sessionId` and `turnId`.

## When extraction is incomplete

If structured extraction cannot be validated, SurrealDB Agent Memory still retains the turn text. The content remains searchable and can be reprocessed; you are not left with a silent failure. Open **`uncertainty`** records flag cases where the pipeline could not commit to a single interpretation - see [Instructions and uncertainties](/docs/agent-memory/reasoning/instructions-and-uncertainties.md).

## Untrusted metadata in prompts

Document titles, section paths, retrieved hit text, and LLM-emitted identifiers are **sanitised** before they are interpolated into extraction, chat, elaboration, or consolidation prompts - control characters and newline-based framing attacks are collapsed or truncated. This complements the ingest-time **injection scanner** (which inspects chunk body text). Sub-threshold injection findings are stored as **`uncertainty`** rows scoped to the same visibility as the write they describe.

## Related reading

- [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md)
- [Provenance and traceability](/docs/agent-memory/mental-model/provenance-and-traceability.md)
- [Adding turns](/docs/agent-memory/sessions/adding-turns.md)
- [REST API - facts and sessions](/docs/agent-memory/reference/rest-api.md)

---

Source: https://surrealdb.com/docs/agent-memory/reasoning/instructions-and-uncertainties

# Instructions and uncertainties

How behavioural directives are captured and tracked. Including ambiguous or contradictory information.

Not everything in a turn is a fact about the world. Some statements tell the agent **how to behave**; others cannot be interpreted confidently. SurrealDB Agent Memory stores these as **instructions** and **uncertainties** - separate from ordinary attributes.

## Instructions

An **instruction** is a persistent behavioural directive extracted from conversation:

- “Always respond in bullet points.”
- “Never use technical jargon.”
- “From now on, label sarcasm when you detect it.”
- “Keep responses under three sentences.”

Instructions are **active constraints** the agent should honour on subsequent turns within the relevant **scope**, not one-off preferences buried in an attribute.

### Revoking instructions

When a user reverses a directive (“stop using bullet points”), SurrealDB Agent Memory **deactivates** the matching instruction rather than deleting it. History of what was asked - and when it was revoked - remains available for audit.

### Using instructions in your agent

Active instructions appear in **`GET /profile`** ( **`instructions`** section) and **`POST /state`**. Fetch a profile before assembling a system prompt:

```http
GET /api/v1/{context_id}/profile
```

```json
{
  "instructions": [
    {
      "label": "Bullet-point responses",
      "description": "Always respond using bullet points, never flowing prose"
    }
  ]
}
```

See [Profiles](/docs/agent-memory/operations/profiles.md) for the full profile shape.

---

## Uncertainties

An **uncertainty** is recorded when extraction cannot commit to a single interpretation. The pipeline stores **what** was ambiguous and **why**, instead of guessing.

Common triggers:

- A statement followed by a contradictory correction (“I'm CEO - well, co-CEO, it's complicated”)
- Ambiguous references when several entities are in scope
- A claim that contradicts existing memory with no clear winner
- Temporal references that cannot be resolved to a date
- Sarcasm or humour that may or may not contain genuine information

### How uncertainties resolve

A later turn that clarifies the topic can resolve an open uncertainty automatically during extraction. Until then, the uncertainty stays visible so the agent can ask a follow-up question when appropriate.

Open uncertainties appear in **`POST /state`** under **`unknowns`**:

```json
{
  "unknowns": [
    {
      "about": "job title",
      "reason": "User joked about being CEO, then appeared to correct themselves",
      "resolved": false
    }
  ]
}
```

### Uncertainties and partial extractions

SurrealDB Agent Memory may still write a **tentative** attribute when extraction is partial, linked to the same turn as the uncertainty. Default current-state queries include it; applications that need higher confidence can treat attributes with open uncertainties on their source turn as lower trust until resolved.

## Related reading

- [Extraction pipeline](/docs/agent-memory/reasoning/extraction-pipeline.md)
- [Authority when pillars meet](/docs/agent-memory/reasoning/authority-hierarchy.md)
- [Profiles](/docs/agent-memory/operations/profiles.md)
- [REST API - state and profile](/docs/agent-memory/reference/rest-api.md)

---

Source: https://surrealdb.com/docs/agent-memory/reasoning/reconciliation-and-supersession

# Reconciliation and supersession

How entities are deduplicated and conflicts detected. Corrections are tracked with full history.

Extracted data is not written directly to memory. A **reconciliation** pass compares new extractions against what is already stored: matching entities, detecting conflicts, and deciding whether a new value replaces an old one or coexists alongside it.

## Reconciliation in brief

### Entity matching

SurrealDB Agent Memory attaches a new extraction to an existing entity by **exact composite key**. An entity's record id is **`[normalised_type, normalised_name]`**, and matching is a direct lookup on that id: either the record exists and the new attributes and relations land on it, or a new entity is created. There is no fuzzy, phonetic, or embedding-based matching on the write path - a name that normalises differently becomes a separate entity.

Keeping references together therefore depends on **extraction emitting a consistent name**. To support that, the extraction prompt is given the entities the Context already holds and instructed to reuse their exact names. Seeding the entities you care about before a bulk ingest measurably improves the odds that later mentions land on them.

**Type is half the identity** - the same name under two types is two entities that nothing merges. Supplied types are normalised into a closed vocabulary (`person`, `organisation`, `project`, `location`, `topic`, `product`, `policy`, `concept`, `event`, `agent`, `service`, `other`). Near-synonyms fold into it - `company`, `org`, and `organization` all become `organisation` - and a type outside the set becomes **`other`** rather than minting a new type. When you write entities directly, pick the type once and keep it stable, or a later write lands on a different record.

**Nicknames and alternative names** - because matching is exact, a referent named two ways produces two entities. `Matt` and `Matthew Cauldwell`, `IBM` and `International Business Machines`, a person referred to by first name in one document and in full in another: each pair is two records, each with its own attributes and its own supersession chains.

There is no alias field and no automatic merge. Three things help:

- **Seed canonical names first.** Create the entity under the name you want before ingesting, so it appears in the extraction prompt's known-entity list.
- **Normalise at the edge.** Map the variants you know about to one canonical name in your own pipeline, before the write.
- **Audit with [`POST /fsck`](/docs/agent-memory/reference/rest-api.md).** The `duplicates` check reports entity pairs above a cosine-similarity floor (default **`0.95`**) in the same scope. It reports candidates for you to act on; it does not merge them.

> [!NOTE]
> A **`same_as`** relation is an ordinary edge label, not an instruction to the reconciler. It records that two entities denote the same referent - useful when that is a fact the reader or agent learns at a particular moment, and gate-able with **`asOf`** - but the two entities stay separate, keep their own attributes, and are returned separately. Merging them is a decision for the layer above.

**Cross-language mentions** - the same concept in different languages (for example `skill_math` from an English conversation and `skill_matte` from a Swedish conversation) are usually stored as **separate relation labels** unless extraction or reconciliation maps them to the same entity and key. That is expected: labels carry language-specific semantics. Background **elaboration** may later link related entities. Labels cannot be constrained up front, but extraction is shown the labels already in use and told to reuse them where the meaning matches - see [Extraction vocabulary](/docs/agent-memory/tuning/ontology-grounding.md). SurrealDB Agent Memory does not automatically mint a meta-edge between every cross-language synonym.

### Attribute conflicts

When the same entity and key already have a value in the **same scope** and the new value differs, SurrealDB Agent Memory treats it as a conflict. What happens depends on where the existing value came from:

- **Experiential-only conflict** - the new value **supersedes** the old. The previous value is closed with a **`valid_until`** timestamp and remains in history.
- **Authoritative conflict** - curated knowledge (documents, operator upserts) is **not** modified. The user’s assertion is stored in experiential memory with full provenance, and the clash is surfaced (for example via **`uncertainty`** records). See [Authority when pillars meet](/docs/agent-memory/reasoning/authority-hierarchy.md).

SurrealDB Agent Memory does **not** use last-write-wins. Concurrent updates reconcile in **one ACID transaction** per write; cross-provenance clashes become **`uncertainty`**, same-stream updates **supersede** when confidence allows, and writes below the **confidence floor** cannot silently overwrite stronger beliefs.

### Confidence floor

Each Context configures **`config.reconciliation.confidence_floor`** (default **`0.7`**). On a same-provenance conflict, SurrealDB Agent Memory supersedes the prior value only when the new extraction’s confidence is at or above the floor **and** at or above the existing row’s confidence. Otherwise both values stay live and an **`uncertainty`** is recorded - a low-confidence extraction cannot erase an established high-confidence fact.

### Scope-aware coexistence

Different scopes may legitimately hold different values for the same key - an org default and a user override, or the same person in two organisations. Conflict detection runs **within one scope**. Cross-scope differences are independent records, not supersessions.

### Invalidation

When a user explicitly negates a fact (“I no longer work there”, “forget my old address”), reconciliation **closes** matching active attributes with **`valid_until`** rather than deleting them. History and audit trails stay intact.

## The supersession chain

When a fact changes over time, SurrealDB Agent Memory keeps a **chain** of values - each link records what it replaced and what replaced it. Only the **current** head of the chain is returned by default queries; earlier values remain available for history and point-in-time reads.

Example: Alice’s role moves from COO → CEO → CTO. Each step opens a new value with **`valid_from`** set to the correction time and closes the previous one with **`valid_until`**. Nothing is erased.

## Querying current and historical state

Use the public API rather than ad hoc database queries:

- **Current beliefs** - **`POST /query`** or **`GET /profile`** for a scope; entity GETs return active attributes.
- **History for one key** - **`GET /api/v1/{context_id}/entities/{type}/{name}/history/{key}`** returns the supersession chain.
- **Point in time** - entity reads accept **`asOf`**, **`atInstant`**, **`validFrom`**, and **`validUntil`** query parameters. See [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md) and the [REST API](/docs/agent-memory/reference/rest-api.md).

## Correction versus coexistence

**Correction** - same scope, new value replaces old reality (role change, corrected name). Supersession applies.

**Coexistence** - same key, different scopes, both valid in their contexts. Neither record supersedes the other.

### Unresolved relation objects

When reconciliation cannot resolve a relation’s object entity, it materialises a placeholder typed **`unknown`** so the stated fact is not dropped from recall. These placeholders may not auto-merge with a later properly typed extraction until a maintenance sweep reconciles them.

## History by default - and forget when you need removal

For **corrections and supersession**, SurrealDB Agent Memory keeps a full chain of past values. Nothing is erased: earlier beliefs stay in the store so you can audit, recover from a bad correction, or answer “what did we believe in January?”

That default does **not** mean users can never remove data. When someone asks to forget something, SurrealDB Agent Memory supports two distinct levels:

| Intent | What happens | Typical use |
| --- | --- | --- |
| **Stop using this in the agent** (default) | Matching memories are **expired** - they no longer appear in recall, query, profile, or chat context, but remain stored for audit and point-in-time review. | “Forget my old job”, entity delete, routine privacy tidying. |
| **Remove it for compliance** (explicit opt-in) | The same match is **permanently erased**, including correction history for the affected entities. | Right-to-erasure requests, retention policy enforcement. |
| **Erase a whole scope subtree** | Everything tagged under a scope path is **hard-deleted** across memory and knowledge. | GDPR-style deletion for a user or team branch. |

Default forget and entity delete are **soft**: the agent behaves as if the information is gone, while operators can still inspect what was retired. Compliance paths require an explicit **`purge`** flag on **`POST /forget`**, or **`POST /scopes/forget`** for subtree erasure - both need the **`memory:forget`** grant on the relevant scope. Audit traces may still record that a forget operation ran, without retaining the forgotten content itself.

See [Forgetting memories](/docs/agent-memory/operations/forget.md) for request shapes and CLI equivalents.

---

Source: https://surrealdb.com/docs/agent-memory/reasoning/temporal-validity

# Temporal validity

How SurrealDB Agent Memory tracks when facts were true. Using valid_from and valid_until.

Facts change. SurrealDB Agent Memory records **when each fact was true**, not only what the current value is.

Every active attribute and relation carries a **validity interval**:

- **`valid_from`** - when the fact became true (often the extraction or correction time; document facts may use an effective date from the source).
- **`valid_until`** - when the fact ceased to be true. **`null`** means still valid.

Together these fields power current-state reads, point-in-time reconstruction, and full correction history. The broader picture - system time, known time, and valid time - is in [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md).

## What temporal validity enables

**Current state** - default queries return attributes whose validity interval includes “now”.

**Point in time** - ask what was true on a past date (“what role did Alice hold in February 2025?”) using **`asOf`** or validity parameters on entity reads.

**History** - walk the supersession chain for a key to see every value it took, in order.

**Temporal language in chat** - when a turn says “I became CEO in January 2025”, “last week I moved to Berlin”, or “I used to live in Berlin”, extraction sets or adjusts validity bounds where it can. **Relative phrases** (“last week”, “three weeks ago”) resolve against the **turn’s spoken time**, not wall-clock ingest time - the same anchoring documents get from `authored_at` or caller-supplied **`observed_at`** / **`observedAt`** on ingest. Unresolved dates may become **`uncertainty`** records instead of guessed timestamps.

## How it differs from deletion

Closing a fact with **`valid_until`** is not the same as deleting it. The record stays in the store for audit and time-travel queries; it simply drops out of “what is true now”.

| Operation | Delete-and-replace | Temporal validity |
| --- | --- | --- |
| Apply correction | Old value gone | Old value closed; new value opened |
| Query current state | Latest record wins | Filter by open validity interval |
| Query past state | Often impossible | Filter by interval containing the instant |
| Undo a bad correction | Restore from backup | Reopen prior value in the chain |

## Interaction with supersession

**Supersession** tracks **why** a value changed (replaced by a newer belief). **Temporal validity** tracks **when** each value was active. A correction updates both: the old attribute gets **`valid_until`** and a link to its replacement; the new attribute gets **`valid_from`** and a link back. See [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md).

## Correction path versus expiry path

Two situations set **`valid_until`**:

**Correction** - a conflicting new value supersedes the old one. The chain links old → new.

**Expiry** - a fact ends without a replacement (a promotion that lapses, a time-boxed assignment, an instruction that was revoked). **`valid_until`** is set directly - at extraction when temporal language supplies an end date, or through lifecycle operations when you close a fact off explicitly.

Both leave the fact readable at an earlier point in time, which is what makes a correction chain auditable. [Forgetting](/docs/agent-memory/operations/forget.md) is the third case and behaves differently: it is recorded on its own axis rather than as an end to valid time, and a forgotten fact is withheld from point-in-time reads as well as from current ones.

## Narrative playback and spoiler safety

Agents that ingest an entire canon at once - every chapter of a novel, every episode of a series, every film in a franchise - **know things the user has not reached yet**. That is a problem for:

- **Serial fiction** - a reader on chapter 8 of *Dr Jekyll and Mr Hyde* must not learn that Hyde and Jekyll are the same person before the reveal page.
- **Episodic or franchise media** - a viewer on *The Empire Strikes Back* in release order must not be told Vader is Luke's father before that scene; someone watching in chronological order (I → II → III) needs a **different** spoiler boundary entirely, as the spoiler for the viewer in this case is that Anakin becomes a villain.
- **Long-running book series** - a *Wheel of Time* reader on book three should not get answers that depend on book twelve.

The fix is not to omit later material from ingest. Ingest everything, but stamp **when each fact entered the reader's timeline**, then query **`asOf`** the user's current position.

| User position | On ingest | On recall |
| --- | --- | --- |
| "I've read up to page *N*" | `observed_at` / `observedAt` per page or chapter (a synthetic timeline, not wall-clock) | `asOf` = that page's stamp |
| "I'm on episode 5 of my chosen order" | One stamp per episode in **viewing order** (release, chronological, or machete) | `asOf` = stamp for episode 5 |
| "Show me chapter 3 only" | `labels: ["chapter=3"]` on upload | `labels: ["chapter=3"]` (often with `include: ["passages"]`) |

**`asOf` gates the knowledge graph** - attributes **and relations**. A `same_as` edge between two characters only appears once the ingest stamp for the reveal page has passed. Earlier queries see separate entities with no link.

> [!NOTE]
> Raw **passages** keep wall-clock upload time. Spoiler guarantees on structured answers use the entity/attribute/relation view (`GET /entities/...?asOf=...`, or `/query` with `asOf`). For passage-level navigation ("what's on page 3?"), pair **`labels`** with `include: ["passages"]`. **`lens`** narrows by scope region at every tier (facts and passages).

See the [spoiler-safe narrative memory](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md) cookbook for franchises, non-chronological viewing orders, and page-by-page ingest recipes.

## Caller-supplied known time

When each unit of a serial (page, chapter, episode, policy revision) should appear on a **known-time axis** distinct from when you ran the import, pass **`observed_at`** on `POST /facts` (or **`observedAt`** in document upload metadata). SurrealDB Agent Memory stamps derived facts with that instant as their **known time** (`created_at`), so **`asOf`** queries reconstruct what the system would have believed at that point in the narrative - not at bulk-import time.

**Derived values ride the same axis.** A number you recompute per unit - mentions so far, a running score, sentiment to date - becomes one supersession per unit when you write it with that unit's **`observed_at`**. Reading it back with **`asOf`** returns the value as it stood at that position. See [Tracking prominence over the timeline](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md#tracking-prominence-over-the-timeline).

## Reading validity through the API

- **`GET /entities/{type}/{name}`** - active attributes for an entity; optional temporal query params on the request.
- **`GET /entities/{type}/{name}/history/{key}`** - ordered supersession history for one key.
- **`POST /query`** with **`asOf`** - belief-level recall at a past instant. **`asOf` now gates relations** as well as attributes: a relationship is visible only when its known time and validity interval include the requested instant.

Field-level schema detail lives in [Data model and schema](/docs/agent-memory/reference/data-model-and-schema.md) for operators who manage SurrealDB directly.

---

Source: https://surrealdb.com/docs/agent-memory/retrieve/graph-traversal

# Graph traversal

Structural edges in the knowledge layer and how retrieval uses them.

SurrealDB Agent Memory builds a **structural graph** during document ingest and fact extraction: documents, chunks, keywords, and entities linked by typed edges. You do not need a separate graph database - these edges live in SurrealDB alongside vectors and text.

Most retrieval uses this graph **implicitly** through **`hybrid_graph`** mode on **`POST /api/v1/{context_id}/documents/query`**, which reranks vector/BM25 hits using graph-density signals. You can also inspect or query edges directly with SurrealQL on self-hosted deployments.

> Dedicated **`/knowledge/traverse`** REST endpoints are **not shipped yet**. This page documents the **edge types that exist today** and how retrieval consumes them.

## Document-layer edges (ingest pipeline)

These relations are created automatically when documents are processed:

| Edge | From | To | Role |
| --- | --- | --- | --- |
| `knowledge_has_keyword` | Document | Keyword | Links content to RAKE keyphrases |
| `knowledge_links_to` | Document | Document | Outbound hyperlinks or citations between files |

Optional **`graph_edges`** on **`/documents/query`** select which of these signals contribute during **`hybrid_graph`** reranking (`knowledge_has_keyword`, `section_match`, `document_link`, `document_summary`). See [Hybrid search](/docs/agent-memory/retrieve/hybrid-search.md).

## Entity graph (memory + extraction)

Conversational and document extraction both write **`entity`**, **`attribute`**, and **`relates_to`** records in the unified graph. Add or update relations explicitly with **`POST /api/v1/{context_id}/facts`** and `infer: "triples"` when you already know the structure.

Unified recall via **`POST /api/v1/{context_id}/query`** can traverse one- or two-hop entity relationships as part of tier-3 hybrid retrieval - you do not call a separate traverse API.

## Example: hybrid graph query

```http
POST /api/v1/{context_id}/documents/query
Content-Type: application/json

{
  "query": "return policy restocking fee",
  "mode": "hybrid_graph",
  "limit": 10,
  "graph_edges": ["knowledge_has_keyword", "document_link"]
}
```

## Example: SurrealQL inspection (self-hosted)

```surql
-- Keywords attached to a document
SELECT ->knowledge_has_keyword->keyword AS keywords
FROM document:01hx9…;

-- Documents linked from a handbook
SELECT ->knowledge_links_to->document AS related
FROM document:01hx9…;
```

## Related reading

- [Hybrid search](/docs/agent-memory/retrieve/hybrid-search.md)
- [Keywords and BM25](/docs/agent-memory/retrieve/keywords-and-bm25.md)
- [Cross-layer linking](/docs/agent-memory/reasoning/cross-layer-linking.md)
- [Data model and schema](/docs/agent-memory/reference/data-model-and-schema.md)

---

Source: https://surrealdb.com/docs/agent-memory/retrieve/hybrid-search

# Hybrid search

Vector, BM25, and graph-density retrieval modes. How each mode behaves in SurrealDB Agent Memory.

SurrealDB Agent Memory exposes four retrieval modes for **document passages** (`POST .../documents/query`) and for the **unified** read path (`POST .../query`, which also ranks experiential facts). Each mode trades coverage, precision, and computational cost differently. See [Recalling memories](/docs/agent-memory/retrieve/recall.md) for unified recall; this page focuses on mode selection and graph-density signals.

## Answer size vs search breadth

**`k`** (and **`limit`** on `/query`) controls how many hits are returned after fusion - the **answer size**. Default **10**, maximum **50** (`SPECTRON_MAX_QUERY_K`, clamp-down only).

The **candidate pool** that vector, BM25, and graph signals search over is sized separately (`SPECTRON_RETRIEVAL_POOL_SIZE`, default **256**). Raising `k` returns more fused results but does not widen the internal search. Tier escalation (when confidence is thin) doubles the pool rather than re-running an identical pass.

Structured memories, not hundreds of raw chunks, should answer most queries - see [Coherence, retrieval, and cost tiers](/docs/agent-memory/architecture/coherence-retrieval-and-tiers.md).

## Section expansion

After ranking, SurrealDB Agent Memory can follow `chunk.section_ref` (and related section pointers) and append same-section siblings into a separate **`contextHits`** channel on `/query`. Those passages are **not** counted against `k` and do not displace ranked `hits`. `/chat` and `/reflect` synthesise over both lists; citations can resolve either. Expansion is **on by default** - set `SPECTRON_RETRIEVAL_SECTION_EXPANSION=0` to disable. See [Recalling memories](/docs/agent-memory/retrieve/recall.md#section-expansion).

## Query modes

### `vector`

Pure HNSW (hierarchical navigable small world) approximate nearest-neighbour search over dense embeddings. The query is embedded with the same model used at ingestion, and the top-k nearest chunk embeddings are returned.

Vector search excels at paraphrase and semantic similarity - finding chunks that express the same idea in different words. It is weak on exact strings, product codes, proper names, and rare terms that are poorly represented in the embedding model's training data.

The vector leg also searches **transcript segments** (`audio_chunk` rows) in the same 3072-dim embedding space as text chunks, so spoken content from audio and video documents can surface as passage hits with time-coded provenance - not only the parent chunk spine.

### `bm25`

BM25 full-text search over the chunk corpus. The query is tokenized and matched against the inverted index. Results are ranked by term frequency - inverse document frequency weighted by document length.

BM25 excels at exact terms, product identifiers, model numbers, and specific technical phrases. It is weak on synonyms and paraphrase - if the query uses a term not present in the chunk, BM25 will not find it.

### `hybrid`

Reciprocal rank fusion (RRF) of vector and BM25 results. Both retrieval passes run independently, and their ranked lists are merged into a single ranking using the RRF formula:

```text
score(d) = Σ 1 / (k + rank_i(d))
```

where `k` is a smoothing constant (default 60) and `rank_i(d)` is the rank of document `d` in retrieval pass `i`. Documents appearing in both lists receive a combined score; documents appearing in only one list are still represented with a lower combined score.

Hybrid mode is the default. It reliably outperforms either mode in isolation across a wide range of query types and is appropriate for most production deployments.

### `hybrid_graph`

Hybrid retrieval plus a graph-density reranking pass. After the initial hybrid retrieval, each candidate chunk is rescored based on its connectivity in the knowledge graph. Chunks that are more densely connected to other relevant content - via keyword co-occurrence, document-level links, typed-knowledge edges, or semantic section similarity - receive a higher rerank score.

The graph-density reranker draws on:

- **Keyword graph**: chunks linked to high-scoring keywords that match query terms
- **Typed-knowledge graph**: chunks adjacent to knowledge nodes relevant to the query
- **Section vectors**: semantic similarity between query and document section headings
- **Document links**: outbound links from the retrieved document to related documents
- **Document summaries**: summary-level similarity as a signal for topic alignment
- **Personalised PageRank**: random-walk authority scores from query-matched seed nodes

`hybrid_graph` produces the highest-quality results for complex queries requiring multi-hop reasoning, but adds latency proportional to graph depth. For simple factual lookups, `hybrid` is sufficient.

When tuning graph-density reranking, optional **`graph_edges`** selects which structural signals contribute. Each value must be one of the recognised edge kinds - typos or unknown values return `400 Bad Request` rather than being silently ignored:

| Edge kind | Signal |
| --- | --- |
| `knowledge_has_keyword` | Chunks linked to query-matching keywords |
| `section_match` | Section-heading similarity |
| `document_link` | Cross-document link density |
| `document_summary` | Document-level summary similarity |

Responses may report `hybrid_graph` on individual hits when several signals combine; that value describes merged evidence in the result, not an input filter.

## Basic query

```python
from surrealdb.memory import Memory

memory = Memory(context="acme-prod", api_key=os.environ["SPECTRON_API_KEY"])

hits = await memory.documents.query(
    query="what is the return window for unopened items?",
    mode="hybrid",
    k=10,
)

for hit in hits:
    print(hit.score)
    print(hit.chunk.text)
    print(hit.document.title)
```

```javascript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "acme-prod", apiKey: process.env.SPECTRON_API_KEY });

const hits = await memory.documents.query({
    query: "what is the return window for unopened items?",
    mode: "hybrid",
    k: 10,
});

for (const hit of hits) {
    console.log(hit.score, hit.chunk.text, hit.document.title);
}
```

## Full query with all options

```python
hits = await memory.documents.query(
    query="what is the return window for unopened items?",
    mode="hybrid_graph",
    k=10,
    threshold=0.5,          # minimum score to include in results
    vector_weight=0.5,      # relative weight of vector vs BM25 in RRF
    rrf_k=60,               # RRF smoothing constant
    graph_alpha=0.3,        # weight of graph-density rerank vs base score
    expand_graph=True,      # include one-hop keyword and typed-knowledge context
    use_hyde=False,         # Hypothetical Document Embeddings query expansion
    decompose_query=False,  # sub-question decomposition
    use_reranker=False,     # cross-encoder reranking
    filter={"mime_type": ["application/pdf"]},
    scope=["org/acme"],
)
```

## Query result structure

Each hit in the result contains:

```json
{
  "chunk": {
    "id": "chunk:01hy2…",
    "text": "Unopened items may be returned within 30 days of the original purchase date.",
    "section": "Eligibility",
    "position": 7
  },
  "score": 0.87,
  "document": {
    "id": "doc:01hx9…",
    "title": "Returns Policy",
    "source": "returns.pdf"
  }
}
```

- `chunk.position` is the chunk's ordinal position within the document, useful for retrieving surrounding context.
- `chunk.section` is the heading of the section containing this chunk, or `null` for documents without section structure.
- `score` is the normalised relevance score after all reranking passes.
- `document` provides provenance for display, citation, or follow-up retrieval.

## Advanced options

These flags are honoured on **`POST /documents/query`** and the MCP recall path when a request-path LLM provider is configured. On failure or when no LLM is attached, SurrealDB Agent Memory falls back to single-query retrieval without error.

### HyDE (Hypothetical Document Embeddings)

When `use_hyde=True`, SurrealDB Agent Memory generates a hypothetical answer to the query using the configured response model, then embeds that hypothetical answer rather than the raw query string. This improves recall for queries phrased as questions rather than document-like statements, at the cost of one LLM call per query.

```python
hits = await memory.documents.query(
    query="what is the return window for unopened items?",
    mode="hybrid",
    use_hyde=True,
)
```

### Sub-question decomposition

When `decompose_query=True`, SurrealDB Agent Memory splits complex queries into a set of simpler sub-questions, executes each independently, and merges the results. This is useful when a single query implicitly asks multiple things:

```python
hits = await memory.documents.query(
    query="what are the return and warranty policies for AirPods Pro 2?",
    mode="hybrid",
    decompose_query=True,
)
```

Decomposition adds latency proportional to the number of sub-questions and consumes additional LLM tokens. Use it for explicit multi-topic queries rather than as a default.

### Cross-encoder reranking

When `use_reranker=True`, the top-k results from the initial retrieval pass are reranked using a cross-encoder model that jointly encodes the query and each candidate chunk. Cross-encoder reranking is more accurate than bi-encoder (embedding) similarity but significantly slower.

**Requires server configuration:** set `SPECTRON_RERANKER_URL` and `SPECTRON_RERANKER_MODEL` at startup (see [Configuration](/docs/agent-memory/reference/configuration.md#reranker-optional)). Without a reranker provider, `use_reranker=true` falls through to bi-encoder ordering.

```python
hits = await memory.documents.query(
    query="return policy for international purchases",
    mode="hybrid",
    use_reranker=True,
    k=20,   # retrieve more candidates for the reranker to rescore
)
```

### Metadata filtering

Apply hard filters before retrieval to restrict results to a subset of the document corpus:

```python
hits = await memory.documents.query(
    query="Q3 revenue",
    mode="hybrid",
    filter={
        "mime_type": ["application/pdf"],
        "scope": ["org/acme"],
    },
)
```

Filters are applied before scoring, so they do not affect the ranking of results that pass through.

## Choosing a mode

| Query pattern | Recommended mode |
|---|---|
| General natural-language questions | `hybrid` |
| Product codes, model numbers, exact phrases | `bm25` |
| Paraphrase, synonym-heavy queries | `vector` |
| Complex multi-hop reasoning, graph context | `hybrid_graph` |
| High-recall requirements (academic, legal) | `hybrid` + `use_reranker=True` |

---

Source: https://surrealdb.com/docs/agent-memory/retrieve/keywords-and-bm25

# Keywords and BM25

Keyword extraction and full-text search. How both work in SurrealDB Agent Memory's knowledge layer.

SurrealDB Agent Memory combines two statistical retrieval mechanisms - RAKE keyword extraction and BM25 full-text search - to complement dense vector retrieval. Together, they ensure that exact terms, product identifiers, technical phrases, and domain vocabulary are reliably findable even when their embedding representation is weak.

## RAKE keyword extraction

During document ingestion, SurrealDB Agent Memory applies RAKE (Rapid Automatic Keyword Extraction) to each document. RAKE is a statistical, language-agnostic algorithm that identifies multi-word keyphrases by exploiting word co-occurrence and word frequency without requiring a language model or training data.

SurrealDB Agent Memory extracts the **top-5 keyphrases per document** with a RAKE score of **0.8 or above**. These keyphrases are stored as keyword nodes in the knowledge graph and linked to their source documents via `knowledge_has_keyword` edges.

Because RAKE runs without an LLM, it adds negligible latency to the ingestion pipeline and incurs no token cost.

### Why keyword extraction matters

Embedding models represent meaning holistically. A query for "MLWK3LL/A" (a product SKU) may produce a low-similarity result even against a document that contains that exact string, because the embedding model has not learned to weight arbitrary identifier strings meaningfully. BM25 and keyword graph lookups have no such blind spot - they operate on token identity, not semantic proximity.

Keywords extracted by RAKE also serve as seeds for `hybrid_graph` retrieval. When a query matches a keyword node, the retrieval pass can expand to all documents linked to that keyword before the final scoring step.

## Keyword endpoints

### Keywords for a document

Retrieve the keyphrases extracted from a specific document:

```python
from surrealdb.memory import Memory

memory = Memory(context="acme-prod", api_key=os.environ["AGENT_MEMORY_API_KEY"])

keywords = await memory.documents.keywords.for_document(doc.id)
for kw in keywords:
    print(kw.text, kw.score)
# RETURN POLICY       1.8
# UNOPENED ITEMS      1.5
# 30 DAYS             1.2
# PURCHASE DATE       1.1
# INTERNATIONAL ORDERS 0.9
```

```javascript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "acme-prod", apiKey: process.env.AGENT_MEMORY_API_KEY });

const keywords = await memory.documents.keywords.forDocument(doc.id);
for (const kw of keywords) {
    console.log(kw.text, kw.score);
}
```

### List keywords across the corpus

List all keywords in the Context, optionally filtered by minimum document count:

```python
# Keywords that appear in at least 3 documents, sorted by frequency
keywords = await memory.documents.keywords.list(
    min_document_count=3,
    sort="-document_count",
)
for kw in keywords:
    print(kw.text, kw.document_count)
```

### Get a keyword by text

```python
detail = await memory.documents.keywords.get("RETURN POLICY")
print(detail.text)            # RETURN POLICY
print(detail.score)           # 1.8
print(detail.document_count)  # 12
print([d.id for d in detail.documents])
```

### Vector search over keywords

Find keywords semantically similar to a query string. This uses the keyword text embeddings rather than the chunk embeddings, making it useful for building keyword-expansion pipelines:

```python
similar = await memory.documents.keywords.search("refund policies", k=10)
for kw in similar:
    print(kw.text, kw.similarity)
# RETURN POLICY     0.94
# REFUND WINDOW     0.91
# EXCHANGE POLICY   0.88
```

## BM25 full-text search

SurrealDB Agent Memory indexes all chunk text in a full-text index using the `spectron_analyzer`. The analyzer applies two tokenisation passes:

- **Blank tokenizer**: splits on whitespace only, preserving punctuation within tokens (useful for product codes like `MLWK3LL/A`)
- **Class tokenizer**: splits on character class boundaries (letter/digit transitions), which separates alphanumeric identifiers without requiring spaces

Both passes feed through two token filters:

- **Lowercase**: case-normalisation for consistent matching
- **Snowball**: English stemming (e.g. `returning` → `return`, `purchases` → `purchas`)

This combination means that BM25 matches both exact-cased identifiers (via blank tokenizer) and inflected natural-language terms (via class tokenizer + Snowball).

BM25 is invoked automatically when you use `mode="bm25"` or `mode="hybrid"` in the query endpoint. The BM25 score contributes to the RRF fusion in hybrid mode.

```python
# Pure BM25 - best for exact-term matching
hits = await memory.documents.query(
    query="MLWK3LL/A MacBook Pro",
    mode="bm25",
    k=10,
    scope=["org/acme"],
)
```

## Keyword graph in hybrid_graph retrieval

When `mode="hybrid_graph"` is used, keywords serve as intermediate nodes in the graph-density rerank. The retrieval pipeline:

1. Runs hybrid (vector + BM25) to produce initial candidates.
2. Embeds the query and finds the top-k matching keyword nodes.
3. Expands from those keyword nodes through `knowledge_has_keyword` edges to include additional chunks that share relevant keywords.
4. Boosts the score of candidates reachable through those keyword bridges and other graph-density signals.

This means a query containing "returns" will boost any chunk connected to the keywords `RETURN POLICY`, `RETURN WINDOW`, and `REFUND` - even if the chunk's embedding or term frequency score would not have ranked it highly on its own.

**Scope gating:** keyword listing, BM25 candidate reads, and graph-density expansion (PageRank hops over `knowledge_has_keyword` and typed relation edges) honour the same scope visibility model as unified recall - edges and keyword records outside the caller’s grant are not traversed.

## When to use keyword versus vector search

| Query type | Recommended mode |
|---|---|
| Product SKU, model number, API key fragment | `bm25` |
| Exact phrase from a document | `bm25` |
| Natural-language question (paraphrase likely) | `vector` or `hybrid` |
| Mixed: named entities + prose context | `hybrid` |
| Graph-connected topic exploration | `hybrid_graph` |
| Keyword discovery and corpus analysis | Keyword endpoints |

In practice, `hybrid` or `hybrid_graph` are the right defaults for most production queries. Use `bm25` directly only when you have strong reason to believe exact-match precision matters more than recall - for example, in lookup-style queries where the answer is a specific document known to contain an exact string.

---

Source: https://surrealdb.com/docs/agent-memory/retrieve/recall

# Recalling memories

Unified retrieval over facts and document passages.

SurrealDB Agent Memory exposes one fused read path over the **unified substrate**: structured facts from turns and passages from documents. Use **`POST /api/v1/{context_id}/query`** for ranked hits and **`POST /api/v1/{context_id}/context`** for a pre-formatted LLM block.

Register scope paths before use (`spectron scopes create org/acme/user/alice`). See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md).

## Ranked hits - `/query`

```bash
spectron recall "What role does Alice have?" --json \
  --url "$SPECTRON_URL" \
  --api-key "$SPECTRON_API_KEY" \
  --context-id "$SPECTRON_CONTEXT_ID" \
  --limit 10
```

Pass **`lens`** on the REST body (the CLI does not expose a `--scope` flag on `recall` today):

```http
POST /api/v1/{context_id}/query
Authorization: Bearer <key>
Content-Type: application/json

{
  "query": "What role does Alice have?",
  "k": 10,
  "lens": [["org/acme/user/alice"]]
}
```

**Response fields:**

| Field | Meaning |
| --- | --- |
| `tier` | `direct`, `cache`, `hybrid`, or `full_context` - relational questions may resolve at tier 1 when the classifier routes to a structured relation read |
| `hits` | Ranked results; each hit includes **`source`** (`entity`, `attribute`, `memory_chunk`, `chunk`, or `section`) and optional **`occurredAt`** (known time of the row - use to resolve relative dates in source text) |
| `contextHits` | Same-section sibling passages pulled by **section expansion** - separate from `hits`, not counted against `k`. Empty when expansion is off or found nothing to add. Useful for UIs that show “expanded context” beside the ranked answer set |
| `queryMs` | Server-side latency in milliseconds |
| `trace.traceId` | Correlates with `GET .../traces/{traceId}` |

Optional read modifiers: **`labels`** (descriptor filter only), **`lens`** (DNF scope filter by involvement), **`asOf`** (known-time playback - hide facts and relations from later chapters or episodes; see [spoiler-safe narrative memory](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md)), **`scope_view`** (`strict`, `crossTeam`, `merged` - the latter two resolve like **`strict`**; see [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md#labels-lens-and-scope-views-reads)), **`include`** (`facts`, `passages`, or both - narrows the response only; retrieval considers all families), **`includeDuplicates`** (default `false` - excludes **near-duplicate** document passages and conversation `memory_chunk` rows flagged via SimHash / `duplicate_of`; set `true` to include them, matching `/documents/query`). Exact byte-identical document uploads are still collapsed at ingest (`content_hash`); `includeDuplicates` does not resurrect those.

**`k`** defaults to **10** and is capped at **50** per deployment (`SPECTRON_MAX_QUERY_K`). The internal retrieval pool is independent of `k` - see [Hybrid search](/docs/agent-memory/retrieve/hybrid-search.md#answer-size-vs-search-breadth).

### Section expansion

When a ranked hit lands on a section heading or other “pointer” chunk, SurrealDB Agent Memory can pull the same-section siblings into **`contextHits`** so synthesis (and `/chat` citations) see the section body, not only the heading. Expansion is **on by default** (`SPECTRON_RETRIEVAL_SECTION_EXPANSION`); set it to `0` to opt out. It is read-path only, capped, scope-gated, and does not change the ranked `hits` list - so eval and ranker baselines that read only `hits` stay stable. MCP **`recall`** returns the ranked `hits` path; `/chat` and `/context` fold expanded passages into synthesis.

CLI flags: `--limit` (maps to `k`), `--mode hybrid|vector|bm25|graph`, `--include facts,passages`, tri-temporal `--as-of`, `--at-instant`, `--valid-from`, `--valid-until`.

> [!NOTE]
> **`--min-trust`** is not supported yet - the `/query` response does not expose per-hit trust scores, so the CLI rejects the flag rather than silently returning unfiltered results.

## Formatted context - `/context`

Use when you want a single string for system-prompt injection:

```http
POST /api/v1/{context_id}/context
Content-Type: application/json

{
  "query": "What role does Alice have?",
  "k": 10,
  "lens": [["org/acme/user/alice"]]
}
```

```bash
spectron context "What role does Alice have?"
```

## Session-scoped context

```http
GET /api/v1/{context_id}/sessions/{session_id}/context
```

Returns recall formatted for the session’s scope and recent turns.

## Document-only query

For retrieval limited to uploaded files:

```http
POST /api/v1/{context_id}/documents/query
```

```bash
spectron recall "return policy" --include passages
```

## Chat (composed recall + synthesis)

```http
POST /api/v1/{context_id}/chat
```

```bash
spectron chat "Summarise what you know about Alice"
```

SurrealDB Agent Memory runs recall internally, then calls the configured response model. Use `--stream` for SSE.

## Profile and state

```http
GET  /api/v1/{context_id}/profile
POST /api/v1/{context_id}/state
```

`profile` returns category-grouped attributes (identity, knowledge, context, instructions) for prompt assembly.

## Four-tier router

See [Coherence, retrieval, and cost tiers](/docs/agent-memory/architecture/coherence-retrieval-and-tiers.md). Tiers progress from cheap structured lookup through response cache, hybrid vector + BM25 + graph, to full-context synthesis. Mode details: [Hybrid search](/docs/agent-memory/retrieve/hybrid-search.md), [Keywords and BM25](/docs/agent-memory/retrieve/keywords-and-bm25.md), [Graph traversal](/docs/agent-memory/retrieve/graph-traversal.md).

---

Source: https://surrealdb.com/docs/agent-memory/sessions/adding-turns

# Adding turns

How to add conversation turns. Interpreting the structured extraction result.

A **turn** is a single message in a conversational thread. After a session is open, every message - whether from the user, the assistant, the system, or a tool - is recorded as a turn. SurrealDB Agent Memory processes each turn synchronously through an extraction pipeline and returns a structured diff of everything that was learned or changed.

## The turns endpoint

```http
POST /api/v1/{context_id}/sessions/{session_id}/turns
Content-Type: application/json

{
  "role": "user",
  "content": "I just got promoted to CTO"
}
```

The request blocks until extraction is complete, then returns the structured result. Extraction is synchronous by design: the caller can use the returned diff to update application state, drive real-time UI, or trigger downstream logic without polling.

## Roles

| Role | When to use |
|---|---|
| `user` | Messages from the human participant. Extraction is most active on user turns. |
| `assistant` | Responses generated by the LLM. Recording assistant turns preserves the full conversational context and allows SurrealDB Agent Memory to track what the assistant has stated or committed to. |
| `system` | System prompt or instruction turns. SurrealDB Agent Memory extracts behavioural instructions and constraints from system turns. |
| `tool` | Results returned from tool calls. SurrealDB Agent Memory extracts factual content from tool results and attributes it to the tool source. |

All four roles participate in provenance. Each turn is stored with its role, and every fact extracted from it carries a `source_turn` reference regardless of role.

## Extraction pipeline

When a turn arrives, SurrealDB Agent Memory runs the following steps synchronously before returning:

1. **Named entity recognition** - identifies people, organisations, locations, products, and custom entity types configured in your context.
2. **Attribute extraction** - extracts scalar properties of entities (job title, location, preference, status, and so on).
3. **Relation extraction** - identifies directed relationships between entities.
4. **Instruction detection** - recognises statements that express user preferences, behavioural instructions, or explicit requests about how the agent should behave.
5. **Uncertainty flagging** - marks statements that are speculative, negated, or otherwise of low confidence.
6. **Reconciliation** - compares newly extracted facts against existing memory. Facts that contradict prior records are resolved and a correction record is produced. See [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md) for the full correction model.

## Python SDK

```python
result = await memory.remember("I just got promoted to CTO", session_id=session.id, role="user")
```

## JavaScript SDK

```javascript
const result = await memory.remember("I just got promoted to CTO", { sessionId: session.id, role: "user" });
```

## Extraction result

The response from a turn submission contains the full extraction diff for that turn.

```json
{
  "turn_id": "turn:01hy2…",
  "entities": [
        { "id": "entity:01hy2a…", "type": "person", "label": "Alice",
        "source_turn": "turn:01hy2…" }
  ],
  "attributes": [
    {
      "id": "attr:01hy2b…",
      "entity": "entity:01hy2a…",
      "key": "job_title",
      "value": "CTO",
      "valid_from": "2026-05-12T16:24:00Z",
      "source_turn": "turn:01hy2…"
    }
  ],
  "relations": [],
  "instructions": [],
  "uncertainties": [],
  "corrections": []
}
```

### `turn_id`

The stable identifier for this turn. Store it to correlate later reads - for example to label a `/state` snapshot taken immediately after the turn.

### `entities`

Entities named or implied in the turn content. Each entity carries:

- `id` - stable record identifier
- `type` - entity type (e.g. `person`, `organisation`, `project`)
- `label` - the canonical display name
- `source_turn` - the turn that first introduced this entity

An entity record is created once and then referenced by subsequent attributes and relations. If an entity was already known, the existing record is returned rather than a duplicate.

### `attributes`

Scalar facts about entities. Each attribute carries:

- `entity` - the entity the attribute belongs to
- `key` - the attribute name (e.g. `job_title`, `location`, `preference`)
- `value` - the extracted value
- `valid_from` - when the attribute became valid (defaults to the turn timestamp)
- `valid_until` - when the attribute ceased to be valid, if known
- `source_turn` - the turn that produced this attribute

Attributes are versioned. If the same key already exists for an entity and the new value differs, the prior attribute is superseded rather than overwritten. The supersession chain is preserved so the history of a fact is always queryable.

### `relations`

Directed relationships between two entities. Each relation carries:

- `from` - source entity
- `to` - target entity
- `type` - relationship type (e.g. `works_at`, `manages`, `owns`)
- `source_turn` - the turn that established the relation

### `instructions`

Behavioural directives extracted from the turn. These are statements where the user or system is expressing a persistent preference or rule, such as "always reply in French" or "never mention competitors by name." Instructions are surfaced in the context payload and injected into prompts automatically when using the managed chat loop.

Each instruction carries:

- `text` - the normalised instruction text
- `priority` - extracted priority level (`high`, `medium`, `low`)
- `source_turn` - the turn that introduced the instruction

### `uncertainties`

Statements extracted from the turn that SurrealDB Agent Memory cannot assert with confidence - negated claims, speculative language, conflicting signals, or information that requires human verification. These are stored separately from confirmed attributes and are surfaced in the state under `unknowns`.

### `corrections`

When reconciliation finds that a newly extracted fact contradicts an existing attribute, a correction record is produced. Each correction carries:

- `attribute` - the attribute identifier that was corrected
- `previous` - the prior value and its source turn
- `current` - the new value and this turn as source
- `timestamp` - when the correction occurred

Corrections are the mechanism through which SurrealDB Agent Memory handles conversational drift - for example, when a user corrects a name, updates their role, or revises a stated preference. See [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md) for how the supersession chain works.

## Using the extraction result for real-time UI

Because the extraction result returns synchronously with the turn response, you can use it to drive application state immediately after each message is submitted.

A common pattern is to compare the returned arrays against your local state and highlight changes in the UI:

```python
result = await memory.remember("Actually I moved to Berlin last month", session_id=session.id, role="user")

for attr in result.attributes:
    ui.render_attribute(attr.entity, attr.key, attr.value)

for correction in result.corrections:
    ui.strike_through(correction.previous.value)
    ui.highlight_new(correction.current.value)
```

```javascript
const result = await memory.remember("Actually I moved to Berlin last month", { sessionId: session.id, role: "user" });

for (const attr of result.attributes) {
    ui.renderAttribute(attr.entity, attr.key, attr.value);
}

for (const correction of result.corrections) {
    ui.strikeThrough(correction.previous.value);
    ui.highlightNew(correction.current.value);
}
```

## Recording assistant turns

When you are driving the LLM loop yourself (as opposed to using `chat()`), record the assistant's response as a turn after each generation:

```python
user_result = await memory.remember(user_message, session_id=session.id, role="user")

context = await session.context(query=user_message)
reply = your_llm.generate(system=context.context, user=user_message)

assistant_result = await memory.remember(reply, session_id=session.id, role="assistant")
```

Recording assistant turns preserves the full conversational record and allows SurrealDB Agent Memory to extract any facts the assistant stated - commitments, stated knowledge, or preferences expressed on behalf of the agent. Omitting assistant turns is permitted, but the resulting memory will have gaps that could reduce context quality on subsequent retrieval.

## Listing turns

To retrieve all turns recorded in a session:

```http
GET /api/v1/{context_id}/sessions/{session_id}/turns
```

```python
turns = await session.turns()
```

```javascript
const turns = await session.turns();
```

The response is an ordered list of turns with their roles, content, and timestamps. Extraction results are not re-included in this listing; use the `/state` endpoint to query the facts that were produced.

---

Source: https://surrealdb.com/docs/agent-memory/sessions/chat-sessions

# Chat sessions

Using chat() for conversation loops managed by SurrealDB Agent Memory.

The `chat` endpoint lets SurrealDB Agent Memory manage the entire LLM conversation loop in a single call. Rather than recording a turn, retrieving context, calling your LLM, and recording the assistant's reply separately, you hand all of that to SurrealDB Agent Memory and receive the model's reply along with a memory diff in one response.

## Two integration patterns

SurrealDB Agent Memory supports two ways to integrate memory into a conversation:

| Pattern | Use when |
|---|---|
| **SurrealDB Agent Memory drives the loop** (`chat()`) | You want minimal integration surface. SurrealDB Agent Memory handles context retrieval, model invocation, and memory persistence. |
| **Caller drives the loop** (`remember()` + `sessions.context()`) | You need control over the model call - custom prompts, streaming, tool use, multi-step chains, or your own model infrastructure. |

The two patterns are not mutually exclusive. A single session can use `chat` for simple turns and `turn` + `context` for turns that require tool use or custom prompting.

## What SurrealDB Agent Memory does in `chat`

When you call `chat()`, SurrealDB Agent Memory executes the following steps:

1. **Loads the session transcript window** - the last **10** user/assistant turns of the same session, capped at **8k characters** of the newest content. Rendered as a sanitised `# Conversation so far` block inside the system prompt (one line per turn, inside the existing untrusted-data framing). The user message for this request is not yet stored; it appears under `# Question` after a successful reply.
2. **Retrieves context** - runs the tiered retrieval strategy. Turns already in the transcript window are not retrieved again (the prompt already includes them). Older turns from the same session remain eligible. Prior assistant replies rank below user statements and document sources when both match; citations from those hits include **`role: "assistant"`**.
3. **Calls the response model** - invokes the configured `response` model with retrieved context, the transcript window (when non-empty), and the current question.
4. **Persists both turns** - on a successful reply, stores the user message (`infer: full`) and the assistant reply (`infer: none`, so the reply is not re-extracted).
5. **Returns** the reply text, the memory diff from the user turn's extraction, and a **`citations`** list when the model cites retrieved sources with `[S1]`-style markers.

When the transcript window is non-empty, **tier-2 response reuse is bypassed** - the cache is keyed by query embedding and caller scope, not session, so transcript-dependent replies must neither be served from nor seeded into the cache. First-turn and sessionless calls remain cache-eligible.

Multi-turn chat pins a session across messages. Under scope enforcement, each turn's session is **scope-tagged** to the caller's write region so later messages in the same session authorise correctly.

## The `chat` endpoint

```http
POST /api/v1/{context_id}/chat
Content-Type: application/json

{
  "message": "What do you know about me?",
  "sessionId": "sess_01jt4kx…"
}
```

There is no per-session chat route: `/chat` is a Context-level endpoint and the
session is pinned with `sessionId` in the body. Omit it and the handler
auto-creates a session. The message field is **`message`**, not `content`.

Response:

```json
{
  "reply": "Based on what you've shared, you're Alice, CTO at Acme,
    based in Berlin…",
  "memory_updates": {
    "entities": [],
    "attributes": [],
    "relations": [],
    "corrections": []
  },
  "citations": [
    {
      "marker": "[S1]",
      "id": "chunk:…",
      "kind": "chunk",
      "snippet": "Alice is CTO at Acme…",
      "score": 0.82,
      "documentTitle": "Team handbook",
      "positionPercent": 34
    }
  ]
}
```

### `reply`

The plain-text response from the configured response model. This is ready to display directly in your UI. Inline `[S1]` markers correspond to entries in **`citations`**.

### `citations`

One entry per cited marker. Each resolves to a source row (`id`, `kind`, `snippet`, `score`, optional `occurredAt`, optional **`role`**). Document passages also include **`documentTitle`** and **`positionPercent`** (approximate percentage through the document). When a citation points at a stored assistant reply, **`role`** is `"assistant"` so callers can tell chat is citing its own earlier output. Markers never invent sources - only rows from retrieval (including section-expansion context) are cited.

### `memory_updates`

The extraction diff from the user turn (the assistant reply is stored with `infer: none`). The shape matches the extraction result from `remember()`. Use this to update UI state or trigger any downstream logic that depends on changes to the memory graph.

## Python SDK

```python
from surrealdb.memory import Memory

memory = Memory(context="acme-prod",
    api_key=os.environ["AGENT_MEMORY_API_KEY"])
session = await memory.sessions.create(scopes=["org/acme/user/alice"])

reply = await memory.chat("What do you know about me?", session_id=session.id)

print(reply["reply"])          # The model's response string
print(reply["citations"])      # One entry per [S1] marker
print(reply["memoryUpdates"])  # Entities, attributes, relations, corrections
```

## JavaScript SDK

```javascript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "acme-prod",
    apiKey: process.env.AGENT_MEMORY_API_KEY });
const session = await memory.sessions.create({ scopes: ["org/acme/user/alice"] });

const reply = await memory.chat("What do you know about me?", { sessionId: session.id });

console.log(reply.reply);          // The model's response string
console.log(reply.memoryUpdates);  // Entities, attributes, relations,
    corrections
```

## Full loop example

The following pattern shows a minimal chat loop using `chat()`:

```python
import os
from surrealdb.memory import Memory

memory = Memory(context="acme-prod",
    api_key=os.environ["AGENT_MEMORY_API_KEY"])
session = await memory.sessions.create(scopes=["org/acme/user/alice"])

while True:
    user_input = input("You: ")
    if user_input.lower() in {"exit", "quit"}:
        break

    result = await memory.chat(user_input, session_id=session.id)
    print(f"Agent: {result.reply}")

    if result.memory_updates.corrections:
        for c in result.memory_updates.corrections:
            print(f"  [memory corrected] {c.previous.value!r} → {c.current.value!r}")

await session.close()
```

```javascript
import * as readline from "node:readline";
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "acme-prod",
    apiKey: process.env.AGENT_MEMORY_API_KEY });
const session = await memory.sessions.create({ scopes: ["org/acme/user/alice"] });

const rl = readline.createInterface({ input: process.stdin,
    output: process.stdout });

const ask = () => rl.question("You: ", async (input) => {
    if (input.trim().toLowerCase() === "exit") {
        await session.close();
        rl.close();
        return;
    }

    const result = await memory.chat(input, { sessionId: session.id });
    console.log(`Agent: ${result.reply}`);

    for (const c of result.memoryUpdates.corrections) {
        console.log(`  [memory corrected] ${c.previous.value} → ${c.current.value}`);
    }

    ask();
});

ask();
```

## Configuring the response model

The model used for response generation is configured at the context level in `config.models.response`. This is separate from the extraction model (`config.models.extraction`) used to parse turns into structured facts.

```yaml
# spectron.config.yaml
models:
  extraction: openai/gpt-4o-mini
  response: openai/gpt-4o
```

You may use any provider supported by your SurrealDB Agent Memory deployment. The extraction and response models can differ - a faster, cheaper model is often suitable for extraction while a more capable model handles final response generation.

See [Configuration](/docs/agent-memory/reference/configuration.md#default-provider-api-keys) for the supported providers and how to configure API keys.

## When to use `remember()` instead

Use `remember()` plus your own model call when you need:

- **Streaming responses** - a non-streaming `chat()` waits for the full completion before returning. If you require token-level streaming to your UI, drive the LLM loop yourself.
- **Tool use** - if your agent needs to call external tools mid-conversation, you need control over the turn-taking loop to interleave tool calls and results.
- **Custom prompting** - if you maintain your own system prompt structure, persona configuration, or prompt template that cannot be expressed through SurrealDB Agent Memory's context injection format.
- **Multiple models per turn** - if your architecture uses a router, ensemble, or chain of models.
- **Explicit context control** - if you want to inspect or modify the retrieved context string before it reaches the model.

The `session.context()` method retrieves the formatted context string that SurrealDB Agent Memory would have injected, so you can replicate the retrieval step in the manual pattern. See [Retrieving context](/docs/agent-memory/retrieve/recall.md) for details.

---

Source: https://surrealdb.com/docs/agent-memory/sessions/creating-sessions

# Creating sessions

How to create and manage conversation sessions. Covers session lifecycle in SurrealDB Agent Memory.

A **session** is the fundamental unit of conversation in SurrealDB Agent Memory. It is a scoped, persistent container that groups all turns, extracted facts, and derived knowledge from a single conversational thread. Every entity, attribute, relation, instruction, and uncertainty produced by SurrealDB Agent Memory carries a reference to the session and turn it was extracted from, giving the memory layer complete provenance.

## Why sessions exist

SurrealDB Agent Memory stores memory as a structured graph of discrete facts rather than a bag of text chunks. For that model to be useful, each fact must answer two questions:

- **Where did it come from?** Which conversation, which message, which principal?
- **Who does it apply to?** Which user, organisation, or project?

Sessions answer both. The session is the provenance anchor: every extracted fact carries a `source_turn` that links back through the turn to the session. The session is also the scope container: when you create a session, you declare **scope tags** - user, organisation, project, or any combination - and data produced in that session inherits them.

## Session scope

Scope on create is a **DNF selector** (`scopes`): an OR of conjunctive slash-path clauses (for example `[["org/acme/user/alice"]]`). Register paths with `spectron scopes create` before first use. SurrealDB Agent Memory propagates the session scope to all records created during the session.

```json
[["org/acme/user/alice"]]
```

Scope is used in two places:

1. **Isolation**: queries against the state or recall endpoints are filtered to the declared scope, so a user cannot retrieve facts that belong to another user in the same context.
2. **Attribution**: corrections and supersessions track which scoped principal a fact belongs to, so any comparison of two `/state` reads stays relative to a coherent scope.

A session scoped to `["org/acme"]` (without a `user/…` segment) accumulates org-level knowledge rather than user-level knowledge.

## Creating a session

### REST

```http
POST /api/v1/{context_id}/sessions
Content-Type: application/json

{
  "scopes": [["org/acme/user/alice"]],
  "metadata": { "channel": "web-chat", "locale": "en-GB" }
}
```

Response:

```json
{
  "id": "session:01hx7…",
  "scopes": [["org/acme/user/alice"]],
  "createdAt": "2026-05-12T16:24:00Z"
}
```

The `metadata` field is optional. Use it to attach arbitrary operational data - channel, locale, application version - that should travel with the session record for auditing purposes but is not used by SurrealDB Agent Memory's extraction pipeline.

### Python SDK

```python
from surrealdb.memory import Memory

memory = Memory(context="acme-prod", api_key=os.environ["AGENT_MEMORY_API_KEY"])

session = await memory.sessions.create(
    scopes=["org/acme/user/alice"],
    metadata={"channel": "web-chat"}
)

print(session.id)       # session:01hx7…
print(session.scope)    # ["org/acme/user/alice"]
```

### JavaScript SDK

```javascript
import { AgentMemory } from "@surrealdb/memory";

const memory = new AgentMemory({ context: "acme-prod", apiKey: process.env.AGENT_MEMORY_API_KEY });

const session = await memory.sessions.create({
    scopes: [["org/acme/user/alice"]],
    metadata: { channel: "web-chat" },
});

console.log(session.id);     // session:01hx7…
console.log(session.scopes);   // [["org/acme/user/alice"]]
```

## Session lifecycle

A typical session follows three stages:

### 1. Create

Call `POST /api/v1/{context_id}/sessions` (or `memory.sessions.create()` in the SDK) once per conversational thread. Store the returned session ID in your application state alongside the conversation context.

### 2. Add turns

As messages flow through your application, record each one by posting to the turns endpoint. SurrealDB Agent Memory extracts structured facts from each turn synchronously and returns the extraction result immediately. See [Adding turns](/docs/agent-memory/sessions/adding-turns.md) for the full extraction result shape.

### 3. Close

When the conversation ends, delete the session:

```http
DELETE /api/v1/{context_id}/sessions/{session_id}
```

```python
await session.close()
```

```javascript
await session.close();
```

Closing a session does not delete the extracted data. All entities, attributes, relations, and other facts persisted during the session remain in the memory layer, linked to their source turns, and continue to be available for retrieval and state queries. Closing merely marks the session as ended and releases any server-side resources held for it.

## Session write access

Appending turns (via **`POST .../sessions/{id}/turns`**, **`/chat`**, or a pinned **`session_id`** on **`/facts`** / **`/facts/batch`**) requires the caller's **`memory:write`** grant to cover the **session's scope**. SurrealDB Agent Memory resolves the session through the same scope predicate used for reads - a principal cannot append to a session outside its write region, even if it knows the session id.

Absent and out-of-region sessions both return **`403 Forbidden`** with the same error shape so callers cannot probe for valid session ids.

Creating a **new** session (no pinned id) uses the caller's resolved write region for the new session's scope tags.

The ownership restriction applies to **writes** only. Reading state and retrieving context from a session may be permitted to other principals depending on the access control configuration of the context. See [Permissions and delegation](/docs/agent-memory/mental-model/contexts-and-scope.md#permissions-and-delegation) for details.

## One session per thread

Create one session per conversational thread. Do not reuse a session for unrelated conversations: scope and provenance are inherited from the session record, so mixing conversations produces mixed attribution. If a user starts a fresh chat window, create a new session even if it is the same user.

For long-running agents that operate continuously across many user interactions, the pattern is the same - one session per logical thread - with each new top-level invocation or user-initiated conversation opening a new session.

---

Source: https://surrealdb.com/docs/agent-memory/sessions/state-and-diffs

# State and diffs

Reading structured memory state. Also tracking what changed between turns.

The state endpoints give you a structured, queryable view of everything SurrealDB Agent Memory has learned within a given scope. Rather than replaying the conversation history, you query the current knowledge graph directly - grouped by category and filtered by scope - and receive a clean, structured representation of what the agent knows.

## The `/state` endpoint

```http
GET /api/v1/{context_id}/state?scope[user]=alice&scope[org]=acme
```

The scope parameters filter the state to the specified dimensional intersection. Omitting a dimension broadens the query - `scope[org]=acme` without a user returns the union of all user-level knowledge for that organisation.

Response:

```json
{
  "identity": {
    "entities": [
      {
        "id": "['person', 'alice']",
        "name": "Alice",
        "entityType": "person",
        "memoryCategory": "identity",
        "importance": 1.0,
        "createdAt": "2026-05-12T16:24:00Z",
        "updatedAt": "2026-05-12T16:24:00Z"
      }
    ],
    "attributes": [
      {
        "id": "attr_01hy2c",
        "entity": "entity:person/alice",
        "key": "job_title",
        "value": "CTO",
        "memoryCategory": "identity",
        "importance": 1.0,
        "validFrom": "2026-05-12T16:24:00Z",
        "validUntil": null,
        "supersedes": "attr_01hy1b",
        "supersededBy": null,
        "source": { "kind": "turn", "ref": "turn:01hy2…" },
        "createdAt": "2026-05-12T16:24:00Z"
      }
    ],
    "relations": []
  },
  "knowledge": { "entities": [], "attributes": [], "relations": [] },
  "context": { "entities": [], "attributes": [], "relations": [] },
  "instructions": [
    {
      "id": "instr_01hy4d",
      "label": "language",
      "description": "Always respond in German when the user writes in German."
    }
  ],
  "unknowns": [
    {
      "about": "Whether Alice holds any equity in Acme.",
      "reason": "Equity was never mentioned in the conversation."
    }
  ]
}
```

### Response structure

| Field | Description |
|---|---|
| `identity` | Biographical facts, roles, and names - as `entities`, `attributes`, and `relations`. |
| `knowledge` | Preferences, opinions, and skills, in the same three-part shape. |
| `context` | Situational and recent-event memory, in the same three-part shape. |
| `instructions` | Standing behavioural directives: `id`, `label`, `description`. |
| `unknowns` | Things extraction could not resolve: `about` and `reason`. |

The three memory categories share one shape - each holds `entities`, `attributes`,
and `relations`.

The endpoint always returns the current, canonical view: superseded attributes are
not included. Each returned attribute carries `supersedes` and `supersededBy`
links, so you can walk backwards from the current value. **Corrections are not part
of this response** - they come back on the write that made them, described in
[Showing what changed after a turn](#showing-what-changed-after-a-turn).

## Showing what changed after a turn

There is no `/state/diff` endpoint, and `/state` takes no `since` parameter - but
you rarely need one, because **every write returns its own delta**.

`POST /facts` responds with an `extraction` object describing exactly what that
write produced:

| Field | What it holds |
| --- | --- |
| `turnId` | The turn the extraction belongs to |
| `entities` | Entities created or matched |
| `attributes` | Attributes asserted |
| `relations` | Relations asserted |
| `instructions` | Standing directives picked up |
| `uncertainties` | Things the extractor could not resolve |
| `corrections` | Values this write superseded, with both sides |

A supersession is reported with the value it replaced, so no second request and no
comparison work are needed. Read `/state` for the full picture at a point in time,
and `GET /entities/{type}/{name}/history/{key}` to follow one attribute's ordered
supersession chain.

## Python SDK

```python
state = await memory.state()

print(state.identity.attributes)   # Current attribute list
print(state.instructions)          # Active behavioural instructions
print(state.unknowns)              # Flagged uncertainties
```

`GET /state` takes no parameters at all - no scope, no time filter, no `since`. It
returns everything the calling key can read. Narrow the result yourself, or read a
single entity with `entities.get(…)`.

To read as another principal, send the **`X-Spectron-On-Behalf-Of`** header. That
is a request header available on every end-user route rather than a `state()`
argument, and the effective authority is the intersection of your grants and the
target's - delegation never widens access.

## JavaScript SDK

```javascript
const state = await memory.state();

console.log(state.identity.attributes);
console.log(state.instructions);
console.log(state.unknowns);
```

## Profile endpoint

The profile endpoint returns a richer, opinionated view of a single user's memory - pre-formatted for prompt injection or display in account or preferences UIs.

### Python SDK

```python
profile = await memory.profile()
```

### JavaScript SDK

```javascript
const profile = await memory.profile();
```

Response shape:

| Field | Description |
|---|---|
| `static` | Stable identity facts: name, role, organisation, contact details. |
| `dynamic` | Frequently-updated attributes: location, status, current project, mood. |
| `preferences` | Extracted preferences and stated likes or dislikes. |
| `instructions` | Active behavioural directives applicable to this user. |

The profile view does not include raw turn references. It is a synthesised snapshot intended for injection into system prompts or display in a "what does the AI know about me?" UI.

## Driving real-time UI updates

Apply the `extraction` payload from each write straight to the UI - new attributes as additions, `corrections` as in-place updates:

```python
result = await memory.remember(
    "I moved to Berlin last month",
    session_id=session_id,
)

for attr in result.extraction.attributes:
    ui.add_fact(attr)

for c in result.extraction.corrections:
    ui.animate_update(c["key"], old=c["oldValue"], new=c["newValue"])
```

```javascript
const result = await memory.remember("I moved to Berlin last month", { sessionId });

for (const attr of result.extraction.attributes) {
    ui.addFact(attr);
}

for (const c of result.extraction.corrections) {
    ui.animateUpdate(c.key, { old: c.oldValue, new: c.newValue });
}
```

## The corrections format in detail

Corrections are produced during reconciliation whenever a newly extracted attribute contradicts an existing one. Each correction record contains both sides of the change and the turn references that establish provenance:

```json
{
  "entityId": "entity:person/alice",
  "key": "location",
  "oldValue": "London",
  "newValue": "Berlin"
}
```

The correction is a summary of the change, not the full temporal record. The
timestamps live on the attribute rows themselves: the superseded row gets a
`valid_until` and a `superseded_by` link, the new row a `valid_from` and a
`supersedes` link. That is what makes historical queries - "what did the agent
know about Alice's location in March 2026?" - return the right answer. Read the
chain with `GET /entities/{type}/{name}/history/{key}`.

Corrections accumulate and are never deleted. The full supersession chain for any attribute is always queryable, which means the audit trail for any fact is complete and irrevocable. See [Temporal validity](/docs/agent-memory/reasoning/temporal-validity.md) and [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md) for the detailed model.

---

Source: https://surrealdb.com/docs/agent-memory/tuning/caching-and-invalidation

# Caching and invalidation

How the semantic response cache works. Managing the memory lifecycle in SurrealDB Agent Memory.

SurrealDB Agent Memory applies several complementary mechanisms to control what stays in memory, for how long, and at what quality. The semantic response cache eliminates redundant LLM calls for similar queries. Importance scoring governs which facts survive decay. Lifecycle sweeps enforce time-based expiry and scheduled degradation.

## Semantic response cache

When a recall query arrives, SurrealDB Agent Memory embeds the query text and checks it against a store of previously answered queries using HNSW approximate nearest-neighbour search (cosine similarity). If a stored query embedding matches the incoming one with a similarity score greater than **0.95**, SurrealDB Agent Memory returns the cached response directly without invoking the LLM.

The cache table is capped at **5000** entries (oldest evicted first) so index size stays bounded between invalidation sweeps.

The cache sits at **tier 2** of the query resolution pipeline, after a direct lookup (tier 1) but before the full hybrid retrieval and response generation path (tier 3). For workloads where many users ask semantically similar questions - "what is my current plan?", "which plan am I on?", "tell me my subscription tier?" - this eliminates the majority of response-generation tokens.

Cache behaviour in decision traces:

```json
{
  "query": "what plan am I on?",
  "tier": 2,
  "cached": true,
  "similarity": 0.97,
  "response": "You are on the Enterprise plan."
}
```

The cache is per-Context. Cache entries are scoped to the same dimensions as the query (user, org, project), so a cached answer for one user is never returned to another.

**`/chat` bypass:** when a session already has prior turns, `/chat` skips tier-2 reuse and always synthesises a fresh reply - the transcript window makes the answer session-dependent even when the semantic query matches a cached entry. Windowed turns are also withheld from seeding the cache. First-turn `/chat` calls and sessionless reads remain cache-eligible.

### Cache invalidation

Cache entries are invalidated when new facts that would affect the response are extracted. If a turn is processed that updates the user's plan, all cached query embeddings for that scope that relate to plan-type facts are evicted. Invalidation is automatic and does not require explicit intervention.

There is no manual flush verb - invalidation is driven entirely by the trace
graph as new facts land. To take the cache out of the path for a Context, set
`response_cache.enabled` to `false` in its config.

## Importance scoring

Every fact in SurrealDB Agent Memory carries an importance score between 0.0 and 1.0. The score governs how long the fact survives and how prominently it is weighted during retrieval. Initial scores are assigned by memory category:

| Category | Initial importance |
|---|---|
| Identity | 1.0 |
| Knowledge | 0.8 |
| Context | 0.5 |

**Identity** facts - user preferences, persistent attributes, long-term profile information - are assigned the maximum score and never decay (see below). **Knowledge** facts - extracted from documents or structured ingestion - start high because they represent deliberate, curated information. **Context** facts - extracted from ephemeral conversation turns - start lower, reflecting that most conversational detail is transient.

### Reinforcement on recall

Each time a fact is retrieved and returned to a caller, its importance score is multiplied by 1.1, capped at 1.0. This means frequently recalled facts reinforce themselves, while facts that are never retrieved gradually become less significant. The reinforcement reflects observed utility: if a fact keeps being surfaced, it is clearly relevant and should be retained.

## Importance decay

Importance scores decay on a per-category schedule. Decay runs as a background sweep at regular intervals (in standard deployments, nightly).

| Category | Decay factor per day | Notes |
|---|---|---|
| Context | × 0.95 | Aggressive - most conversational facts become negligible within a few weeks |
| Knowledge | × 0.995 | Slow - curated knowledge remains relevant for much longer |
| Identity | No decay | Identity facts persist indefinitely unless explicitly deleted |

A context-category fact starting at importance 0.5 decays to approximately 0.07 after 30 days, and to effectively zero after 60 days. It will be swept up by the auto-expiry TTL long before it reaches those values.

## Auto-expiry TTL

SurrealDB Agent Memory applies a default time-to-live (TTL) of **7 days** to context-category facts. After the TTL elapses, the fact is eligible for removal during the next lifecycle sweep. This prevents the memory layer from accumulating stale conversational detail indefinitely.

The TTL applies to the context category only. Knowledge and identity facts are not subject to the default TTL unless a retention policy overrides this.

## Retention policies

There are no declarative per-scope retention rules. Expiry is driven by the
**lifecycle sweeps** instead - `lifecycle.expire()` closes facts whose validity
has lapsed, and `lifecycle.decay()` ages importance scores. Run them on your own
schedule, or leave them to the background scheduler.

Trace rows are the one thing with a configurable retention window, set per
Context rather than per scope:

| Field | What it bounds |
| --- | --- |
| `trace_retention.decision_trace_days` | Reconciliation decision traces |
| `trace_retention.retrieval_trace_days` | Retrieval traces |
| `trace_retention.response_trace_days` | `/chat` and `/reflect` response traces |

Retention policies are enforced during the reconciliation sweep, not at write time. A fact written when a policy allows 30-day retention will be expired 30 days after creation, regardless of whether the policy is later changed.

## Lifecycle sweeps

Two background sweeps manage memory lifecycle:

### Expiry sweep

The expiry sweep removes facts that have exceeded their TTL. It runs as a background job in standard deployments. You can trigger it explicitly:

```python
await memory.lifecycle.expire()
```

```javascript
await memory.lifecycle.expire();
```

### Decay sweep

The decay sweep applies the per-category importance multipliers to all facts in the Context. It runs nightly in standard deployments. To trigger manually:

```python
await memory.lifecycle.decay()
```

```javascript
await memory.lifecycle.decay();
```

Manual triggering is useful during testing, when you want to fast-forward the decay state of a Context, or when managing self-hosted deployments where background job scheduling is under your control.

## Querying lifecycle state

To inspect the importance score and TTL of a specific fact:

```python
entity = await memory.entities.get("person", "alice")
for attr in entity.attributes:
    print(attr.key, attr.importance, attr.valid_until)
```

There is no endpoint that lists facts by upcoming expiry. Read the entities you
care about and filter on each attribute's `valid_until` yourself, or inspect the
whole Context state with `memory.state()`.

---

Source: https://surrealdb.com/docs/agent-memory/tuning/models-per-stage

# Models per stage

Configure which LLM runs at each processing stage. For every stage in SurrealDB Agent Memory.

SurrealDB Agent Memory’s pipeline uses separate LLM stages with different latency and quality needs. Assign a model per stage rather than one model for everything. Embeddings are fixed deployment-wide.

For provider keys and Gemini defaults, see [Configuration](/docs/agent-memory/reference/configuration.md#default-provider-api-keys).

## The stages

### Extraction

Runs on conversational turns and document jobs. Classifies content and extracts entities, attributes, relations, instructions, and uncertainties. Often on the critical path for `/facts?infer=full`, so latency matters.

### Reconciliation

Optional LLM assist when structural entity merge is inconclusive. Not every write invokes it.

### Synthesis

Powers `/chat` and `/reflect` answer generation from retrieved context. Quality usually matters more than on extraction.

### Elaboration and consolidation

Background “dreaming” work on the worker tier: elaboration and consolidation passes. Not on the user-facing critical path.

### Embedding

Converts text (and multimodal content) into dense vectors for HNSW retrieval and the semantic cache. Fixed to **`gemini-embedding-2`** at **3072** dimensions. Not freely assignable per Context - Context config may only pin that same model id. Vector reads filter by **`embedding_model_id`**. After changing the deployment embedding model, run a [reindex](/docs/agent-memory/reference/management-api.md#force-reindex).

## Configuring models per stage

Model assignment is per-Context. Each LLM stage takes `{provider, model}`:

```http
PATCH /api/v1/contexts/{context_id}
Content-Type: application/json

{
  "config": {
    "models": {
      "extraction": { "provider": "google", "model": "gemini-2.5-flash" },
      "synthesis": { "provider": "anthropic", "model": "claude-sonnet-4" },
      "elaboration_consolidation": { "provider": "google", "model": "gemini-2.5-flash" },
      "embedding": "gemini-embedding-2"
    }
  }
}
```

Omit any stage to leave the current assignment (or deployment default) unchanged. Missing provider keys for a selected provider fail at configuration time rather than silently re-routing.

## Supported providers

| Provider | Role |
| --- | --- |
| Google (Gemini) | LLM stages; **required** for embeddings |
| OpenAI | LLM stages |
| Anthropic | LLM stages |

Provider keys may be set deployment-wide (`SPECTRON_PROVIDER_*`) or per Context. Keys are write-only on the API; reads return which providers are configured, not the secrets.

## Latency versus quality

| Stage | Typical path | Prefer |
| --- | --- | --- |
| `extraction` | Sync on write / ingest | Fast, inexpensive model |
| `reconciliation` | Occasional on write | Fast or mid-tier |
| `synthesis` | `/chat`, `/reflect` | More capable when answer quality matters |
| `elaboration_consolidation` | Worker background | Fast enough for batch work |
| `embedding` | Ingest + query | Fixed: `gemini-embedding-2` (3072-dim) |

A common pattern is a cheap model for extraction and a stronger model for synthesis only.

## Changing the embedding model

Embeddings are Gemini-only at 3072 dimensions. Switching the deployment embedding model requires a reindex so stored vectors and query vectors share one space. See [Management API - force reindex](/docs/agent-memory/reference/management-api.md#force-reindex).

---

Source: https://surrealdb.com/docs/agent-memory/tuning/ontology-grounding

# Extraction vocabulary

How entity names and attribute keys stay consistent. Relation labels are grounded across extractions too.

Free-form extraction invents vocabulary. Given a conversation turn, the model infers attribute names and relation labels from the language in front of it, so the same concept surfaces under different names depending on phrasing. A customer's subscription plan becomes `plan` in one turn, `subscription_tier` in the next, then `pricing_tier`. Nothing is wrong with any single extraction, but the graph fragments: a query for `plan` misses two thirds of the data.

SurrealDB Agent Memory addresses this by **feeding the vocabulary already in the graph back into the extraction prompt**. Every extraction run is told which names, keys, labels, and verbs the Context has used before, and is instructed to reuse them when the meaning matches. Vocabulary converges as the graph grows, without configuration.

## What extraction is shown

Four vocabulary lists are gathered before each run and rendered into the prompt:

| List | How many | Selected by |
| --- | --- | --- |
| Entity names and types | up to 500 | no ranking |
| Relation labels | up to 100 | most-used first |
| Attribute keys | up to 100 | most-used first, live rows only |
| Action verbs | up to 100 | most-used first |

Relation labels, attribute keys, and action verbs are counted within the write scope, ranked by frequency, and truncated to the most common. Attribute keys are drawn only from rows that are still current - superseded and expired values do not keep a dead key alive in the prompt.

The instruction attached to each list is the same shape. For attribute keys:

> Attribute keys already used in this memory. Reuse one as an attribute `key` when it means the same thing; only mint a new key when none fits. A key invented for a single fact can never be looked up again.

Reuse is a strong preference, not a constraint. Extraction can still mint a new key when nothing fits, which is what keeps a novel concept from being forced into a nearby-but-wrong slot.

## Entity types are a fixed set

Entity types are not part of this vocabulary and cannot be extended. They are a closed list - `person`, `organisation`, `project`, `location`, `topic`, `product`, `policy`, `concept`, `event`, `agent`, `service`, `other` - and a supplied type outside it normalises to `other` rather than creating a new type.

This matters more than it looks: the type is half an entity's identity, so an invented type silently produces a second entity. See [Entity matching](/docs/agent-memory/reasoning/reconciliation-and-supersession.md#entity-matching).

## The cold start is the weak point

The mechanism is self-reinforcing, which cuts both ways. An empty Context has no vocabulary to offer, so the earliest extractions set the terms that everything later converges on. Whatever the first few documents happen to call something becomes the house style.

Two consequences worth planning around:

- **Seed the names you care about before a bulk import.** Entities that already exist are listed in the prompt and get reused. Creating them up front is the most reliable lever available for keeping references together.
- **Ingest in a deliberate order where you can.** A representative document first establishes better vocabulary than an unusual one.

> [!NOTE]
> The entity list is the one that is **not** frequency-ranked, and it is capped at 500. On a Context with more entities than that, which names reach the prompt is arbitrary - so on large graphs, seeding matters more, not less, and important entities can drop out of the prompt without any signal.

## What is not configurable

There is no API for supplying your own controlled vocabulary. Allowed entity types, attribute keys, and relation labels cannot be pinned per Context; the vocabulary is emergent, derived from what the graph already holds.

If you need harder guarantees than convergence provides, normalise before the write: map your variants to canonical names in your own pipeline and send those, or write structured triples with `infer: "triples"` to bypass model-chosen vocabulary entirely. For repairing drift after the fact, `POST /fsck` with the `duplicates` check reports near-identical entities for you to act on.

## Related reading

- [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md) - exact-key entity matching, and why nicknames fragment
- [Storing memories](/docs/agent-memory/ingest/experiential/remember.md) - `infer` modes, including caller-supplied triples
- [Extraction pipeline](/docs/agent-memory/reasoning/extraction-pipeline.md) - where extraction sits in ingest

---

Source: https://surrealdb.com/docs/agent-memory/welcome/accuracy-promise

# The accuracy promise

Defensible correctness in SurrealDB Agent Memory. Provenance, reconciliation, tri-temporal history, traces, and inspectable substrate state.

SurrealDB Agent Memory is built around a single design question:

> Can someone watch the substrate in real time and **verify** that the agent is operating on the right information - not infer it from output quality alone?

That requires structured facts, **first-class provenance**, **non-destructive** belief history, **explicit uncertainty**, and **graph-resident traces** that make retrieval and reconciliation auditable. It is the same standard you apply informally when someone says they **used to** have a cat: you keep the old fact in mind, date it, and do not treat it as current - SurrealDB Agent Memory makes that distinction queryable.

## Structured facts, not opaque blobs

Turns and document passages are reconciled into **entities**, **attributes**, and **relations** stored in SurrealDB - not only as embedded chunks. Discrete records are what make the rest of the guarantees possible: you can query “what is the current role?” deterministically, enumerate everything known about an entity, and detect duplicates structurally.

## Provenance is a field, not a log line

Fact-bearing records carry a **`source`** object ([Provenance and traceability](/docs/agent-memory/mental-model/provenance-and-traceability.md)):

- **`source.kind`** - `turn`, `document`, `reflect`, `elaboration`, `consolidation`, …
- **`source.ref`** - originating turn, document, or trace identifier
- **`source.span`** - character positions for quoting the originating message or passage
- **`source.trust`** - default weight for how authoritative that source stream is (curated document versus casual turn, and so on)
- **`source.derived_from`** - lineage for reflections, elaborations, consolidations, collective promotion

Calibration stores **`attribute.confidence`** (reconciler posterior) **separately** from `source.trust`. A low-confidence extraction cannot silently supersede a higher-confidence belief; it becomes **`uncertainty`** instead. See [Surface, models, and security](/docs/agent-memory/architecture/surface-security-and-models.md) for the model-hook story.

## Model-derived facts are first-class - and visibly probabilistic

Facts minted by reflection, elaboration, or consolidation are stored with their own **`source.kind`**, a **`source.derived_from`** link to the inputs they were synthesised from, and a **lower default trust** than a human turn or an authoritative document. A probabilistic upstream remains visible at every hop in the chain - it is not laundered into ground truth because it became input to a later stage.

## Walking “why did this change?”

Supersession chains answer **what** changed between two points in time. **`decision_trace`** nodes answer **why** the reconciler moved: what it considered, what it superseded, and which source triggered the update. Compare two traces to see whether a revision came from **new inputs**, **different retrieval**, or **model variance** with the same inputs recorded in the trace metadata.

## Three ways memory stops being “current”

**Supersession** (“this replaced that”), **decay with reinforcement** (“this faded from relevance”), and **forget** (“remove from the agent’s working set”) are intentionally separate. See [Supersession, decay, and forget](/docs/agent-memory/mental-model/memory-lifecycle.md).

## One reconciler, explicit uncertainty

Document facts and turn facts **meet in the same graph** and pass through the **same** supersession-and-uncertainty logic. Cross-provenance contradictions become **`uncertainty`** records instead of silent overwrites - **not last-write-wins**. Same-stream updates supersede with `valid_until` on the prior record. When confidence is below the calibration floor, SurrealDB Agent Memory refuses to do away with a stronger existing belief and surfaces uncertainty instead.

Concurrent writers hit the same reconciler in **one ACID transaction** per write, so conflicts are recorded where the facts live - not reconciled eventually by a background job that leaves stale reads in between.

## Tri-temporal, not “delete and forget”

SurrealDB Agent Memory tracks **three clocks** - system (MVCC), known (`as_of` over supersession), and valid (real-world `valid_from` / `valid_until`). Aging and supersession replace naive deletion; **`forget`** exists when operators or users require explicit removal. See [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md).

## Traces make behaviour inspectable

Every ranked read can emit a **`retrieval_trace`**; every reconciliation emits a **`decision_trace`**; `/chat` and `/reflect` emit **`response_trace`**. They are **nodes** with edges to the entities and attributes they touched - so you can walk “this answer used these retrievals → which considered these records → which decisions superseded what.” See [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md).

## Authoritative versus experiential streams

Curated knowledge and conversational knowledge are distinguished by **`source.kind`** and trust policy, **not** by hiding one half in a separate database you cannot join transactionally. When user assertions disagree with curated documents, reconciliation preserves curated truth and surfaces the clash ([Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md)).

## Observable today

Operators can use HTTP and CLI surfaces (`spectron entities …`, `spectron sessions …`, `spectron recall …`, `spectron inspect trace:…`) plus direct SurrealDB access on self-hosted stacks to read live state and historical belief - the same substrate the ranker uses. A fuller inspectability list is in [Surface, models, and security](/docs/agent-memory/architecture/surface-security-and-models.md).

Together, these properties mean “is the agent correct?” has a defensible answer: you can point to records, spans, traces, and time axes - not only to a model transcript.

---

Source: https://surrealdb.com/docs/agent-memory/welcome/how-it-works

# How it works in five minutes

From a message to structured substrate state. Extraction, reconciliation, traces, and tiered reads.

This page sketches the lifecycle from a user message to durable, queryable state in SurrealDB, and how a later question is answered cheaply first, with expensive paths only when needed.

Think of it as teaching an agent to remember the way people do: capture what was said (**episodic**), pull out who someone is and what they know (**identity** and **knowledge**), note what matters right now (**context**), link "cat" to manuals and prior turns (**association** in one graph), and when someone says "I *used to* have a cat", **close** the old fact instead of treating both versions as current.

For pillars, categories, hybrid retrieval, trace feedback, and tri-temporal semantics, see the [overview hub](/docs/agent-memory.md) and [Principles and goals](/docs/agent-memory/architecture/principles-and-goals.md).

## End-to-end write path

```text
User message (or uploaded file)
        │
        ▼
   Session / ingest
   (turn record or async document job)
        │
        ▼
┌───────────────────────────────┐
│  Extraction (pattern + LLM)   │  Typed entities, attributes, relations
└───────────────┬───────────────┘
                ▼
┌───────────────────────────────┐
│  Reconciliation (one function)│  Dedup, scope, supersession, uncertainty
└───────────────┬───────────────┘
                ▼
┌───────────────────────────────┐
│  SurrealDB substrate          │  Graph + vectors + docs + geometry
│  + decision_trace nodes       │  Linked to considered / created records
└───────────────────────────────┘
```

[Sessions and turns](/docs/agent-memory/mental-model/sessions-and-turns.md) remain the conversational unit of work. **Documents** enter the multi-modal [Knowledge](/docs/agent-memory/memory-and-knowledge.md) ingest pipeline instead of the turn path; extracted facts reconcile through the same pipeline.

## Reconciliation in one sentence

New extractions never “win” by accident: they are merged, superseded, or rejected into **`uncertainty`** using the same rules whether the source was a turn or a document (`source.kind` and trust decide how they combine).

## Read path: tiered resolution

Reads climb a four-tier ladder after a small query-understanding step. The cheapest tier runs first (fewest tokens, lowest latency). The next tier runs only when the current one cannot answer confidently:

1. **Structured lookup** when the question maps to a key in the graph.
2. **Semantic response reuse** when a prior `response_trace` still cites **current** facts (`reused_from` links the new trace).
3. **Hybrid retrieval + synthesis** - BM25, vectors, graph hops, keyword bridges, trace-derived features fused into one ranking, then LLM synthesis.
4. **Broader sweep** only when tier 3 is thin or low-confidence.

Each tier emits **`retrieval_trace`** metadata so you can see **which tier answered** and why. Full detail: [Coherence, retrieval, and cost tiers](/docs/agent-memory/architecture/coherence-retrieval-and-tiers.md).

## What is stored on each fact

Beyond value and scope, expect **`source.*`**, **`valid_from` / `valid_until`**, **`confidence`**, and edges into **`decision_trace`** records. The **invariants** are: provenance is mandatory, supersession is non-destructive, and uncertainty is representable.

## Integration shapes

You can let SurrealDB Agent Memory drive `/chat`, or you can own the loop and call ingest + `/query` yourself via the [REST API](/docs/agent-memory/reference/rest-api.md) and [Integrations](/docs/agent-memory/integrations.md).

## Next steps

- [Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md)
- [Extraction pipeline](/docs/agent-memory/reasoning/extraction-pipeline.md)
- [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md)
- [Traces and memory evolution](/docs/agent-memory/architecture/traces-and-evolution.md)

---

Source: https://surrealdb.com/docs/agent-memory/welcome/what-is-surrealdb-agent-memory

# What is SurrealDB Agent Memory?

A memory and knowledge layer for AI agents. One SurrealDB substrate, provenance-first, trace-aware, and tri-temporal.

SurrealDB Agent Memory is a memory and knowledge layer for AI agents. It runs as an application tier in front of SurrealDB. Documents, turns, entities, attributes, relations, embeddings, and traces all live in one multi-model database, and each write is an ACID transaction.

That single-substrate choice avoids stitching a **relational database**, a **vector index**, and a **graph store** by hand - with dual writes, sync jobs, and drift when those stores disagree.
Documents, graph edges, and index entries share that transactional core. A write that touches a record, its relations, and its embeddings commits atomically under snapshot isolation.

## Designed like human memory

You already know how memory feels. Hear the word **cat** and you do not store one flat sentence - you bring up a web of associations: a pet on the sofa, a lion on a documentary, a sports nickname, even a distant link like “Richard the Lionheart”. SurrealDB Agent Memory aims for that shape in software: one **entity** (`Cat` or `Animal/cat`) linked to images, facts, documents, and things people said, so recall can follow meaning and connection, not only text similarity.

Human memory also cares about **time and who it is about**. The same word “cat” in four short sentences is four different kinds of memory:

| What someone says | How you read it | What SurrealDB Agent Memory models |
| --- | --- | --- |
| “I have a cat.” | A **present** fact about **this** person | Scoped **identity** / **knowledge**, valid **now** |
| “I saw a cat last night.” | A **past episode** tied to the speaker | **Episodic** turn plus extracted fact with **valid time** in the past |
| “House cats weigh about 4 kg.” | A **general** fact, not about the speaker | Broader-scope **knowledge** (or **authoritative** if it came from a manual) |
| “I used to have a cat.” | Something **true before**, not now | **Supersession** - old belief kept, end dated with `valid_until` |

You do not need a special mental model for “vector database” versus “graph database”. SurrealDB Agent Memory is the layer that turns chat and documents into **structured, time-aware beliefs** agents can trust - then retrieves them with the same blend of association, wording, and recency you would expect from a good colleague who was actually listening.

## What problem it solves

An agent that only keeps the current window forgets everything when the session ends. The usual stand-ins are:

- Truncating an ever-growing chat transcript, or
- Embedding turns into a vector index with no structured reconciliation, no contradiction handling, and no audit trail you can query.

SurrealDB Agent Memory extracts structured entities, attributes, and relations, attaches provenance (`source.kind`, spans, trust, derivation), runs everything through one reconciler, and stores traces of retrieval and decisions as graph nodes that feed back into ranking and consolidation.

## What SurrealDB Agent Memory is *not*

SurrealDB Agent Memory is **not** a vector database, a chat-log archive, a hand-authored knowledge graph, a context-window manager, a full agent runtime, or an observability SaaS. See [Principles and goals](/docs/agent-memory/architecture/principles-and-goals.md).

## Eight pillars, two streams, one graph

The full operational model is the **[eight pillars](/docs/agent-memory/architecture/eight-pillars-and-categories.md)** (authoritative and experiential knowledge, reflection, elaboration, consolidation, calibration, collective memory, and the trace layer). The first two pillars are often described as two **streams** of knowledge because they answer different questions:

- **Authoritative** - manuals, policies, product data, repos - via **document** ingest (`source.kind = "document"`).
- **Experiential** - what people and agents said - via turns and related paths (`source.kind = "turn"`, `reflect`, `elaboration`, `consolidation`, …).

Both streams land in the **same** `entity` / `relation` graph. **Authority** is expressed by **reconciliation and trust**, not by copying records between silos. When chat disagrees with a curated document, the reconciler records **uncertainty** and supersession metadata - it does not silently overwrite curated truth.

Read more in [Unified substrate and authority](/docs/agent-memory/mental-model/two-layer-architecture.md).

## Contexts

A **Context** is the hard **isolation** unit: its own SurrealDB namespace/database, keys, and quotas. End-user HTTP paths are rooted at `/api/v1/{context_id}/…`.

## Where next?

- [Eight pillars and six categories](/docs/agent-memory/architecture/eight-pillars-and-categories.md) - the conceptual spine.
- [Why agentic memory?](/docs/agent-memory/welcome/why-agentic-memory.md) - why embeddings-only and “big context” shortcuts break down.
- [Cookbooks](/docs/agent-memory/cookbooks.md) - patterns for assistants, support bots, coding agents, and shared memory.

---

Source: https://surrealdb.com/docs/agent-memory/welcome/why-agentic-memory

# Why agentic memory?

Why conversational agents need structured memory. Plain retrieval is not enough on its own.

An agent without durable memory cannot build on prior conversations. Every new session starts cold: the agent cannot personalise responses, cannot build on prior decisions, and cannot recognise that it has been asked the same question before. This page explains why memory matters, and why the most common shortcuts are insufficient.

## The bar: how people remember

People rarely "search their transcript". Instead, we generally use the following to learn and remember:

* Association (seeing the word "cat" pulls up pets, lions, teams, stories)
* Beliefs are given  timestamps and scope ("I have one now", "I saw one yesterday", "I used to years ago, but no longer").
* General facts are derived from experience ("they weigh about four kilos in general")

SurrealDB Agent Memory is built to that shape: a graph of entities and relations for association, six experiential categories for what kind of thing was learnt, and tri-temporal fields so present, past, and general facts do not collapse into one undated chunk. The implementation is precise - provenance, scopes, reconcilers - but the aim is simple: memory an engineer can explain, and an agent can query.

## What happens without memory

### Context loss across sessions

Each conversation turn exists in isolation. The moment a session ends, everything learnt is discarded. If someone told the agent their preferred time zone last Tuesday, the agent asks again on Wednesday. If a support agent resolved a billing issue in March, the agent in April has no record of it.

Within a session the context window compounds the problem. As conversations grow, earlier turns are truncated or summarised to fit the model's token limit, so even intra-session memory fades.

### No personalisation

Personalisation requires knowing things about the person: their role, their preferences, their history with the product, their current project. Without persistent memory, every response must be generic. The agent cannot adapt tone, skip explanations the user has already heard, or surface information relevant to their specific situation.

### No learning from past interactions

When an agent makes a mistake - misunderstands a user's intent, applies the wrong policy, gives outdated information - there is no durable record of the correction, so the same failure can recur in the next session.

For agentic workflows that run autonomously over long periods, this is especially costly. An agent executing a multi-step research task cannot resume where it left off if the session is interrupted. An agent coordinating with other agents cannot rely on a shared understanding of what has already been done.

## Why a vector store is not enough

A common response is to embed past interactions and retrieve similar chunks at query time. While better than nothing, it has structural limitations that prevent it from serving as a genuine memory layer.

### No structure, no verifiability

With vector stores alone, memory is text chunks. Retrieval returns the chunks most similar to the query. There is no entity model - no concept of "user", "project", or "preference". There is no way to ask "what is Christian's current role?" and get a direct answer; instead, you get chunks that mention Christian and hope the right one surfaces.

There is also no way to inspect what the system "knows" in any meaningful sense. As the store is an opaque cloud of embeddings, checking correctness means re-running queries and reading the outputs.

### No provenance

Which conversation produced a given chunk? When was it captured? Has the underlying fact since been corrected? A vector store has no answers. Retrieved context may be outdated, contradictory, or sourced from an unreliable turn, and there is no field that says so.

### No correction tracking

When someone corrects the agent ("actually, I switched roles in January") the store cannot reconcile this with prior data. Both the old and new statements exist as equal-weight chunks. Future retrieval may return either one, or both, and the model has to guess which is current. There is no supersession, no temporal ordering of facts, and no way to mark old information as invalid.

### No temporal validity

Facts have no reliable lifespan in storage. A current project is not the same as the project six months ago. A pricing policy changes. An employee changes teams. Without `valid_from` and `valid_until` on stored facts, stale data sits next to current data, and retrieval cannot tell them apart.

### Retrieval is a guess

Semantic similarity is not the same as relevance. A chunk retrieved because it is textually similar to a query may not be factually relevant. High-similarity matches can be coincidental; low-similarity matches may be the ones you need. Ranking by cosine distance alone is a weak strategy for factual queries.

## What structured memory provides

SurrealDB Agent Memory addresses each of these gaps:

| Problem | SurrealDB Agent Memory's approach |
|---------|---------------------|
| No structure | Extracted entities, attributes, and relations stored as a **queryable graph** in SurrealDB |
| No provenance | Every record carries a **`source`** object (kind, ref, spans, trust, derivation); see [Provenance and traceability](/docs/agent-memory/mental-model/provenance-and-traceability.md) |
| No correction tracking | Supersession chains plus explicit **`uncertainty`** for cross-provenance clashes |
| No temporal validity | [Tri-temporal](/docs/agent-memory/architecture/tri-temporal-model.md) model (system, known, and valid time) |
| No verifiability | **Traces** (`retrieval_trace`, `decision_trace`, `response_trace`) as substrate nodes, not disposable logs |
| Unreliable retrieval | [Hybrid structural retrieval plus tiered resolution](/docs/agent-memory/architecture/coherence-retrieval-and-tiers.md) |

The result is a memory layer **you can trust**: correctness can be demonstrated, not just assumed. See [The accuracy promise](/docs/agent-memory/welcome/accuracy-promise.md) for how that works in practice.

## Agent memory should not be a cache

Many "memory" products are really caches: store an output, retrieve something similar later. A cache can tell you what was returned last time, but cannot reliably tell you what the agent believed at the time, why that belief changed, or which source contradicted which.

SurrealDB Agent Memory is built as state management first and retrieval second. Embedding-and-ranking finds candidates; supersession, provenance, and traces keep a current view of the world as it changes.

## Long sessions and context limits

Even within a single session, context windows force **truncation or compaction**. If durable memory only lives in the transcript, anything not carried forward in the summary is gone when the session ends.

SurrealDB Agent Memory **extracts and reconciles** important facts into structured memory as turns arrive, runs **consolidation and elaboration between interactions**, and keeps the **full episodic record** citeable via provenance - so you are not betting everything on one compaction at the right moment. See [Supersession, decay, and forget](/docs/agent-memory/mental-model/memory-lifecycle.md).

---

Source: https://surrealdb.com/docs

# Getting started

The official documentation for SurrealDB, a multi-model database. Built for modern applications.

SurrealDB stores relational, document, graph, time-series, vector and full-text data in one engine, queried through SurrealQL and reachable from ten official SDKs. It runs embedded in your application, as a single node, or as a distributed cluster.

<AgentBanner />

## Where do you want to start?

- [I am new here](/docs/running/overview.md) - Install SurrealDB, run it locally or in the cloud, and write your first query.

- [I want to build something](/docs/learn.md) - Connect an SDK, model your data, secure it, and put it into production.

- [I need the details](/docs/reference.md) - Every SurrealQL statement, protocol message and SDK method.

## Your first query

SurrealQL is SQL-shaped, so most of it reads as you would expect. What it adds is the ability to relate records directly and traverse those relations in the same statement, without a join table or a second database. Edit and run this:

```surql
-- Records are addressed by table and id
CREATE person:alice SET name = 'Alice';
CREATE company:acme SET name = 'Acme';
-- RELATE creates a graph edge, which can carry its own fields
RELATE person:alice->works_at->company:acme SET since = d'2024-01-15';
-- Traverse the edge from the record, in the projection
SELECT name, ->works_at->company.name AS employers FROM person;
```

The [querying guide](/docs/learn/querying.md) covers the language properly, and [data models](/docs/learn/data-models.md) covers what else the engine stores.

## Connect from your language

Each SDK has a quickstart that gets you connected, and a reference covering every method.

- [Rust](/docs/languages/rust.md)

- [JavaScript](/docs/languages/javascript.md)

- [Python](/docs/languages/python.md)

- [Go](/docs/languages/golang.md)

- [.NET](/docs/languages/dotnet.md)

- [Java](/docs/languages/java.md)

- [Kotlin](/docs/languages/kotlin.md)

- [PHP](/docs/languages/php.md)

- [Swift](/docs/languages/swift.md)

- [Mojo](/docs/languages/mojo.md)

[Community SDKs](/docs/languages/community.md) cover further languages, and the [Expo](/docs/frameworks/expo.md) and [React Native](/docs/frameworks/react-native.md) guides cover mobile.

## Run it somewhere

- [SurrealDB Cloud](/docs/manage/instances.md) - A managed instance with scaling, backups and monitoring handled for you.

- [Self-hosted](/docs/manage/self-hosted.md) - Run and operate SurrealDB on your own infrastructure.

- [Docker](/docs/running/docker.md) - A container for local development and consistent environments.

- [Embedded](/docs/build/embedding.md) - The engine in-process, natively or through WebAssembly.

## The rest of the documentation

- [Learn](/docs/learn.md) - Querying, schema, data models and security.

- [Build](/docs/build.md) - Embedding, migrating, integrations and AI agents.

- [Manage](/docs/manage.md) - Instances, organisations, observability and self-hosting.

- [Explore](/docs/explore.md) - SurrealDB Studio, tutorials, demos and labs.

- [Reference](/docs/reference.md) - SurrealQL, protocols, CLI tools and SDK methods.

- [Agent Memory](/docs/agent-memory.md) - The memory and knowledge layer for AI agents.

---

Source: https://surrealdb.com/docs/agents

# Agent setup

Connect your agents to SurrealDB with Agent Skills and MCP.

SurrealDB publishes Agent Skills and MCP servers, so the agent you already code with can write correct SurrealQL, run it against your databases, and manage the instances they live on.

Copy the setup prompt and paste it into your agent, or pick your agent below and follow the steps by hand. For a deeper reference on every way SurrealDB fits into AI tooling, see [AI agents](/docs/build/ai-agents.md).

<AgentPrompt />

## Pick your agent

Select an agent for its setup steps. Every agent listed supports both Skills and MCP.

<AgentPicker />

Using something else? Any MCP client can reach the hosted server. Add `https://mcp.surrealdb.com` as a remote server in whatever form the client accepts, then install the skills with `npx skills add surrealdb/agent-skills`.

## What your agent gets

Setup connects two things: packaged knowledge of how SurrealDB behaves, and tools your agent can call against your databases.

- [Agent Skills](/docs/build/ai-agents/agent-skills.md) - Three official skills covering SurrealQL, vector search, and the Python SDK, so generated queries match how SurrealDB actually behaves.

- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - One hosted URL connects your agent to SurrealDB Cloud: deploy and resize instances, query them, read metrics and logs, and check what it all costs.

## Next steps

- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - the hosted server in full, including every tool it publishes
- [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) - what each skill covers, and how to find community skills
- [Example usages](/docs/build/ai-agents/mcp/examples.md) - prompts that show what an assistant can do once it is set up

---

Source: https://surrealdb.com/docs/agents/claude-code

# Claude Code

Hosted MCP server and Agent Skills setup for Claude Code. Covers the .mcp.json entry, signing in, and checking it worked.

[Claude Code](https://claude.com/claude-code) is Anthropic's terminal coding agent. With the SurrealDB MCP server connected it can deploy and manage your Cloud instances and query the data inside them, and with the skills installed the SurrealQL it writes matches how the database actually behaves.

## Add the MCP server

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

Add `--scope project` to write the entry to `.mcp.json` in the repository instead of your global configuration. The file holds no credentials, so it is safe to commit; everyone who opens the project signs in as themselves.

## Sign in

Run `/mcp` inside Claude Code, choose **surrealdb**, and approve the connection in the browser window that opens. Until you sign in, only the sign-in tool works.

If you are running somewhere without a browser, create a [personal access token](/docs/build/ai-agents/mcp.md#signing-in) and pass it as a header instead:

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

## Install the Agent Skills

Run this in your project root:

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

Claude Code reads the installed skills automatically and picks one up when a task calls for it.

## Check it worked

```bash
claude mcp list
```

**surrealdb** should be listed as connected.

## 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.

## Remove it

```bash
claude mcp remove surrealdb
```

## Next steps

- [MCP in Claude](/docs/build/ai-agents/mcp/claude.md) - Claude Desktop and the Claude app, and troubleshooting
- [Example usages](/docs/build/ai-agents/mcp/examples.md) - more prompts to try

---

Source: https://surrealdb.com/docs/agents/codex

# Codex

Hosted MCP server and Agent Skills setup for the Codex CLI. Covers the config.toml entry, signing in, and checking it worked.

[Codex](https://developers.openai.com/codex/) is OpenAI's coding agent, available as a terminal CLI and a desktop app. Connecting the SurrealDB MCP server lets it work against your Cloud instances while it edits code.

## Add the MCP server

```bash
codex mcp add surrealdb --url https://mcp.surrealdb.com
```

Codex also reads MCP servers from `~/.codex/config.toml`, or `.codex/config.toml` in a trusted project:

```toml
[mcp_servers.surrealdb]
url = "https://mcp.surrealdb.com"
```

## Sign in

Start a Codex session and approve the SurrealDB connection when it prompts you. For an unattended run, create a [personal access token](/docs/build/ai-agents/mcp.md#signing-in) and point Codex at it through an environment variable rather than writing the value into the file:

```toml
[mcp_servers.surrealdb]
url = "https://mcp.surrealdb.com"
bearer_token_env_var = "SURREALDB_TOKEN"
```

## Install the Agent Skills

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

## Check it worked

```bash
codex mcp list
```

**surrealdb** should appear in the list. Ask Codex what SurrealDB tools it has available.

## 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.

## Next steps

- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - every tool the server publishes
- [Agent Skills](/docs/build/ai-agents/agent-skills.md) - what each skill covers

---

Source: https://surrealdb.com/docs/agents/cursor

# Cursor

Hosted MCP server and Agent Skills setup for Cursor. Covers the mcp.json entry, signing in, and checking it worked.

[Cursor](https://cursor.com) is an AI-first editor built on VS Code. With the SurrealDB MCP server connected, its agent can check what is deployed, query a database, and read logs without leaving the editor.

## Add the MCP server

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

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

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.

## 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.

> [!WARNING]
> If you use a [personal access token](/docs/build/ai-agents/mcp.md#signing-in) instead, keep it in the global `~/.cursor/mcp.json`. A token in a project-level file will be committed with the repository.

## Install the Agent Skills

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

## Check it worked

Ask the chat panel:

> Which SurrealDB Cloud organisations can you see?

Cursor should answer with your organisations. Reload the window if the tools do not appear after editing `mcp.json`.

## Remove it

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

## Next steps

- [MCP in Cursor](/docs/build/ai-agents/mcp/cursor.md) - the full guide, including troubleshooting
- [Example usages](/docs/build/ai-agents/mcp/examples.md) - more prompts to try

---

Source: https://surrealdb.com/docs/agents/github-copilot

# GitHub Copilot

Hosted MCP server and Agent Skills setup for GitHub Copilot. Copilot reads the same .vscode/mcp.json file that VS Code does.

[GitHub Copilot](https://github.com/features/copilot) reaches MCP servers through agent mode in VS Code. Once connected, Copilot Chat can query your SurrealDB Cloud instances and manage them alongside the code it is writing.

## Prerequisites

- VS Code 1.99 or later
- The GitHub Copilot extension with agent mode enabled

To enable agent mode, open VS Code settings, search for `github.copilot.chat.agent.enabled`, and set it to `true`.

## Add the MCP server

Copilot reads the same file VS Code does: `.vscode/mcp.json` for the workspace, or the user-level `mcp.json` for every project.

```json
{
  "servers": {
    "surrealdb": {
      "type": "http",
      "url": "https://mcp.surrealdb.com"
    }
  }
}
```

A workspace file makes the server available to everyone who opens the project. It holds no credentials, so it is safe to commit.

## Sign in

Reload the window, start the server from the MCP view, and approve the connection in the browser.

## Install the Agent Skills

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

## Check it worked

Open Copilot Chat in agent mode and ask which SurrealDB tools it can use. The tools picker in the chat input lists them once the server is running.

## Try it

> List my SurrealDB Cloud instances and tell me which region each one is in.

## Next steps

- [Visual Studio Code](/docs/agents/vscode.md) - the same file, without Copilot
- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - every tool the server publishes

---

Source: https://surrealdb.com/docs/agents/vscode

# Visual Studio Code

Hosted MCP server and Agent Skills setup for VS Code. Covers the servers object in mcp.json, for a workspace or globally.

[Visual Studio Code](https://code.visualstudio.com) has a native MCP client, so any chat extension that uses it can reach your SurrealDB Cloud account.

## Add the MCP server

VS Code uses a `servers` object with an explicit transport type. Put it in `.vscode/mcp.json` for the workspace, or in the user-level `mcp.json` to have it everywhere.

```json
{
  "servers": {
    "surrealdb": {
      "type": "http",
      "url": "https://mcp.surrealdb.com"
    }
  }
}
```

| Scope | Path |
| --- | --- |
| Workspace | `.vscode/mcp.json` |
| Global (macOS) | `~/Library/Application Support/Code/User/mcp.json` |
| Global (Windows) | `%APPDATA%\Code\User\mcp.json` |
| Global (Linux) | `~/.config/Code/User/mcp.json` |

## Sign in

Reload the window, start the server from the MCP view, and approve the connection in the browser window that opens.

## Install the Agent Skills

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

## Check it worked

Ask chat which SurrealDB tools it has available. If nothing appears, reload the window - VS Code reads `mcp.json` at startup.

## Next steps

- [GitHub Copilot](/docs/agents/github-copilot.md) - the same file, with Copilot agent mode
- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - every tool the server publishes

---

Source: https://surrealdb.com/docs/agents/windsurf

# Windsurf

Hosted MCP server and Agent Skills setup for Windsurf. Windsurf uses serverUrl in mcp_config.json rather than url.

[Windsurf](https://windsurf.com) supports MCP servers natively through its Cascade agent. Connecting SurrealDB gives Cascade your Cloud instances and the data inside them while it works.

## Add the MCP server

Windsurf uses `serverUrl` rather than `url`. The file is `~/.codeium/windsurf/mcp_config.json` on macOS and Linux, and `%USERPROFILE%\.codeium\windsurf\mcp_config.json` on Windows.

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

## Sign in

Restart Windsurf, then open **Settings → MCP** and connect. The panel reports each server's status.

## Install the Agent Skills

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

## Check it worked

Open the Cascade panel and ask which SurrealDB tools it has access to.

## Try it

> Deploy nothing yet - just tell me what a small instance in eu-west-1 would cost per month.

Cascade prices the configuration and waits for your go-ahead. Creating an instance costs money, so the server never does it without one.

## Next steps

- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - every tool the server publishes
- [Agent Skills](/docs/build/ai-agents/agent-skills.md) - what each skill covers

---

Source: https://surrealdb.com/docs/agents/zed

# Zed

Hosted MCP server and Agent Skills setup for Zed. Covers the context_servers entry in the Zed settings file.

[Zed](https://zed.dev) calls MCP servers **context servers**. Adding SurrealDB gives Zed's assistant your Cloud instances and the data inside them.

## Add the MCP server

Open settings with `cmd`/`ctrl` `,`, which edits `~/.config/zed/settings.json`, and add SurrealDB under `context_servers`:

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

> [!NOTE]
> Zed runs stdio context servers as child processes. If your version does not yet accept a remote `url`, bridge the endpoint through `mcp-remote` instead, using a `command` with `"path": "npx"` and the arguments `["-y", "mcp-remote", "https://mcp.surrealdb.com"]`.

## Sign in

Restart Zed and approve the connection in the browser window it opens.

## Install the Agent Skills

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

## Check it worked

Ask the assistant panel which SurrealDB tools it can call.

## Next steps

- [SurrealDB MCP Server](/docs/build/ai-agents/mcp.md) - every tool the server publishes
- [Agent Skills](/docs/build/ai-agents/agent-skills.md) - what each skill covers

---

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

# Build

Putting SurrealDB into an application: embedding the engine. Migrating from another database, connecting frameworks, and wiring it to AI agents.

This section is about the seams between SurrealDB and everything around it. Where [Learn](/docs/learn.md) covers the database itself, these pages cover getting it into a codebase and keeping it there.

- [Embedding SurrealDB](/docs/build/embedding.md) - Run the engine inside your application, natively or through WebAssembly.

- [Migrating](/docs/build/migrating.md) - Import data and schemas from PostgreSQL, MongoDB and other databases.

- [Integrations](/docs/build/integrations.md) - Connect frameworks, auth providers, embedding providers and data tooling.

- [AI agents](/docs/build/ai-agents.md) - Give an agent a database it can query, and rules that keep it accurate.

## Related sections

Language-specific setup lives with each SDK in [the reference](/docs/reference.md). Running the server, whether managed or on your own hardware, is covered in [Manage](/docs/manage.md).

---

Source: https://surrealdb.com/docs/explore

# Explore

SurrealDB Studio, video tutorials and demos, and worked examples. Machine-learning models, and the SurrealDB Labs archive.

Hands-on material. Where the rest of the documentation explains one thing at a time, these pages show whole projects and the tools used to build them.

- [SurrealDB Studio](/docs/explore/studio.md) - Query, visualise and manage your data in the official dashboard.

- [Tutorials and demos](/docs/explore/tutorials.md) - Worked walkthroughs, from first query to a deployed application.

- [Machine-learning models](/docs/explore/ml-models.md) - Store and run models alongside the data they operate on.

- [SurrealDB Labs](/docs/labs.md) - Talks, videos and experiments from the team and the community.

---

Source: https://surrealdb.com/docs/frameworks/expo

# Expo

Connect an Expo app to SurrealDB and run your first queries.

[Expo](https://expo.dev/) builds Android and iOS apps from React components. This guide connects an Expo app to SurrealDB, writes a few records, and shows them on screen.

When you finish, you will have a screen that lists records read from your database.

## Before you begin

You need two things:

- An Expo project on SDK 54 or newer, created with `npx create-expo-app`
- A running [SurrealDB instance](/docs/running/overview.md) on your development machine

A mobile app cannot run SurrealDB on the device itself, so it always talks to a database over the network. Everything in this guide is plain JavaScript, so it works in Expo Go as well as in a development build.

## 1. Install the SDK

Install the SDK with `npx expo install`, which picks a version that matches your Expo SDK.

```bash
npx expo install surrealdb
```

## 2. Create the database client

Create a file that holds a single `Surreal` client and the function that opens the connection. Keeping both in one module means every screen shares the same connection.

```ts title="surreal.ts"
import { Surreal } from "surrealdb";

export const db = new Surreal();

export function connectToSurreal() {
    return db.connect(process.env.EXPO_PUBLIC_SURREAL_ENDPOINT!, {
        namespace: "example",
        database: "getting_started",
        authentication: {
            username: "root",
            password: "secret",
        },
    });
}
```

`.connect()` takes the address of your database, along with the [namespace and database](/docs/learn/data-models.md) to work in and the credentials to sign in with. Use `ws://` or `wss://` addresses: a WebSocket connection stays open, so the app authenticates once instead of on every request.

### Setting the address

On a phone or emulator, `localhost` means the device itself, not your development machine. Set `EXPO_PUBLIC_SURREAL_ENDPOINT` to the address that the device can actually reach.

```bash title=".env"
EXPO_PUBLIC_SURREAL_ENDPOINT=ws://10.0.2.2:8000
```

| Where the app runs | Address to use |
|--------------------|----------------|
| iOS simulator | `ws://127.0.0.1:8000` |
| Android emulator | `ws://10.0.2.2:8000` |
| Physical device | `ws://<your-machine-lan-ip>:8000` |

Variables that start with `EXPO_PUBLIC_` are readable inside your app. Restart the development server after changing the file.

> [!WARNING]
> The `root` credentials above are for a local database you are experimenting with. Everything you compile into a mobile app is readable by anyone who installs it, so a released app must never carry them. Use [record access](/docs/reference/query-language/statements/define/access/record.md) to sign real users in instead.

## 3. Connect when the app starts

Expo Router renders `app/_layout.tsx` around every screen, which makes it the right place to open the connection. Wait for it before showing the rest of the app, so no screen queries a connection that is not ready.

```tsx title="app/_layout.tsx"
import { useEffect, useState } from "react";
import { ActivityIndicator, Text } from "react-native";
import { Stack } from "expo-router";
import { connectToSurreal } from "../surreal";

export default function RootLayout() {
    const [status, setStatus] = useState<"connecting" | "ready" | "failed">("connecting");

    useEffect(() => {
        connectToSurreal()
            .then(() => setStatus("ready"))
            .catch(() => setStatus("failed"));
    }, []);

    if (status === "connecting") return <ActivityIndicator />;
    if (status === "failed") return <Text>Could not reach the database.</Text>;

    return <Stack />;
}
```

If you see the failure message, the address is usually the cause. Check the table in step 2 and confirm that SurrealDB is running.

## 4. Insert your first records

Use `.create()` to add a record to a table. The `.content()` chain holds the fields you want to store.

```ts
import { RecordId, Table } from "surrealdb";
import { db } from "./surreal";

const products = new Table("products");

// Create a record with an ID generated by the database
const [banana] = await db.create(products).content({
    name: "Banana",
    price: 0.8,
});

console.log(banana);
// { id: products:0dxay1r0dc9c1cn8vzuq, name: 'Banana', price: 0.8 }

// Create a record with an ID you choose
const apple = await db.create(new RecordId(products, "apple")).content({
    name: "Apple",
    price: 1.5,
});

console.log(apple);
// { id: products:apple, name: 'Apple', price: 1.5 }
```

Every record has an ID made of its table name and a unique key, written as `products:apple`. You do not need to create the table first - SurrealDB adds it on the first write.

## 5. Read and display data

`.select()` reads records back. Pass a `Table` to read all of them, or a `RecordId` to read one. Chain `.where()` and `.limit()` to narrow the result.

```tsx title="app/index.tsx"
import { useEffect, useState } from "react";
import { FlatList, Text, View } from "react-native";
import { Table, lt } from "surrealdb";
import { db } from "../surreal";

interface Product {
    id: string;
    name: string;
    price: number;
}

export default function ProductList() {
    const [products, setProducts] = useState<Product[]>([]);

    useEffect(() => {
        db.select<Product>(new Table("products"))
            .where(lt("price", 1.0))
            .then(setProducts)
            .catch(console.error);
    }, []);

    return (
        <FlatList
            data={products}
            keyExtractor={(product) => String(product.id)}
            renderItem={({ item }) => (
                <View>
                    <Text>{item.name}</Text>
                    <Text>{item.price}</Text>
                </View>
            )}
        />
    );
}
```

`lt` is one of the [expression helpers](/docs/reference/javascript/api/utilities/expr.md) the SDK provides for building conditions. Run the app and you should see the products you created.

## Next steps

You now have an Expo app that connects to SurrealDB, writes records, and reads them back. Real apps also need to sign users in, keep working when the phone locks the screen, and react to changes as they happen.

- [Expo SDK guide](/docs/reference/javascript/frameworks/expo.md) - The full integration guide: connection provider, app lifecycle, secure token storage, and live queries.

- [Authentication](/docs/reference/javascript/concepts/authentication.md) - Sign users up and in with record access instead of database credentials.

- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) - Learn the query builders and how to run SurrealQL statements directly.

- [Live queries](/docs/reference/javascript/concepts/live-queries.md) - Receive changes from the database as they happen, without polling.

> [!NOTE]
> Building without Expo? The [React Native guide](/docs/frameworks/react-native.md) covers the same steps, plus the polyfills a bare project needs.

---

Source: https://surrealdb.com/docs/frameworks/react-native

# React Native

Connect a React Native app to SurrealDB and run your first queries.

[React Native](https://reactnative.dev/) builds Android and iOS apps from React components. This guide connects a React Native app to SurrealDB, writes a few records, and shows them on screen.

When you finish, you will have a screen that lists records read from your database.

## Before you begin

You need two things:

- A React Native project on 0.79 or newer, created with `npx @react-native-community/cli init`
- A running [SurrealDB instance](/docs/running/overview.md) on your development machine

A mobile app cannot run SurrealDB on the device itself, so it always talks to a database over the network.

> [!NOTE]
> Using [Expo](/docs/frameworks/expo.md)? Follow that guide instead. Expo already provides the browser APIs installed in step 1.

## 1. Install the SDK and its polyfills

React Native's JavaScript engine leaves out two browser APIs that the SDK needs: `TextDecoder`, used to read responses from the database, and a complete `URL`, used to parse the address you connect to. Install them alongside the SDK.

```bash
npm install --save surrealdb @bacons/text-decoder react-native-url-polyfill
```

Import both at the very top of `index.js`, above every other import. They have to be in place before the SDK is loaded.

```js title="index.js"
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);
```

Both packages are plain JavaScript, so there is nothing to rebuild.

## 2. Create the database client

Create a file that holds a single `Surreal` client and the function that opens the connection. Keeping both in one module means every screen shares the same connection.

```ts title="surreal.ts"
import { Surreal } from "surrealdb";

export const db = new Surreal();

export function connectToSurreal() {
    return db.connect("ws://10.0.2.2:8000", {
        namespace: "example",
        database: "getting_started",
        authentication: {
            username: "root",
            password: "secret",
        },
    });
}
```

`.connect()` takes the address of your database, along with the [namespace and database](/docs/learn/data-models.md) to work in and the credentials to sign in with. Use `ws://` or `wss://` addresses: a WebSocket connection stays open, so the app authenticates once instead of on every request.

### Setting the address

On a phone or emulator, `localhost` means the device itself, not your development machine. Use the address that the device can actually reach.

| Where the app runs | Address to use |
|--------------------|----------------|
| iOS simulator | `ws://127.0.0.1:8000` |
| Android emulator | `ws://10.0.2.2:8000` |
| Physical device | `ws://<your-machine-lan-ip>:8000` |

A new React Native project already permits unencrypted local connections while you develop, so `ws://` works without any further setup. Release builds do not, which is why production apps connect over `wss://`.

> [!WARNING]
> The `root` credentials above are for a local database you are experimenting with. Everything you compile into a mobile app is readable by anyone who installs it, so a released app must never carry them. Use [record access](/docs/reference/query-language/statements/define/access/record.md) to sign real users in instead.

## 3. Connect when the app starts

Open the connection in `App.tsx` and wait for it before showing the rest of the app, so no screen queries a connection that is not ready.

```tsx title="App.tsx"
import React, { useEffect, useState } from "react";
import { ActivityIndicator, Text } from "react-native";
import { connectToSurreal } from "./surreal";
import { ProductList } from "./ProductList";

export default function App() {
    const [status, setStatus] = useState<"connecting" | "ready" | "failed">("connecting");

    useEffect(() => {
        connectToSurreal()
            .then(() => setStatus("ready"))
            .catch(() => setStatus("failed"));
    }, []);

    if (status === "connecting") return <ActivityIndicator />;
    if (status === "failed") return <Text>Could not reach the database.</Text>;

    return <ProductList />;
}
```

If you see the failure message, the address is usually the cause. Check the table in step 2 and confirm that SurrealDB is running.

## 4. Insert your first records

Use `.create()` to add a record to a table. The `.content()` chain holds the fields you want to store.

```ts
import { RecordId, Table } from "surrealdb";
import { db } from "./surreal";

const products = new Table("products");

// Create a record with an ID generated by the database
const [banana] = await db.create(products).content({
    name: "Banana",
    price: 0.8,
});

console.log(banana);
// { id: products:0dxay1r0dc9c1cn8vzuq, name: 'Banana', price: 0.8 }

// Create a record with an ID you choose
const apple = await db.create(new RecordId(products, "apple")).content({
    name: "Apple",
    price: 1.5,
});

console.log(apple);
// { id: products:apple, name: 'Apple', price: 1.5 }
```

Every record has an ID made of its table name and a unique key, written as `products:apple`. You do not need to create the table first - SurrealDB adds it on the first write.

## 5. Read and display data

`.select()` reads records back. Pass a `Table` to read all of them, or a `RecordId` to read one. Chain `.where()` and `.limit()` to narrow the result.

```tsx title="ProductList.tsx"
import React, { useEffect, useState } from "react";
import { FlatList, Text, View } from "react-native";
import { Table, lt } from "surrealdb";
import { db } from "./surreal";

interface Product {
    id: string;
    name: string;
    price: number;
}

export function ProductList() {
    const [products, setProducts] = useState<Product[]>([]);

    useEffect(() => {
        db.select<Product>(new Table("products"))
            .where(lt("price", 1.0))
            .then(setProducts)
            .catch(console.error);
    }, []);

    return (
        <FlatList
            data={products}
            keyExtractor={(product) => String(product.id)}
            renderItem={({ item }) => (
                <View>
                    <Text>{item.name}</Text>
                    <Text>{item.price}</Text>
                </View>
            )}
        />
    );
}
```

`lt` is one of the [expression helpers](/docs/reference/javascript/api/utilities/expr.md) the SDK provides for building conditions. Run the app and you should see the products you created.

## Next steps

You now have a React Native app that connects to SurrealDB, writes records, and reads them back. Real apps also need to sign users in, keep working when the phone locks the screen, and react to changes as they happen.

- [React Native SDK guide](/docs/reference/javascript/frameworks/react-native.md) - The full integration guide: connection provider, app lifecycle, secure token storage, and live queries.

- [Authentication](/docs/reference/javascript/concepts/authentication.md) - Sign users up and in with record access instead of database credentials.

- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) - Learn the query builders and how to run SurrealQL statements directly.

- [Live queries](/docs/reference/javascript/concepts/live-queries.md) - Receive changes from the database as they happen, without polling.

---

Source: https://surrealdb.com/docs/languages

# SDKs

One official SurrealDB SDK per language. Each has a quickstart that gets you connected and a reference covering every method.

SurrealDB has ten official SDKs. Each speaks the same RPC protocol over WebSocket or HTTP, so the concepts carry between them: connect, select a namespace and database, sign in, then query. What differs is the idiom - types, async model and error handling follow the host language.

Every SDK page opens with a quickstart that gets you connected, and continues into a reference for each method.

- [Rust](/docs/languages/rust.md)

- [JavaScript](/docs/languages/javascript.md)

- [Python](/docs/languages/python.md)

- [Go](/docs/languages/golang.md)

- [.NET](/docs/languages/dotnet.md)

- [Java](/docs/languages/java.md)

- [Kotlin](/docs/languages/kotlin.md)

- [PHP](/docs/languages/php.md)

- [Swift](/docs/languages/swift.md)

- [Mojo](/docs/languages/mojo.md)

[Community SDKs](/docs/languages/community.md) cover further languages, and the [Expo](/docs/frameworks/expo.md) and [React Native](/docs/frameworks/react-native.md) guides cover mobile.

## Choosing between the interfaces

An SDK is the usual way in, but it is not the only one. The [RPC protocol](/docs/reference/rest-api/rpc-protocol.md) keeps one connection open and is what the SDKs use themselves, so it is the interface that supports live queries. The [HTTP REST API](/docs/reference/rest-api/http-protocol.md) opens a connection per request and suits environments where holding a socket is awkward. The [CLI](/docs/reference/cli.md) covers import, export and one-off queries.

---

Source: https://surrealdb.com/docs/languages/community

# Community SDKs

Community-maintained and experimental SurrealDB clients. For languages beyond the official SDKs.

Alongside the official SDKs listed in this section, the community maintains clients for a range of other languages. The projects below are **not** first-party SurrealDB SDKs unless noted. They are maintained by the community (or marked experimental) and may lag behind the database, omit features, or become unmaintained. **Use at your own risk**.

This list is **illustrative, not exhaustive**; other clients exist on package registries and GitHub.

- **C**: [surrealdb.c](https://github.com/surrealdb/surrealdb.c) (experimental C driver; hosted in the SurrealDB organisation)
- **Dart / Flutter**: [surrealdb_flutter](https://github.com/duhanbalci/surrealdb_flutter), [surrealdb-client-dart](https://github.com/wrbl606/surrealdb-client-dart)
- **Elixir**: [surrealix](https://hex.pm/packages/surrealix), [unreal](https://hex.pm/packages/unreal), [surreal_ex](https://hex.pm/packages/surreal_ex), [surrealdb_ex](https://hex.pm/packages/surrealdb_ex) (several overlapping options on Hex)
- **Erlang**: [surreal](https://hex.pm/packages/surreal)
- **Gleam**: [surreal_gleam](https://hex.pm/packages/surreal_gleam)

If you maintain a client and would like it considered for this list, open an issue or pull request against [surrealdb/docs.surrealdb.com](https://github.com/surrealdb/docs.surrealdb.com).

---

Source: https://surrealdb.com/docs/languages/dotnet

# .NET quickstart

Connect to SurrealDB and run your first queries with the .NET SDK.

The .NET 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.

## 1. Install the SDK

Follow the [installation guide](/docs/reference/dotnet/installation.md) to add the SDK to your project. From the .NET CLI, add the `SurrealDb.Net` package:

```bash
dotnet add package SurrealDb.Net
```

Once installed, import the SDK and its models namespaces.

```csharp
using SurrealDb.Net;
using SurrealDb.Net.Models;
using SurrealDb.Net.Models.Auth;
```

The `SurrealDb.Net` namespace contains the `SurrealDbClient`, while `SurrealDb.Net.Models` and `SurrealDb.Net.Models.Auth` provide the record base types and authentication types like [`RootAuth`](/docs/reference/dotnet/core/authentication.md).

## 2. Connect to SurrealDB

Create a new [`SurrealDbClient`](/docs/reference/dotnet/core/create-a-new-connection.md) with a connection string. The URL scheme determines the connection type.

Supported connection protocols include:
- **WebSocket** (`ws://`, `wss://`) for long-lived stateful connections, required for live queries, sessions, and transactions
- **HTTP** (`http://`, `https://`) for short-lived stateless connections
- **Memory** (`mem://`) for [embedded instances](/docs/reference/dotnet/embedding.md)

```csharp
using var db = new SurrealDbClient("ws://127.0.0.1:8000/rpc");
```

After connecting, use [`.SignIn()`](/docs/reference/dotnet/methods/signin.md) to authenticate and [`.Use()`](/docs/reference/dotnet/methods/use.md) to select the namespace and database you want to work with. Most operations require both.

```csharp
await db.SignIn(new RootAuth { Username = "root", Password = "secret" });
await db.Use("main", "main");
```

## 3. Inserting data into SurrealDB

Once connected, you can use the [`Create`](/docs/reference/dotnet/methods/create.md) method to create records. Define a class that derives from `Record` to map to your table, then pass an instance to `Create`.

```csharp
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(created);
```

The `Person` and `Name` classes describe the shape of your data. `Person` derives from `Record`, which provides the record `Id`.

```csharp
public class Person : Record
{
    public string? Title { get; set; }
    public Name? Name { get; set; }
    public bool Marketing { get; set; }
}
public class Name
{
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
}
```

## 4. Retrieving data from SurrealDB

### Selecting records

The [`Select`](/docs/reference/dotnet/methods/select.md) method retrieves all records from a table. Provide the type parameter to tell the SDK what type to deserialise the response into.

```csharp
var people = await db.Select<Person>(TABLE);
Console.WriteLine(people);
```

### Running SurrealQL queries

For more advanced use cases, you can use the [`Query`](/docs/reference/dotnet/methods/query.md) method to run [SurrealQL](/docs/reference/query-language.md) statements directly. Use [`GetValue<>`](/docs/reference/dotnet/core/writing-surrealql.md) to extract a typed result from the response.

```csharp
var queryResponse = await db.Query(
  $"SELECT Marketing, count() AS Count FROM type::table({TABLE}) GROUP BY Marketing"
);
var groups = queryResponse.GetValue<List<Group>>(0);
Console.WriteLine(groups);

public class Group
{
    public bool Marketing { get; set; }
    public int Count { get; set; }
}
```

## 5. Closing the connection

When you create the client with `using var db = ...`, the client is disposed automatically when it goes out of scope, releasing the connection and its resources. If you need to close it explicitly, call `Dispose()`.

```csharp
db.Dispose();
```

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Connection management](/docs/reference/dotnet/core/create-a-new-connection.md) - Learn how to create and configure a SurrealDbClient connection.

- [Authentication](/docs/reference/dotnet/core/authentication.md) - Read more about authentication levels and how to integrate them into your application.

- [Data manipulation](/docs/reference/dotnet/core/data-manipulation.md) - Learn how to create, read, update, and delete records using the SDK.

- [API Reference](/docs/reference/dotnet/methods.md) - Complete reference for all methods, types, and errors.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [.NET SDK reference](/docs/reference/dotnet.md).

---

Source: https://surrealdb.com/docs/languages/golang

# Go quickstart

Connect to SurrealDB and run your first queries with the Go SDK.

The Go 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.

## 1. Install the SDK

Follow the [installation guide](/docs/reference/golang/installation.md) to add the SDK to your project. Once installed, import the SDK and its models package.

```go
import (
	"context"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)
```

The `surrealdb` package contains the client, query functions, and authentication types. The `models` package contains value types like [`RecordID`](/docs/reference/golang/api/values/record-id.md) and [`Table`](/docs/reference/golang/api/values/table.md) that map to SurrealDB's data model.

## 2. Connect to SurrealDB

Use [`FromEndpointURLString`](/docs/reference/golang/api/core/db.md#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.

Supported connection protocols include:
- **WebSocket** (`ws://`, `wss://`) for long-lived stateful connections, required for live queries, sessions, and transactions
- **HTTP** (`http://`, `https://`) for short-lived stateless connections
- **Memory** (`mem://`) for [embedded instances](/docs/reference/golang/embedding.md)

```go
ctx := context.Background()

db, err := surrealdb.FromEndpointURLString(ctx, "ws://localhost:8000")
if err != nil {
	log.Fatal(err)
}
defer db.Close(ctx)
```

After connecting, use [`.Use()`](/docs/reference/golang/api/core/db.md#use) to select the namespace and database you want to work with, and [`.SignIn()`](/docs/reference/golang/api/core/db.md#signin) to authenticate. Most operations require both.

```go
if err := db.Use(ctx, "company_name", "project_name"); err != nil {
	log.Fatal(err)
}

_, err = db.SignIn(ctx, surrealdb.Auth{
	Username: "root",
	Password: "secret",
})
if err != nil {
	log.Fatal(err)
}
```

The `defer db.Close(ctx)` ensures the connection is cleaned up when your function returns, similar to closing a file or database connection in Go.

## 3. Inserting data into SurrealDB

Once connected, you can use the [`Create`](/docs/reference/golang/api/core/db.md#create) function to create records. 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
type User struct {
	ID    *models.RecordID `json:"id,omitempty"`
	Name  string           `json:"name"`
	Email string           `json:"email"`
	Age   int              `json:"age"`
}

user, err := surrealdb.Create[User](ctx, db, models.Table("users"), User{
	Name:  "John",
	Email: "john@example.com",
	Age:   32,
})
if err != nil {
	log.Fatal(err)
}
fmt.Printf("%+v\n", user)
```

You can also create a record with a specific ID using `RecordID`:

```go
type Product struct {
	ID       *models.RecordID `json:"id,omitempty"`
	Name     string           `json:"name"`
	Price    float64          `json:"price"`
	Category string           `json:"category"`
}

product, err := surrealdb.Create[Product](ctx, db, models.NewRecordID("products", "apple"), Product{
	Name:     "Apple",
	Price:    1.50,
	Category: "fruit",
})
if err != nil {
	log.Fatal(err)
}
fmt.Printf("%+v\n", product)
```

Notice that the Go SDK uses generic functions like `surrealdb.Create[User](...)` rather than methods on the `db` object. The type parameter tells the SDK what type to unmarshal the response into.

## 4. Retrieving data from SurrealDB

### Selecting records

The [`Select`](/docs/reference/golang/api/core/db.md#select) function retrieves all records from a table, or a single record by its [`RecordID`](/docs/reference/golang/api/values/record-id.md). Use a slice type parameter when selecting a table, and a single type when selecting a specific record.

```go
users, err := surrealdb.Select[[]User](ctx, db, models.Table("users"))
if err != nil {
	log.Fatal(err)
}
fmt.Printf("All users: %+v\n", users)

apple, err := surrealdb.Select[Product](ctx, db, models.NewRecordID("products", "apple"))
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Apple: %+v\n", apple)
```

### Running SurrealQL queries

For more advanced use cases, you can use the [`Query`](/docs/reference/golang/api/core/db.md#query) function to run [SurrealQL](/docs/reference/query-language.md) statements directly. Use [parameters](/docs/reference/query-language/language-primitives/parameters.md) to safely pass dynamic values.

```go
results, err := surrealdb.Query[[]Product](ctx, db,
	"SELECT name, price FROM products WHERE price < $max_price ORDER BY price",
	map[string]any{"max_price": 5.00},
)
if err != nil {
	log.Fatal(err)
}

for _, qr := range *results {
	fmt.Printf("%+v\n", qr.Result)
}
```

## 5. Closing the connection

Always close the connection when you are done to release resources. The idiomatic Go pattern is to `defer` the close immediately after creating the connection, as shown in step 2.

```go
db.Close(ctx)
```

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Connection management](/docs/reference/golang/concepts/connecting-to-surrealdb.md) - Learn how to manage your database connections, including protocols and configuration.

- [Authentication](/docs/reference/golang/concepts/authentication.md) - Read more about authentication levels and how to integrate them into your application.

- [Data manipulation](/docs/reference/golang/concepts/data-manipulation.md) - Learn how to create, read, update, and delete records using the SDK.

- [API Reference](/docs/reference/golang/api/core/db.md) - Complete reference for all functions, methods, types, and errors.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [Go SDK reference](/docs/reference/golang.md).

---

Source: https://surrealdb.com/docs/languages/java

# Java quickstart

Connect to SurrealDB and run your first queries with the Java SDK.

The Java 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.

## 1. Install the SDK

Follow the [installation guide](/docs/reference/java/installation.md) to install the SDK as a dependency in your project. Once installed, import the SDK to start using it.

```java
import com.surrealdb.Surreal;
```

## 2. Connect to SurrealDB

Use a try-with-resources block to create a connection, then call [`.useNs()`](/docs/reference/java/api/core/surreal.md#use-ns) and [`.useDb()`](/docs/reference/java/api/core/surreal.md#use-db) to select a namespace and database, and [`.signin()`](/docs/reference/java/api/core/surreal.md#signin) to authenticate.

Supported connection protocols include:
- **WebSocket** (`ws://`, `wss://`) for long-lived stateful connections
- **HTTP** (`http://`, `https://`) for short-lived stateless connections
- **Embedded** (`memory`, `surrealkv://`) for in-process databases

```java
import com.surrealdb.Surreal;
import com.surrealdb.signin.RootCredential;

try (Surreal db = new Surreal()) {
    db.connect("ws://localhost:8000");
    db.useNs("company_name").useDb("project_name");
    db.signin(new RootCredential("root", "root"));
}
```

The [`Surreal`](/docs/reference/java/api/core/surreal.md) class implements `AutoCloseable`, so the connection is automatically closed at the end of the try-with-resources block.

## 3. Inserting data into SurrealDB

To represent database records in your application, define [POJO](https://wikipedia.org/wiki/Plain_old_Java_object) classes that match your table structure. A public no-argument constructor is required. Use a [`RecordId`](/docs/reference/java/api/values/record-id.md) field named `id` to hold the record identifier.

```java
import com.surrealdb.RecordId;

public class Person {
    public RecordId id;
    public String name;
    public int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
```

Use [`.create()`](/docs/reference/java/api/core/surreal.md#create) to insert records into a table. When passing a table name, the method returns a list of created records with generated IDs.

```java
Person person = new Person("John", 32);
List<Person> created = db.create(Person.class, "persons", person);
```

To create a record with a specific ID, pass a [`RecordId`](/docs/reference/java/api/values/record-id.md) instead. This returns the created record directly.

```java
Person created = db.create(Person.class, new RecordId("persons", "john"), person);
```

## 4. Retrieving data from SurrealDB

### Selecting records

The [`.select()`](/docs/reference/java/api/core/surreal.md#select) method retrieves all records from a table, or a single record by its `RecordId`.

```java
Iterator<Person> persons = db.select(Person.class, "persons");

Optional<Person> john = db.select(Person.class, new RecordId("persons", "john"));
```

### Running SurrealQL queries

For more advanced use cases, use the [`.queryBind()`](/docs/reference/java/api/core/surreal.md#query-bind) method to execute [SurrealQL](/docs/reference/query-language.md) statements with bound [parameters](/docs/reference/query-language/language-primitives/parameters.md).

```java
Response response = db.queryBind(
    "SELECT * FROM persons WHERE age > $min_age",
    Map.of("min_age", 25)
);

Value result = response.take(0);
```

## 5. Closing the connection

If you use a try-with-resources block as shown above, the connection is closed automatically. Otherwise, call [`.close()`](/docs/reference/java/api/core/surreal.md#close) manually to release resources.

```java
db.close();
```

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Connection management](/docs/reference/java/concepts/connecting-to-surrealdb.md) - Learn how to manage your database connections, including protocols and embedded mode.

- [Authentication](/docs/reference/java/concepts/authentication.md) - Read more about authentication and how to integrate it into your application.

- [Data manipulation](/docs/reference/java/concepts/data-manipulation.md) - Learn how to create, read, update, and delete records using the SDK.

- [API Reference](/docs/reference/java/api/core/surreal.md) - Complete reference for all classes, methods, types, and errors.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [Java SDK reference](/docs/reference/java.md).

---

Source: https://surrealdb.com/docs/languages/javascript

# JavaScript quickstart

Connect and run your first queries with the JavaScript SDK.

The JavaScript 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.

## 1. Install the SDK

Follow the [installation guide](/docs/reference/javascript/installation.md) to install the SDK as a dependency in your project.
Once installed, you can import and instantiate the SDK to start using it.

**ESM**

```ts
import { Surreal } from 'surrealdb';

// Create a new Surreal instance
const db = new Surreal();
```

**CommonJS**

```ts
const { Surreal } = require('surrealdb');

// Create a new Surreal instance
const db = new Surreal();
```

The `Surreal` class can be instantiated multiple times to connect to multiple SurrealDB instances at once.

## 2. Connect to SurrealDB

You can use the `.connect()` method to connect to a local or remote SurrealDB instance.
This method accepts a connection string and a set of options, including namespace, database, and authentication details. Supported connection protocols include:

- **WebSocket** (`ws://`, `wss://`) for long-lived connections (e.g. backend or frontend applications)
- **HTTP** (`http://`, `https://`) for short-lived stateless connections (e.g. server-side rendering applications)
- **Embedded** engines using the [WebAssembly engine](/docs/reference/javascript/engines/wasm.md) or [Node.js engine](/docs/reference/javascript/engines/node.md)

This approach is suitable for connecting to a SurrealDB instance as a [system user](/docs/learn/security/authentication/users.md#system-users), for example when connecting from a server-side application.

```ts
const db = new Surreal();

// Connect as system user using the WebSocket protocol
await db.connect('ws://localhost:8000', {
	namespace: "company_name",
	database: "project_name",
	authentication: {
		username: 'root',
		password: 'secret'
	}
});
```

Alternatively you can use the `.signin()` method to authenticate, however passing the authentication details to the `.connect()` method is the preferred way and allows for automatic reconnecting.

## 3. Inserting data into SurrealDB

Once connected, you can use the `.create()` method to execute a [`CREATE`](/docs/reference/query-language/statements/create.md) query. This method accepts either a `Table` or a `RecordId` as the first argument. Use the `.content()` chain to specify the record data.

```ts
import { Table, RecordId } from 'surrealdb';

const users = new Table('users');
const products = new Table('products');

// Create a record with a random id
const user = await db.create(users).content({
	name: 'John',
	email: 'john@example.com',
	age: 32
});

console.log(user);
// { id: user:w6xb3izpgvz4n0gow6q7, name: 'John', email: 'john@example.com', age: 32 }

// Create a record with a specific ID
const appleId = new RecordId(products, 'apple');
const product = await db.create(appleId).content({
	name: 'Apple',
	price: 1.50,
	category: 'fruit'
});

console.log(product);
// { id: product:apple, name: 'Apple', price: 1.50, category: 'fruit' }
```

## 4. Retrieving data from SurrealDB

### Selecting records

The `.select()` method retrieves all records from a table, or a single record by its `RecordId`.
You can chain methods like `.fields()`, `.where()`, and `.limit()` to refine your query.

```ts
import { Table, RecordId, eq } from 'surrealdb';

const users = new Table('users');
const products = new Table('products');

// Select all users
const allUsers = await db.select(users);

// Select a specific record by ID
const apple = await db.select(new RecordId(products, 'apple'));

// Select specific fields with filtering
const results = await db.select(products)
	.fields('name', 'price')
	.where(eq("category", "fruit"))
	.limit(10);
```

In addition to the `.eq()` function in the above example, we offer a comprehensive set of [expression utilities](/docs/reference/javascript/api/utilities/expr.md) for building type-safe SurrealQL conditions.

### Running SurrealQL queries

For more advanced use cases, you can use the `.query()` method to run [SurrealQL](/docs/reference/query-language.md) statements directly. Use [parameters](/docs/reference/query-language/language-primitives/parameters.md) to safely pass dynamic values.

```ts
const [cheapProducts] = await db.query<[{ name: string; price: number }[]]>(
	'SELECT name, price FROM product WHERE price < $max_price ORDER BY price',
	{ max_price: 5.00 }
);

console.log(cheapProducts);
// [{ name: 'Apple', price: 1.50 }]
```

## 5. Closing the connection

Once you are done, close the connection to free up resources.

```ts
await db.close();
```

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Connection management](/docs/reference/javascript/concepts/connecting-to-surrealdb.md) - Learn how to manage your database connections, including events, reconnecting, and more.

- [Authentication](/docs/reference/javascript/concepts/authentication.md) - Read more about authentication and how to integrate it into your application.

- [Executing queries](/docs/reference/javascript/concepts/executing-queries.md) - Learn how to execute queries against your database, use the query builders, and customise responses.

- [API Reference](/docs/reference/javascript/api/core/surreal.md) - Explore the full API reference for the Surreal client class and its methods.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [JavaScript SDK reference](/docs/reference/javascript.md).

---

Source: https://surrealdb.com/docs/languages/kotlin

# Kotlin quickstart

Connect to SurrealDB and run your first queries with the Kotlin SDK.

The Kotlin SDK for SurrealDB lets you connect to a database and query it from your application with coroutines. This guide covers connecting, authenticating, and running your first queries.

> [!NOTE]
> Every network operation on the SDK is a `suspend` function, so the examples below run inside a coroutine (for example, a [`runBlocking`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) block or a `CoroutineScope`).

## 1. Install the SDK

Follow the [installation guide](/docs/reference/kotlin/installation.md) to add the SDK as a dependency in your project. Once installed, import the client to start using it.

```kotlin
import com.surrealdb.kotlin.SurrealClient
import com.surrealdb.kotlin.SurrealClientConfig
```

## 2. Connect to SurrealDB

Create a [`SurrealClient`](/docs/reference/kotlin/api/core/surreal-client.md) with a [`SurrealClientConfig`](/docs/reference/kotlin/api/core/client-config.md), then call [`.use()`](/docs/reference/kotlin/api/core/surreal-client.md#use) to select a namespace and database, and [`.signin()`](/docs/reference/kotlin/api/core/surreal-client.md#signin) to authenticate.

The transport is selected automatically from the URL scheme:
- **WebSocket** (`ws://`, `wss://`) for long-lived stateful connections that support live queries, transactions, and multiple sessions
- **HTTP** (`http://`, `https://`) for short-lived stateless connections

```kotlin
import com.surrealdb.kotlin.SurrealClient
import com.surrealdb.kotlin.SurrealClientConfig
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

fun main() = runBlocking {
    val client = SurrealClient(SurrealClientConfig(url = "ws://localhost:8000"))

    client.signin(buildJsonObject {
        put("user", "root")
        put("pass", "root")
    })
    client.use("surrealdb", "docs")

    // ...

    client.close()
}
```

The client connects automatically on first use (`autoConnect` defaults to `true`). Call [`.close()`](/docs/reference/kotlin/api/core/surreal-client.md#close) when you are finished to release resources.

## 3. Inserting data into SurrealDB

To represent records in your application, define [`@Serializable`](/docs/reference/kotlin/concepts/serialization.md) data classes that match your table structure.

```kotlin
import kotlinx.serialization.Serializable

@Serializable
data class Person(val name: String, val age: Int)
```

Use the [`.create()`](/docs/reference/kotlin/api/core/surreal-client.md#create) builder to insert a record. Pass a [`Table`](/docs/reference/kotlin/api/values/table.md) to create in a table with a generated ID, or a [`RecordId`](/docs/reference/kotlin/api/values/record-id.md) to create a record with a specific ID. Builders are terminated with `await()` (raw JSON) or the typed [`awaitAs<T>()`](/docs/reference/kotlin/api/core/query-builder.md#await-as) extension.

```kotlin
import com.surrealdb.kotlin.query.RecordId
import com.surrealdb.kotlin.query.Table
import com.surrealdb.kotlin.query.awaitAs
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

val created: Person = client
    .create(RecordId("person", "john"))
    .content(buildJsonObject {
        put("name", "John")
        put("age", 32)
    })
    .awaitAs()
```

## 4. Retrieving data from SurrealDB

### Selecting records

The [`.select()`](/docs/reference/kotlin/api/core/surreal-client.md#select) builder retrieves records from a table or a single record by its [`RecordId`](/docs/reference/kotlin/api/values/record-id.md). Refine it with [`.where()`](/docs/reference/kotlin/api/core/query-builder.md) 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()
```

### Running SurrealQL queries

For more advanced use cases, use [`.query()`](/docs/reference/kotlin/api/core/surreal-client.md#query) to execute [SurrealQL](/docs/reference/query-language.md) statements with bound [parameters](/docs/reference/query-language/language-primitives/parameters.md), or [`.queryAs<T>()`](/docs/reference/kotlin/api/core/surreal-client.md#query-as) to decode the result directly.

```kotlin
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

val people: List<Person> = client.queryAs(
    "SELECT * FROM person WHERE age > \$min_age",
    buildJsonObject { put("min_age", 25) },
)
```

## 5. Closing the connection

Call [`.close()`](/docs/reference/kotlin/api/core/surreal-client.md#close) to release the connection and all associated resources.

```kotlin
client.close()
```

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Connecting to SurrealDB](/docs/reference/kotlin/concepts/connecting-to-surrealdb.md) - Learn how to manage connections, protocols, and reconnection.

- [Authentication](/docs/reference/kotlin/concepts/authentication.md) - Read more about authentication and how to integrate it into your application.

- [Data manipulation](/docs/reference/kotlin/concepts/data-manipulation.md) - Learn how to create, read, update, and delete records using the builders.

- [API Reference](/docs/reference/kotlin/api/core/surreal-client.md) - Complete reference for the client, builders, types, and errors.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [Kotlin SDK reference](/docs/reference/kotlin.md).

---

Source: https://surrealdb.com/docs/languages/mojo

# Mojo quickstart

Connect to SurrealDB and run your first queries with the Mojo SDK.

The Mojo 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.

## 1. Install the SDK

Install the SDK, then import `AsyncSurrealClient` and `ConnectOptions` into your Mojo program.

```python
from surrealdb import AsyncSurrealClient, ConnectOptions
from std.collections import Optional
```

## 2. Connect to SurrealDB

Create a client and connect over HTTP, passing your namespace, database, and access token through `ConnectOptions`.

```python
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
        ),
    )
```

Supported connection protocols include:

- HTTP

The `access_token` here is the base64 encoding of `root:secret`, sent as HTTP Basic auth.

## 3. Inserting data into SurrealDB

The client exposes convenience methods that wrap common SurrealQL statements. Each takes the table or record to act on and a JSON document. Use `create` to insert a new record.

```python
client.create("person", '{ "name": "Chiru", "age": 30 }')
```

## 4. Retrieving data from SurrealDB

### Selecting records

Use `select` to retrieve a record or all records in a table.

```python
client.select("person:chiru")
```

### Running SurrealQL queries

Use `query` to run any SurrealQL statement.

```python
var resp = client.query("SELECT * FROM person WHERE age > 18;")
```

Every call returns an `RpcResponse`. Check `is_ok()` before reading the result, and inspect the error fields otherwise.

```python
if resp.is_ok():
    if resp.result:
        print(resp.result.value())
else:
    print("code:", resp.error_code().value())
    print("message:", resp.error_message().value())
```

## 5. Closing the connection

When you are finished, close the connection to release its resources.

```python
client.close()
```

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Connecting to SurrealDB](/docs/reference/mojo/concepts/connecting-to-surrealdb.md) - Learn about transports, wire formats, and TLS.

- [Executing queries](/docs/reference/mojo/concepts/executing-queries.md) - Work with responses and convenience methods.

- [Authentication](/docs/reference/mojo/concepts/authentication.md) - Sign in and manage credentials.

- [API Reference](/docs/reference/mojo/methods.md) - Complete reference for all methods available in the Mojo SDK.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [Mojo SDK reference](/docs/reference/mojo.md).

---

Source: https://surrealdb.com/docs/languages/php

# PHP quickstart

Connect to SurrealDB and run your first queries with 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.

> [!NOTE]
> This guide covers the stable **v1** line of the PHP SDK. A **v2** rewrite is available in alpha. See the [v2 (alpha) getting-started guide](/docs/reference/php/versions/v2-alpha.md).

## 1. Install the SDK

Follow the [installation guide](/docs/reference/php/v1/installation.md) to add the SDK to your project. Once installed, include the autoloader and import the `Surreal` class.

```php
require __DIR__ . '/vendor/autoload.php';

use Surreal\Surreal;

$db = new Surreal();
```

## 2. Connect to SurrealDB

Use `connect()` to open a connection, then `use()` to select the namespace and database you want to work with, and `signin()` to authenticate. Most operations require both.

Supported connection protocols include:
- **WebSocket** (`ws://`, `wss://`) for long-lived stateful connections
- **HTTP** (`http://`, `https://`) for short-lived stateless connections

```php
$db->connect("ws://127.0.0.1:8000/rpc");

$db->use([
    "namespace" => "surrealdb",
    "database" => "docs",
]);

$token = $db->signin([
    "username" => "root",
    "password" => "root",
]);
```

## 3. Inserting data into SurrealDB

Once connected, use `create()` to create records. Pass a record ID such as `person:tobie` to specify the identifier explicitly, or a table name to generate a random ID.

```php
$person = $db->create("person:tobie", [
    "name" => "Tobie",
    "age" => 32,
]);
```

## 4. Retrieving data from SurrealDB

### Selecting records

The `select()` method retrieves all records from a table, or a single record by its ID.

```php
$everyone = $db->select("person");
```

### Running SurrealQL queries

For more advanced use cases, use `query()` to run [SurrealQL](/docs/reference/query-language.md) statements directly. Use [parameters](/docs/reference/query-language/language-primitives/parameters.md) to safely pass dynamic values.

```php
$result = $db->query(
    'SELECT * FROM person WHERE age > $min',
    ["min" => 18]
);
```

## 5. Closing the connection

Always close the connection when you are done to release resources.

```php
$db->close();
```

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Connection management](/docs/reference/php/v1/concepts/connecting.md) - Learn how to manage your database connections, including protocols and configuration.

- [Authentication](/docs/reference/php/v1/concepts/authentication.md) - Read more about authentication and how to integrate it into your application.

- [Executing queries](/docs/reference/php/v1/concepts/executing-queries.md) - Create, select, update, and delete records in depth.

- [API Reference](/docs/reference/php/v1/methods.md) - The full method reference for the Surreal class.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [PHP (v1) SDK reference](/docs/reference/php/v1.md).

---

Source: https://surrealdb.com/docs/languages/python

# Python quickstart

Connect to SurrealDB and run your first queries with the Python SDK.

The Python 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.

## 1. Install the SDK

Follow the [installation guide](/docs/reference/python/installation.md) to install the SDK as a dependency in your project. Once installed, import the SDK to start using it.

**Synchronous**

		```python
		from surrealdb import Surreal

		db = Surreal("ws://localhost:8000")
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		db = AsyncSurreal("ws://localhost:8000")
		```

The `Surreal` and `AsyncSurreal` factory functions accept a connection URL and return the appropriate connection class based on the protocol.

## 2. Connect to SurrealDB

You can use the `.connect()` method to open the connection, then `.use()` to select a namespace and database, and `.signin()` to authenticate.

Supported connection protocols include:
- **WebSocket** (`ws://`, `wss://`) for long-lived stateful connections
- **HTTP** (`http://`, `https://`) for short-lived stateless connections
- **Embedded** (`mem://`, `file://`, `surrealkv://`) for in-process databases

**Synchronous**

		```python
		from surrealdb import Surreal

		db = Surreal("ws://localhost:8000")
		db.connect()
		db.use("company_name", "project_name")
		db.signin({"username": "root", "password": "secret"})
		```

**Asynchronous**

		```python
		from surrealdb import AsyncSurreal

		db = AsyncSurreal("ws://localhost:8000")
		await db.connect()
		await db.use("company_name", "project_name")
		await db.signin({"username": "root", "password": "secret"})
		```

You can also use a context manager to automatically close the connection when you are done.

**Synchronous**

		```python
		with Surreal("ws://localhost:8000") as db:
		    db.use("company_name", "project_name")
		    db.signin({"username": "root", "password": "secret"})
		```

**Asynchronous**

		```python
		async with AsyncSurreal("ws://localhost:8000") as db:
		    await db.use("company_name", "project_name")
		    await db.signin({"username": "root", "password": "secret"})
		```

## 3. Inserting data into SurrealDB

Once connected, you can use the `.create()` method to create records. Pass a table name or a `RecordID` as the first argument and the record data as the second.

**Synchronous**

		```python
		from surrealdb import RecordID

		user = db.create("users", {
		    "name": "John",
		    "email": "john@example.com",
		    "age": 32,
		})

		product = db.create(RecordID("products", "apple"), {
		    "name": "Apple",
		    "price": 1.50,
		    "category": "fruit",
		})
		```

**Asynchronous**

		```python
		from surrealdb import RecordID

		user = await db.create("users", {
		    "name": "John",
		    "email": "john@example.com",
		    "age": 32,
		})

		product = await db.create(RecordID("products", "apple"), {
		    "name": "Apple",
		    "price": 1.50,
		    "category": "fruit",
		})
		```

## 4. Retrieving data from SurrealDB

### Selecting records

The `.select()` method retrieves all records from a table, or a single record by its `RecordID`.

**Synchronous**

		```python
		users = db.select("users")

		apple = db.select(RecordID("products", "apple"))
		```

**Asynchronous**

		```python
		users = await db.select("users")

		apple = await db.select(RecordID("products", "apple"))
		```

### Running SurrealQL queries

For more advanced use cases, you can use the `.query()` method to run [SurrealQL](/docs/reference/query-language.md) statements directly. Use the `vars` parameter to safely pass dynamic values.

**Synchronous**

		```python
		result = db.query(
		    "SELECT name, price FROM products WHERE price < $max_price ORDER BY price",
		    {"max_price": 5.00},
		)
		```

**Asynchronous**

		```python
		result = await db.query(
		    "SELECT name, price FROM products WHERE price < $max_price ORDER BY price",
		    {"max_price": 5.00},
		)
		```

## 5. Closing the connection

Always close the connection when you are done to release resources. If you use a context manager, this happens automatically.

**Synchronous**

		```python
		db.close()
		```

**Asynchronous**

		```python
		await db.close()
		```

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Connection management](/docs/reference/python/concepts/connecting-to-surrealdb.md) - Learn how to manage your database connections, including protocols and context managers.

- [Authentication](/docs/reference/python/concepts/authentication.md) - Read more about authentication and how to integrate it into your application.

- [Data manipulation](/docs/reference/python/concepts/data-manipulation.md) - Learn how to create, read, update, and delete records using the SDK.

- [API Reference](/docs/reference/python/api/core/surreal.md) - Complete reference for all classes, methods, types, and errors.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [Python SDK reference](/docs/reference/python.md).

---

Source: https://surrealdb.com/docs/languages/rust

# Rust quickstart

Connect to SurrealDB and run your first queries with the Rust SDK.

The Rust 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.

## 1. Install the SDK

Create a new project using `cargo new` and add the [`surrealdb`](https://crates.io/crates/surrealdb) crate along with [`tokio`](https://crates.io/crates/tokio), which lets you use the database inside an `async fn main()`. Enabling the `macros` and `rt-multi-thread` features on `tokio` allows the `#[tokio::main]` attribute to be used on top of `fn main()`.

The two main ways to connect to SurrealDB when getting started are by connecting to a running instance via the `protocol-ws` feature, or by running an embedded instance in memory using the `kv-mem` feature. Each of these can be added via a feature flag in the SDK.

```sh
cargo new my_project
cd my_project
cargo add surrealdb --features kv-mem,protocol-ws
cargo add tokio --features macros,rt-multi-thread
```

Once installed, import the SDK's types into `src/main.rs`.

```rust
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::Surreal;
```

## 2. Connect to SurrealDB

Use [`Surreal::new`](/docs/reference/rust/methods/new.md) with the `Ws` engine to connect to a running SurrealDB instance, then [`signin`](/docs/reference/rust/methods/signin.md) to authenticate and [`use_ns`/`use_db`](/docs/reference/rust/methods/use.md) to select the namespace and database you want to work with. Most operations require both.

Supported connection protocols include:
- **WebSocket** (`ws://`, `wss://`) via the `protocol-ws` feature, for long-lived stateful connections
- **HTTP** (`http://`, `https://`) for short-lived stateless connections
- **Memory/embedded** (`mem://`) via the `kv-mem` feature, for [embedded instances](/docs/reference/rust/embedding.md)

```rust
#[tokio::main]
async fn main() -> surrealdb::Result<()> {

    // Connect to the server
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    // Signin as a namespace, database, or root user
    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    // Select a specific namespace / database
    db.use_ns("main").use_db("main").await?;

    Ok(())
}
```

## 3. Inserting data into SurrealDB

Once connected, you can use [`create`](/docs/reference/rust/methods/create.md) to create records. The most ergonomic way to pass data to and from the database is to use a struct that derives [`SurrealValue`](https://docs.rs/surrealdb/latest/surrealdb/types/trait.SurrealValue.html), which allows for both serialisation and deserialisation between the Rust code and the database.

```rust
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::auth::Root;
use surrealdb::types::{RecordId, SurrealValue};

#[derive(Debug, SurrealValue)]
struct Name {
    first: String,
    last: String,
}

#[derive(Debug, SurrealValue)]
struct Person {
    title: String,
    name: Name,
    marketing: bool,
}

#[derive(Debug, SurrealValue)]
struct Record {
    id: RecordId,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("main").use_db("main").await?;

    // Create a new person with a random id
    let created: Option<Record> = db
        .create("person")
        .content(Person {
            title: "Founder & CEO".to_string(),
            name: Name {
                first: "Tobie".to_string(),
                last: "Morgan Hitchcock".to_string(),
            },
            marketing: true,
        })
        .await?;
    dbg!(created);

    Ok(())
}
```

## 4. Retrieving data from SurrealDB

### Selecting records

The [`select`](/docs/reference/rust/methods/select.md) method retrieves all records from a table. Deserialise the result into a `Vec` of a struct that derives `SurrealValue`.

```rust
// Select all people records
let people: Vec<Record> = db.select("person").await?;
dbg!(people);
```

### Running SurrealQL queries

For more advanced use cases, you can use the [`query`](/docs/reference/rust/methods/query.md) method to run [SurrealQL](/docs/reference/query-language.md) statements directly. Use [`.bind()`](/docs/reference/query-language/language-primitives/parameters.md) to safely pass dynamic values, and [`.take()`](/docs/reference/rust/methods/query.md) to transform a query result into anything that can be deserialised, in this case a `Value`.

```rust
use surrealdb::types::Value;

// Perform a custom advanced query
let mut groups = db
    .query("SELECT marketing, count() FROM type::table($table) GROUP BY marketing")
    .bind(("table", "person"))
    .await?;
dbg!(groups.take::<Value>(0).unwrap());
```

## 5. Closing the connection

Rust has no explicit close method. The connection is closed automatically when the `Surreal` client is dropped, for example when it goes out of scope at the end of your function.

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Authentication](/docs/reference/rust/concepts/authenticating-users.md) - Read more about authentication levels and how to integrate them into your application.

- [Live queries](/docs/reference/rust/concepts/live.md) - Learn how to subscribe to real-time changes in your data with live queries.

- [Embedding SurrealDB](/docs/reference/rust/embedding.md) - Run SurrealDB in memory or on disk directly inside your Rust application.

- [API Reference](/docs/reference/rust/methods.md) - Complete reference for all methods, types, and errors.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [Rust SDK reference](/docs/reference/rust.md).

---

Source: https://surrealdb.com/docs/languages/swift

# Swift quickstart

Connect to SurrealDB and run your first queries with the Swift SDK.

The Swift 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.

## 1. Install the SDK

Follow the [installation guide](/docs/reference/swift/installation.md) to add the SDK to your project. Once installed, import the SDK.

```swift
import SurrealDB
```

## 2. Connect to SurrealDB

Create a client for your endpoint and connect. The `SurrealHTTPClient` is the simplest starting point for most applications.

Supported connection protocols include:
- **HTTP** (`http://`, `https://`) using [`SurrealHTTPClient`](/docs/reference/swift/concepts/connecting.md) for short-lived stateless connections, the simplest way to get started
- **WebSocket** (`ws://`, `wss://`) using the WebSocket client for long-lived stateful connections, required for [live queries](/docs/reference/swift/concepts/live-queries.md)

```swift
let client = try SurrealHTTPClient(endpoint: "http://localhost:8000")
try await client.connect()
defer { Task { await client.close() } }
```

After connecting, use [`signin`](/docs/reference/swift/concepts/authentication.md) to authenticate and [`use`](/docs/reference/swift/concepts/connecting.md) to select the namespace and database you want to work with. Most operations require both.

```swift
let tokens = try await client.signin(.root(username: "root", password: "secret"))
try await client.use(namespace: "myapp", database: "mydb")
```

## 3. Inserting data into SurrealDB

The Swift SDK is model-first. The `@SurrealModel` macro generates `SurrealModel` conformance, the `surrealTable` name, and a type-safe `Fields` namespace used to build [predicates](/docs/reference/swift/concepts/predicates.md).

```swift
@SurrealModel("person")
struct Person: Codable, Sendable {
    let id: String?
    let name: String
    let age: Int
}
```

Once you have a model, use [`create`](/docs/reference/swift/methods/create.md) to insert a record.

```swift
let created: [Person] = try await client.create(
    Person(id: nil, name: "Ada", age: 30)
)
```

## 4. Retrieving data from SurrealDB

### Selecting records

Use [`select`](/docs/reference/swift/methods/select.md) to retrieve all records of a table, or narrow the results with a type-safe predicate built from the model's `Fields` namespace.

```swift
// Select all records of a table
let people = try await client.select(Person.self)

// Select with a type-safe predicate
let adults = try await client.select(
    Person.self,
    where: Person.Fields.age >= 18,
    limit: 20,
    start: 0
)
```

### Running SurrealQL queries

For more advanced use cases, use [`queryRaw`](/docs/reference/swift/methods/query-raw.md) to run [SurrealQL](/docs/reference/query-language.md) statements directly. Use bound parameters to safely pass dynamic values.

```swift
let results: [RPCQueryResult] = try await client.queryRaw(
    "SELECT * FROM person WHERE age > $minAge LIMIT $limit;",
    bindings: [
        "minAge": .int(18),
        "limit": .int(50)
    ]
)

for row in results {
    if row.status == .ok {
        print(row.result)
    }
}
```

## 5. Closing the connection

Always close the connection when you are done to release resources. The idiomatic Swift pattern is to `defer` the close immediately after connecting, as shown in step 2.

```swift
await client.close()
```

## Next steps

You have learned how to install the SDK, connect to SurrealDB, create records, and retrieve data. There is a lot more you can do with the SDK, including updating and deleting records, authentication, live queries, and transactions.

- [Connecting to SurrealDB](/docs/reference/swift/concepts/connecting.md) - Learn how to manage your database connections, including protocols and configuration.

- [Authentication](/docs/reference/swift/concepts/authentication.md) - Read more about authentication levels and how to integrate them into your application.

- [Models](/docs/reference/swift/concepts/models.md) - Learn how to define models with the @SurrealModel macro and build type-safe queries.

- [API Reference](/docs/reference/swift/methods.md) - Complete reference for all methods, types, and errors.

> [!NOTE]
> This getting-started guide covers the essentials. For the complete methods, API, and concept reference, see the [Swift SDK reference](/docs/reference/swift.md).

---

Source: https://surrealdb.com/docs/learn

# Learn

How SurrealDB stores, queries and secures data: SurrealQL and schema. The document, graph, vector and time-series models, and access control.

This section covers what SurrealDB does and how to tell it what you want. It is the conceptual half of the documentation: each page explains a mechanism and shows it working, rather than listing every option. The exhaustive syntax lives in [the reference](/docs/reference.md).

- [Querying](/docs/learn/querying.md) - Mutate and query your data with SurrealQL, the SDKs, or GraphQL.

- [Schema management](/docs/learn/schema-management.md) - Define namespaces, tables, fields, indexes, events and functions.

- [Data models](/docs/learn/data-models.md) - Store documents, graphs, vectors, time series and geospatial data in one engine.

- [Security](/docs/learn/security.md) - Authentication, access methods, row-level permissions and capabilities.

- [Extensions](/docs/learn/extensions.md) - Extend the database with custom functions, modules and WASM plugins.

- [SurrealDB Agent Memory](/docs/agent-memory.md) - The memory and knowledge layer for AI agents, built on SurrealDB.

## Where to start

Reading order depends on what you are building. For an application backend, [querying](/docs/learn/querying.md) then [schema management](/docs/learn/schema-management.md) covers most of what you need. For a data model that spans relations and graph edges, start at [data models](/docs/learn/data-models.md). Anything that will hold user data wants [security](/docs/learn/security.md) before it ships.

---

Source: https://surrealdb.com/docs/manage

# Manage

Running SurrealDB in production: managed instances and organisations. Billing, the surrealctl CLI, observability, schema migration and self-hosting.

Operational documentation, for both SurrealDB Cloud and self-hosted deployments. These pages assume a database that already exists and concentrate on keeping it running, observable and up to date.

- [Instances](/docs/manage/instances.md) - Create, connect to, scale, back up and monitor managed instances.

- [Organisations](/docs/manage/organisations.md) - Members, roles, invitations and billing for a team.

- [surrealctl](/docs/manage/surrealctl.md) - Manage instances and organisations from the command line.

- [Observability](/docs/manage/observability.md) - Metrics, logs, traces and slow-query analysis.

- [Schema migration](/docs/manage/schema-migration.md) - Promote schema changes safely with SurrealKit.

- [Self-hosted](/docs/manage/self-hosted.md) - Run and operate SurrealDB on your own infrastructure.

## Enterprise

[SurrealDB Enterprise](/docs/manage/enterprise.md) adds the controls a regulated deployment needs, including FIPS-validated cryptography and single sign-on.

## Related sections

The full command surface for `surrealctl`, `surreal` and `surqlfmt` is in the [CLI reference](/docs/reference/cli.md). The pages here cover what to run and when; the reference covers every flag.

---

Source: https://surrealdb.com/docs/reference

# Reference

SurrealQL statements and functions, and the full API surface. The HTTP, RPC, CBOR and Postgres wire protocols, the command-line tools, and every official SDK.

Exhaustive material, organised for lookup rather than for reading through. Every SurrealQL statement, every built-in function, every protocol message and every SDK method is documented here. For the explanations behind them, see [Learn](/docs/learn.md).

## Core

- [SurrealQL](/docs/reference/query-language.md) - Statements, functions, operators, data types and language primitives.

- [APIs and protocols](/docs/reference/rest-api.md) - REST, HTTP, RPC, CBOR and Postgres wire protocols, and the shared error format.

- [CLI tools](/docs/reference/cli.md) - Command reference for surrealctl, surreal and surqlfmt.

## SDKs

Each SDK page covers installation, connecting, authentication and the full method list for that language.

- [Rust](/docs/reference/rust.md)

- [JavaScript](/docs/reference/javascript.md)

- [Python](/docs/reference/python.md)

- [Go](/docs/reference/golang.md)

- [.NET](/docs/reference/dotnet.md)

- [Java](/docs/reference/java.md)

- [Kotlin](/docs/reference/kotlin.md)

- [PHP](/docs/reference/php.md)

- [Swift](/docs/reference/swift.md)

- [Mojo](/docs/reference/mojo.md)

[Community SDKs](/docs/languages/community.md) cover further languages and runtimes, and the [Expo](/docs/frameworks/expo.md) and [React Native](/docs/frameworks/react-native.md) guides cover mobile.

## Markdown for agents

Every page in this section is also served as markdown: append `.md` to any path, or send `Accept: text/markdown`. The whole corpus is available at [llms-full.txt](/docs/llms-full.txt), and the page index at [llms.txt](/docs/llms.txt).

---

Source: https://surrealdb.com/docs/running/cloud

# SurrealDB Cloud

Get a free managed SurrealDB instance with an email sign-in. Persistent data without installing the server yourself.

[SurrealDB Cloud](/docs/manage/instances.md) is a managed service: SurrealDB runs in our environment, and you connect from SurrealDB Studio, the SDKs, or the HTTP and WebSocket APIs. Compared to the [SurrealDB Studio Sandbox](/docs/running/sandbox.md), you sign in (typically with an email) and your **data persists** in a proper cloud instance.

<img src="~/assets/img/image/cloud/light/cloud-architecture-light.png" darkSrc="~/assets/img/image/cloud/cloud-architecture-dark.png" alt="Diagram of a SurrealDB Cloud instance: requests to [instance-id].surreal.cloud reach a compute node backed by storage on Amazon S3." />

In SurrealDB Studio you can go from the Sandbox to Cloud with **Deploy to Cloud**, create an instance, and then point your connection at the new instance instead of Sandbox.

For day-to-day management, see [Instances](/docs/manage/instances.md) and [Organisations](/docs/manage/organisations.md) in the *Manage* section.

---

Source: https://surrealdb.com/docs/running/docker

# Docker

Use this tutorial to get started with SurrealDB from within Docker.

Docker runs SurrealDB without installing the server on the host machine. This page covers starting it from the official image and choosing a version tag.

## Running the SurrealDB server using Docker

To get started using Docker, you can use the `latest` tag. To view all the available versions and tags, or to use a specific tag visit the [Docker Hub](https://hub.docker.com/r/surrealdb/surrealdb) page. To start a server use the [`start`](/docs/reference/cli/surrealdb-cli/commands/start.md) command. In Docker, SurrealDB listens on port `8000` in all interfaces by default so that the host can connect to the container in the default bridge networking mode.

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

> [!IMPORTANT]
> For local development, use the `latest-dev` image variant (i.e., docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest-dev start). This version includes a shell and package manager, allowing you to install tools and interact with the container's internals.

In order to persist data when the Docker instance is restarted or shut down, specify a Docker folder using the Docker `-v` command line argument, and use the on-disk storage engine in SurrealDB using the path prefix chosen as a Docker folder.

```bash
mkdir mydata # Create a directory to store the database, owned by the current user
docker run --rm --pull always -p 8000:8000 --user $(id -u) -v $(pwd)/mydata:/mydata surrealdb/surrealdb:latest start rocksdb:/mydata/mydatabase.db
```

The default logging level for the database server is `info`. To control the logging verbosity, specify the `--log` argument. The following command starts the database with `debug` level logging, resulting in more logs being output to the terminal. If extra verbosity is not needed, specify a lower level or simply remove the flag, which will default to the `info` level.

```bash
mkdir mydata # Create a directory to store the database, owned by the current user
docker run --rm --pull always -p 8000:8000 --user $(id -u) -v $(pwd)/mydata:/mydata surrealdb/surrealdb:latest start --log debug rocksdb:/mydata/mydatabase.db
```

### Configuring authentication

Authentication is enabled by default on SurrealDB, while the `--unauthenticated` flag can be used to opt out.

To set up access as an authenticated user, configure your initial root-level user by setting the `--user` and `--pass` command-line arguments.

The following command starts the database with a top-level user named `root` with a password set to `secret`. The root user will be persisted in storage, which means you don't have to include these two arguments the next time you start SurrealDB.

```bash
docker run --rm --pull always -p 80:8000 -v /mydata:/mydata surrealdb/surrealdb:latest start --user root --pass secret rocksdb:mydatabase.db
```

In order to change the default port that SurrealDB uses for web connections and from database clients you can use the Docker `-p` command line argument to tunnel the port to the internal SurrealDB port which SurrealDB is served on. The following command starts the database on port `80`.

```bash
docker run --rm --pull always -p 80:8000 -v /mydata:/mydata surrealdb/surrealdb:latest start --user root --pass secret rocksdb:/mydata/mydatabase.db
```

After running the above command, you should see the SurrealDB server start up successfully.

```bash
docker run --rm --pull always -p 80:8000 -v /local-dir:/container-dir surrealdb/surrealdb:latest start --user root --pass secret rocksdb:/container-dir/mydatabase.db
```
```text title="Output"
2025-08-30T15:06:34.788739Z  INFO surreal::dbs: ✅🔒 Authentication is enabled 🔒✅
2025-08-30T15:06:34.788821Z  INFO surrealdb::kvs::ds: Starting kvs store in rocksdb:/container-dir/mydatabase.db
2025-08-30T15:06:34.788859Z  INFO surrealdb::kvs::ds: Started kvs store in rocksdb:/container-dir/mydatabase.db
2025-08-30T15:06:34.789222Z  INFO surrealdb::kvs::ds: Initial credentials were provided and no existing root-level users were found: create the initial user 'root'.
2025-08-30T15:06:35.205123Z  INFO surrealdb::node: Started node agent
2025-08-30T15:06:35.205827Z  INFO surrealdb::net: Started web server on 0.0.0.0:8080
```

For details on the `start` command, and all of the available configuration options and arguments, view the [`start command documentation`](/docs/reference/cli/surrealdb-cli/commands/start.md).

## Using the command-line tools within Docker
The Docker container contains both the server, and the command line tools for importing, exporting, and querying a remote SurrealDB server.

```bash
docker run --rm --pull always surrealdb/surrealdb:latest help
```

The result should look similar to the output below, confirming that the SurrealDB command-line tool was installed successfully.

```text title="Output"
.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'


SurrealDB command-line interface and server

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://discord.gg/surrealdb).

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 [SUBCOMMAND]

OPTIONS:
	-h, --help    Print help information

SUBCOMMANDS:
	start      Start the database server
	import     Import a SQL script into an existing database
	export     Export an existing database into a SQL script
	version    Output the command-line tool version information
	sql        Start an SQL REPL in your terminal with pipe support
	help       Print this message or the help of the given subcommand(s)
```

For details on the different commands available, visit the [CLI tool documentation](/docs/reference/cli/surrealdb-cli/overview.md).

## Run your first query

With the server container running and port `8000` published, connect to it with the [`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) command from the same image. This starts a SurrealQL REPL against the server, using the credentials from the authentication step above.

```bash
docker run --rm --pull always -it surrealdb/surrealdb:latest sql --endpoint http://host.docker.internal:8000 --username root --password secret --namespace main --database main --pretty
```

> [!NOTE]
> `host.docker.internal` resolves to the host on Docker Desktop for macOS and Windows. On Linux, add `--add-host=host.docker.internal:host-gateway` to the command, or use `--network host` and connect to `http://localhost:8000`. If you have the `surreal` binary installed on the host, you can connect with `surreal sql --endpoint http://localhost:8000` and the same flags instead.

Create a record. There is no need to define the table first, because SurrealDB creates it on the first write.

```surql
CREATE person:tobie SET name = "Tobie", city = "London";
```

```surql title="Output"
[
	{
		city: 'London',
		id: person:tobie,
		name: 'Tobie'
	}
]
```

Select it back to confirm the round trip.

```surql
SELECT name, city FROM person;
```

```surql title="Output"
[
	{
		city: 'London',
		name: 'Tobie'
	}
]
```

When you finish, exit the REPL with `Ctrl+C`. If you started the server with a mounted volume and the on-disk storage engine, the record persists across container restarts.

## Next steps

- Query from your application with an [SDK](/docs/languages/javascript.md) - each language guide starts with a connect-and-query walkthrough.
- Try SurrealQL without a server in the [Studio Sandbox](/docs/running/sandbox.md).
- Learn the query language, starting with the [`SELECT` statement](/docs/reference/query-language/statements/select.md).

---

Source: https://surrealdb.com/docs/running/file-backed

# File-backed

Start a RocksDB database that persists data on the filesystem. The quickest way to get started with data that survives a restart.

A single node of SurrealDB can be run with data persisted to the filesystem. This configuration can be done using either RocksDB or SurrealKV as the backend. Both use the same [transaction and isolation semantics](/docs/learn/querying/concepts-and-guides/transactions.md) as every other SurrealDB deployment.

**RocksDB**

## RocksDB

```bash 
surreal start rocksdb://mydatabase.db
```

The default logging level for the database server is `info`. To control the logging verbosity, specify the `--log` argument. The following command starts the database with `debug` level logging, resulting in more logs being output to the terminal. If extra verbosity is not needed, specify a lower level or simply remove the flag, which will default to the `info` level.

```bash
surreal start --log debug rocksdb://mydatabase.db
```

The SurrealDB server runs with authentication enabled by default. To disable it, the `--unauthenticated` flag can be passed in.

```bash
surreal start --unauthenticated rocksdb://mydatabase.db
```

However, for anything but simple testing, it is better to configure your initial root-level user by setting the `--user` and `--pass` command-line arguments. The following command starts the database with a top-level user named root with a password set to `secret`. The root user will be persisted in storage, which means you don't have to include the command line arguments next time you start SurrealDB.

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

In order to change the default port that SurrealDB uses for web connections and from database clients you can use the `--bind` argument. The following command starts the database on port `8080`.

```bash
surreal start --user root --pass secret --bind 0.0.0.0:8080 rocksdb://path/to/mydatabase
```

After running the above command, you should see the SurrealDB server start up successfully.

```text
surreal start --user root --pass secret --bind 0.0.0.0:8080 rocksdb://mydatabase.db
```
```text title="Output"
2025-08-30T15:06:34.788739Z  INFO surreal::dbs: ✅🔒 Authentication is enabled 🔒✅
2025-08-30T15:06:34.788821Z  INFO surrealdb::kvs::ds: Starting kvs store in file:mydatabase.db
2025-08-30T15:06:34.788859Z  INFO surrealdb::kvs::ds: Started kvs store in file:mydatabase.db
2025-08-30T15:06:34.789222Z  INFO surrealdb::kvs::ds: Initial credentials were provided and no existing root-level users were found: create the initial user 'root'.
2025-08-30T15:06:35.205123Z  INFO surrealdb::node: Started node agent
2025-08-30T15:06:35.205827Z  INFO surrealdb::net: Started web server on 0.0.0.0:8080
```

For details on the different commands available, visit the [CLI tool documentation](/docs/reference/cli/surrealdb-cli/overview.md).

## Parameters on startup

A number of parameters can be used on startup such as `sync` to set when to flush the database to the file system. For more details on these parameters, see [this page](/docs/reference/cli/surrealdb-cli/commands/start.md#supported-parameters-for-rocksdb) for the `start` command or [this page](/docs/reference/cli/surrealdb-cli/environment-variables.md#rocksdb-environment-variables) to set the same parameters using environment variables.

**SurrealKV**

## SurrealKV

[SurrealKV](https://github.com/surrealdb/surrealkv) is an embedded storage engine developed in house by SurrealDB: a versioned key-value store built on an LSM (log-structured merge) tree, with optional time-travel reads when versioning is enabled in SurrealDB. It is implemented in Rust and ships with the main SurrealDB release so the storage layer can evolve with SurrealDB’s access patterns.

> [!IMPORTANT]
> SurrealKV is under active development. See [SurrealKV](https://github.com/surrealdb/surrealkv) release notes and SurrealDB release notes before relying on it for critical production workloads.

## Key features

At a high level, SurrealKV provides:

- MVCC-backed concurrent reads and writes on disk (isolation semantics are defined by the [query layer](/docs/learn/data-models/architecture.md#query-layer), not by the engine alone).
- Durability options (immediate vs eventual flush semantics) aligned with how you configure sync behaviour.
- Built-in versioning for historical / temporal queries in SurrealDB when you opt in. See [versioned startup](/docs/reference/cli/surrealdb-cli/commands/start.md#supported-parameters-for-surrealkv) and the [`VERSION` clause](/docs/reference/query-language/statements/select.md#the-version-clause) for details.
- LSM structure: writes batch into memtables and flush to SSTables.
- Value log (WiscKey-style): Allows large values to be stored separately with garbage collection.
- Checkpoint and restore support in the engine for consistent snapshots (see upstream [SurrealKV](https://github.com/surrealdb/surrealkv) documentation).

## Parameters on startup

Parameters such as `versioned` and `sync` control versioning and durability. See [Supported parameters for SurrealKV](/docs/reference/cli/surrealdb-cli/commands/start.md#supported-parameters-for-surrealkv) and [SurrealKV environment variables](/docs/reference/cli/surrealdb-cli/environment-variables.md#surrealkv-environment-variables).

## SurrealKV performance characteristics and trade-offs

## Strengths

- Sequential write path: LSM trees batch writes and flush to sorted runs, which maps well to SSDs and sequential I/O.
- Concurrent reads: MVCC snapshots let readers proceed without blocking writers in the common case.
- Scalability: Unlike SurrealKV's earlier designs, the LSM architecture is intended to support datasets larger than RAM by keeping the hot working set in memtables and caching while spilling cold data to SSTables on disk.

## Limitations and operational notes

- Compaction and space: Compaction reclaims space but uses extra I/O and temporary disk headroom while merges run.
- Read amplification: Point lookups may touch several levels; very wide range scans can require more I/O than a single B-tree - style read - plan indexes and query shapes accordingly.
- Platform notes: The SurrealKV library targets Unix-like desktop and server OSes; WebAssembly is not a supported target for the native engine (browser embeddings use IndexedDB via SurrealDB’s WASM stack, not SurrealKV on disk). Windows support exists with some filesystem caveats. See upstream [platform compatibility](https://github.com/surrealdb/surrealkv#platform-compatibility) notes for more details.

## Performance implications

SurrealKV tends to work well for write-heavy workloads, prefix-based access patterns, and time-series or versioned data where append-heavy writes and ordered keys are common. Point lookups are typically efficient, though they may involve checking multiple levels of on-disk data structures. Large range scans can require reading across multiple SSTables and levels, which may increase I/O without careful compaction and schema design. As with most LSM-based systems, performance can degrade in severely memory- or disk-constrained environments without tuning.

## Run your first query

With the server running, open a second terminal and connect to it with the [`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) command. This starts a SurrealQL REPL against the server, using the credentials from the step above.

```bash
surreal sql --endpoint http://localhost:8000 --username root --password secret --namespace main --database main --pretty
```

Create a record. There is no need to define the table first, because SurrealDB creates it on the first write.

```surql
CREATE person:tobie SET name = "Tobie", city = "London";
```

```surql title="Output"
[
	{
		city: 'London',
		id: person:tobie,
		name: 'Tobie'
	}
]
```

Select it back to confirm the round trip.

```surql
SELECT name, city FROM person;
```

```surql title="Output"
[
	{
		city: 'London',
		name: 'Tobie'
	}
]
```

When you finish, exit the REPL with `Ctrl+C`. The record persists on disk, so it is still there the next time you start the server against the same path.

## Next steps

- Query from your application with an [SDK](/docs/languages/javascript.md) - each language guide starts with a connect-and-query walkthrough.
- Try SurrealQL without a server in the [Studio Sandbox](/docs/running/sandbox.md).
- Learn the query language, starting with the [`SELECT` statement](/docs/reference/query-language/statements/select.md).

---

Source: https://surrealdb.com/docs/running/in-memory

# In-memory

Start an in-memory SurrealDB database. The quickest way to get started, though data does not persist on shutdown.

**SurrealDB 3.x**

```bash
surreal start mem
```

As SurrealDB runs with in-memory storage by default, the following command is identical to the above.

```bash
surreal start
```

The default logging level for the database server is `info`. To control the logging verbosity, specify the `--log` argument. The following command starts the database with `debug` level logging, resulting in more logs being output to the terminal. If extra verbosity is not needed, specify a lower level or simply remove the flag, which will default to the `info` level.

```bash
surreal start --log debug memory
```

The SurrealDB server runs with authentication by default. In order to disable it, the `--unauthenticated` flag can be passed in.

```bash
surreal start --unauthenticated memory
```

However, for anything but simple testing, it is better to configure your initial root-level user by setting the `--user` and `--pass` command-line arguments. The following command starts the database with a top-level user named `root` with a password set to `secret`.

```bash
surreal start --user root --pass secret
```

In order to change the default port that SurrealDB uses for web connections and from database clients you can use the `--bind` argument. The following command starts the database on port `8080`.

```bash
surreal start --user root --pass secret --bind 0.0.0.0:8080
```

After running the above command, you should see the SurrealDB server start up successfully.

```bash
surreal start --user root --pass secret --bind 0.0.0.0:8080
```
```text title="Output"
2026-03-05T03:15:21.128111Z  INFO surrealdb::core::kvs::ds: Starting kvs store in memory
2026-03-05T03:15:21.129330Z  INFO surrealdb::core::kvs::ds: Started kvs store in memory
2026-03-05T03:15:21.130104Z  INFO surreal::dbs: Operation succeeded operation="check_version" attempts=1
2026-03-05T03:15:21.130137Z  INFO surrealdb::core::kvs::ds: This is a new SurrealDB instance. Initialising default namespace 'main' and database 'main'
2026-03-05T03:15:21.131439Z  INFO surreal::dbs: Operation succeeded operation="initialise_defaults" attempts=1
2026-03-05T03:15:21.131465Z  INFO surreal::dbs: Initialising credentials user=root
2026-03-05T03:15:21.131495Z  INFO surrealdb::core::kvs::ds: Credentials were provided, and no root users were found. The root user 'root' will be created
```

For details on the different commands available, visit the [CLI tool documentation](/docs/reference/cli/surrealdb-cli/overview.md).

## About SurrealMX

Since SurrealDB 3.0, in-memory storage runs on a backend called [SurrealMX](https://github.com/surrealdb/surrealmx) that also allows for [versioned queries](/docs/reference/query-language/statements/select.md#the-version-clause) and [persistent storage](/docs/reference/cli/surrealdb-cli/commands/start.md#supported-parameters-for-memory-surrealmx) as a snapshot or AOL (append-only log).

SurrealMX uses an innovative commit pipeline designed around a key insight: by splitting the commit process into discrete, narrowly-scoped stages, multiple transactions can progress through different stages of the pipeline concurrently, much like instruction pipelining in a CPU.

Opting in to versioning and/or persistent storage is done by adding a path to the area to store the data as well as parameters to customise the type of storage and its behaviour. For example, the following command will start an in-memory server with versioning enabled that stores its data in in snapshots taken every 60 seconds.

```bash
surreal start --user root --pass secret "mem://tmp/data?versioned=true&snapshot=60s"
```

Persistent storage is similar to that used in a product like Redis in that it is a convenience to allow in-memory data to persist, but not a true primary solution for long-term storage. For example, all the data stored must also be able to fit in RAM, and each snapshot is essentially a backup of the entire database. In addition, the persisted data is not compacted as is the case with RocksDB and SurrealKV. For more details on these parameters, see [this page](/docs/reference/cli/surrealdb-cli/commands/start.md#supported-parameters-for-memory-surrealmx) for the `start` command.

The following chart sums up the durability guarantees for SurrealMX in contrast with the effect on performance.

| Configuration | Survives Process Crash | Survives System Crash | Performance |
|--------------|----------------------|---------------------|-------------|
| No persistence | ❌ | ❌ | Fastest |
| Snapshot-only | ⚠️ (last snapshot) | ⚠️ (last snapshot) | Fastest |
| Async AOL + No fsync | ⚠️ (mostly) | ⚠️ (mostly + OS buffers) | Very fast |
| Async AOL + Interval fsync | ⚠️ (mostly) | ⚠️ (mostly + since last fsync) | Very fast |
| Async AOL + Every fsync | ⚠️ (mostly) | ⚠️ (mostly) | Very fast |
| Sync AOL + No fsync | ✅ | ⚠️ (OS buffers) | Fast |
| Sync AOL + Interval fsync | ✅ | ⚠️ (since last fsync) | Fast |
| Sync AOL + Every fsync | ✅ | ✅ | Slow |

**SurrealDB 2.x**

```bash
surreal start memory
```

SurrealDB will assume `memory` in case this argument is not passed in, so the following command is identical to the above.

```bash
surreal start
```

The default logging level for the database server is `info`. To control the logging verbosity, specify the `--log` argument. The following command starts the database with `debug` level logging, resulting in more logs being output to the terminal. If extra verbosity is not needed, specify a lower level or simply remove the flag, which will default to the `info` level.

```bash
surreal start --log debug memory
```

The SurrealDB server runs with authentication by default. In order to disable it, the `--unauthenticated` flag can be passed in.

```bash
surreal start --unauthenticated memory
```

However, for anything but simple testing, it is better to configure your initial root-level user by setting the `--user` and `--pass` command-line arguments. The following command starts the database with a top-level user named `root` with a password set to `secret`.

```bash
surreal start --user root --pass secret
```

In order to change the default port that SurrealDB uses for web connections and from database clients you can use the `--bind` argument. The following command starts the database on port `8080`.

```bash
surreal start --user root --pass secret --bind 0.0.0.0:8080 memory
```

After running the above command, you should see the SurrealDB server start up successfully.

```bash
surreal start --user root --pass secret --bind 0.0.0.0:8080 memory
```
```text title="Output"
2025-08-30T15:06:34.788821Z  INFO surrealdb::kvs::ds: Starting kvs store in memory
2025-08-30T15:06:34.788859Z  INFO surrealdb::kvs::ds: Started kvs store in memory
2025-08-30T15:06:34.789222Z  INFO surrealdb::kvs::ds: Initial credentials were provided and no existing root-level users were found: create the initial user 'root'.
2025-08-30T15:06:35.205123Z  INFO surrealdb::node: Started node agent
2025-08-30T15:06:35.205827Z  INFO surrealdb::net: Started web server on 0.0.0.0:8080
```

For details on the different commands available, visit the [CLI tool documentation](/docs/reference/cli/surrealdb-cli/overview.md).

## Run your first query

With the server running, open a second terminal and connect to it with the [`surreal sql`](/docs/reference/cli/surrealdb-cli/commands/sql.md) command. This starts a SurrealQL REPL against the server, using the credentials from the step above.

```bash
surreal sql --endpoint http://localhost:8000 --username root --password secret --namespace main --database main --pretty
```

Create a record. There is no need to define the table first, because SurrealDB creates it on the first write.

```surql
CREATE person:tobie SET name = "Tobie", city = "London";
```

```surql title="Output"
[
	{
		city: 'London',
		id: person:tobie,
		name: 'Tobie'
	}
]
```

Select it back to confirm the round trip.

```surql
SELECT name, city FROM person;
```

```surql title="Output"
[
	{
		city: 'London',
		name: 'Tobie'
	}
]
```

When you finish, exit the REPL with `Ctrl+C`. Data in an in-memory server is lost on shutdown unless you enable the persistence options above.

## Next steps

- Query from your application with an [SDK](/docs/languages/javascript.md) - each language guide starts with a connect-and-query walkthrough.
- Try SurrealQL without a server in the [Studio Sandbox](/docs/running/sandbox.md).
- Learn the query language, starting with the [`SELECT` statement](/docs/reference/query-language/statements/select.md).

---

Source: https://surrealdb.com/docs/running/installation

# Installation

Install the SurrealDB server on your machine: macOS, Windows, Linux. Nightly builds are covered too.

If you are not ready to install anything yet, start with the [SurrealDB Studio Sandbox](/docs/running/sandbox.md) or [SurrealDB Cloud](/docs/running/cloud.md) as described in the [Running overview](/docs/running/overview.md).

One of the most popular ways to get started with SurrealDB is to install and run it as a standalone database service, allowing any number of clients to connect to it and interact with the data.

Whether you are installing SurrealDB on your local machine for development purposes, or spinning up a production database, this section will guide you through the installation process, ensuring that you have all the necessary dependencies and configurations in place to start using SurrealDB effectively.

The current stable version of SurrealDB is <a href='/releases'> `v3.2.4`</a>.

## Installation steps

There are multiple ways to install SurrealDB, depending on your operating system and development environment. In this section, we will outline the installation steps for each operating system and development environment.

You can install SurrealDB on the following operating systems:

- [macOS](/docs/running/installation/macos.md)

- [Windows](/docs/running/installation/windows.md)

- [Linux](/docs/running/installation/linux.md)

## SurrealDB Cloud

Running and maintaining your own database can be a time consuming task. That's why we offer [SurrealDB Cloud](/docs/manage/instances.md), a fully managed database service that allows you to focus on building your applications without worrying about the underlying infrastructure.

## Nightly builds

- [Nightly](/docs/running/installation/nightly.md) - install an unreleased build to try changes early

---

Source: https://surrealdb.com/docs/running/installation/linux

# Linux

Use this tutorial to install SurrealDB on Linux or Unix operating systems using the SurrealDB install script.

Both the SurrealDB database server and the [command-line tool](/docs/reference/cli/surrealdb-cli/overview.md) are packaged as a single executable file.

## Installing SurrealDB using the install script

To get started, you can use the SurrealDB [install script](https://github.com/surrealdb/install.surrealdb.com). This script securely downloads the latest version for the platform and CPU type. It attempts to install SurrealDB into the `/usr/local/bin` folder, falling back to a user-specified folder if necessary.

```bash
curl -sSf https://install.surrealdb.com | sh
```

### Installing a specific version of SurrealDB

To install a specific version of SurrealDB, a version argument (`-v` or `--version`) can be passed to the install script.

```bash
curl -sSf https://install.surrealdb.com | sh -s -- -v <version>
```

### Updating SurrealDB

To ensure that you are using the latest stable version (`v3.2.4`), update SurrealDB using the following command.

```bash
curl -sSf https://install.surrealdb.com | sh
```

### Confirming the installation

Once installed, you can run the SurrealDB command-line tool by using the `surreal` command. To check whether the install was successful run the following command in your terminal.

```bash
surreal help
```

The result should look similar to the output below, confirming that the SurrealDB command-line tool was installed successfully.

```text title="Output"
.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'


SurrealDB command-line interface and server

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://discord.gg/surrealdb).

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 [SUBCOMMAND]

OPTIONS:
	-h, --help    Print help information

SUBCOMMANDS:
	start      Start the database server
	import     Import a SQL script into an existing database
	export     Export an existing database into a SQL script
	version    Output the command-line tool version information
	sql        Start an SQL REPL in your terminal with pipe support
	help       Print this message or the help of the given subcommand(s)

```

---

Source: https://surrealdb.com/docs/running/installation/macos

# macOS

Use this tutorial to install SurrealDB on macOS, using the SurrealDB install script, or using the third-party Homebrew package manager.

Both the SurrealDB database server and the [command-line tool](/docs/reference/cli/surrealdb-cli/overview.md) are packaged as a single executable file. You can install via the [install script](https://github.com/surrealdb/install.surrealdb.com) or the [Homebrew](https://brew.sh/) package manager.

## Installing SurrealDB using the install script

To get started, you can use the SurrealDB [install script](https://github.com/surrealdb/install.surrealdb.com). This script securely downloads the latest version for the platform and CPU type. It attempts to install SurrealDB into the `/usr/local/bin` folder, falling back to a user-specified folder if necessary.

```bash
curl -sSf https://install.surrealdb.com | sh
```

### Installing a specific version of SurrealDB

To install a specific version of SurrealDB, a version argument (`-v` or `--version`) can be passed to the install script.

```bash
curl -sSf https://install.surrealdb.com | sh -s -- -v <version>
```

### Updating SurrealDB

To ensure that you are using the latest stable version (`v3.2.4`), update SurrealDB using the following command.

```bash
curl -sSf https://install.surrealdb.com | sh
```

## Installing SurrealDB using Homebrew

The quickest way to get going with SurrealDB on macOS is to use Homebrew. This will install both the command-line tools, and the SurrealDB server as a single executable. If you don't use Homebrew, follow the instructions for Linux below to install SurrealDB.

```bash
brew install surrealdb/tap/surreal
```

### Updating SurrealDB

To ensure that you are using the latest stable version (`v3.2.4`), update SurrealDB using the following command.

```bash
brew upgrade surrealdb/tap/surreal
```

## Confirming the installation

Once installed, you can run the SurrealDB command-line tool by using the `surreal` command. To check whether the install was successful, run the following command in your terminal.

```bash
surreal help
```

The result should look similar to the output below, confirming that the SurrealDB command-line tool was installed successfully.

```text title="Output"
.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'


SurrealDB command-line interface and server

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://discord.gg/surrealdb).

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 [SUBCOMMAND]

OPTIONS:
	-h, --help    Print help information

SUBCOMMANDS:
	start      Start the database server
	import     Import a SQL script into an existing database
	export     Export an existing database into a SQL script
	version    Output the command-line tool version information
	sql        Start an SQL REPL in your terminal with pipe support
	help       Print this message or the help of the given subcommand(s)

```

---

Source: https://surrealdb.com/docs/running/installation/nightly

# Nightly

If you prefer developing on the bleeding edge, you can follow this tutorial to install SurrealDB Nightly. The nightly version is built and released every night at midnight.

Nightly builds include the latest features and bug fixes. They are available for macOS, Linux, and Windows via the install scripts ([Unix](https://github.com/surrealdb/install.surrealdb.com), [Windows](https://github.com/surrealdb/windows.surrealdb.com)), or through Docker using the `nightly` tag.

## Installing SurrealDB Nightly on macOS

To get started, you can use the SurrealDB [install script](https://github.com/surrealdb/install.surrealdb.com). This script securely downloads the latest version for the platform and CPU type. It attempts to install SurrealDB into the `/usr/local/bin` folder (or `C:\Program Files\SurrealDB` on Windows), falling back to a user-specified folder if necessary. The following command will attempt to install the nightly version. You can re-run this command daily to ensure you are running the latest build of SurrealDB.

```bash
curl --proto '=https' --tlsv1.2 -sSf https://install.surrealdb.com | sh -s -- --nightly
```

## Installing SurrealDB Nightly on Linux

To get started, you can use the SurrealDB [install script](https://github.com/surrealdb/install.surrealdb.com). This script securely downloads the latest version for the platform and CPU type. It attempts to install SurrealDB into the `/usr/local/bin` folder, falling back to a user-specified folder if necessary. The following command will attempt to install the nightly version. You can re-run this command daily to ensure you are running the latest build of SurrealDB.

```bash
curl --proto '=https' --tlsv1.2 -sSf https://install.surrealdb.com | sh -s -- --nightly
```

## Installing SurrealDB Nightly on Windows

To get started, you can use the SurrealDB [install script](https://github.com/surrealdb/install.surrealdb.com). This script securely downloads the latest version for the platform and CPU type. It installs SurrealDB into the `C:\Program Files\SurrealDB` folder, falling back to a user-specified folder if necessary. The following command will attempt to install the nightly version. You can re-run this command daily to ensure you are running the latest build of SurrealDB.

```powershell
iex "& { $(irm https://windows.surrealdb.com) } -Nightly"
```

## Using SurrealDB Nightly with Docker

To use SurrealDB Nightly with Docker, you can use the `nightly` tag. When running the following command, the latest SurrealDB Nightly version will be pulled from Docker Hub.

```bash
docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:nightly start
```

---

Source: https://surrealdb.com/docs/running/installation/windows

# Windows

Use this tutorial to install SurrealDB on Windows using the SurrealDB install script, or using third-party package managers like Chocolatey or Scoop.

Both the SurrealDB database server and the [command-line tool](/docs/reference/cli/surrealdb-cli/overview.md) are packaged as a single executable file. You can install via the [install script](#installing-surrealdb-using-the-install-script), [Chocolatey](https://chocolatey.org/), or [Scoop](https://scoop.sh/).

## Installing SurrealDB using the install script

To get started, you can use the SurrealDB [install script](https://github.com/surrealdb/windows.surrealdb.com). This script securely downloads the latest version for the platform and CPU type. It installs SurrealDB into the `C:\Program Files\SurrealDB` folder, falling back to a user-specified folder if necessary.

```powershell
iwr https://windows.surrealdb.com -useb | iex
```

### Updating SurrealDB

To ensure that you are using the latest stable version (`v3.2.4`), update SurrealDB using the following command.

```powershell
surreal upgrade
```

### Confirming the installation

Once installed, you can run the SurrealDB command-line tool by using the `surreal` command. To check whether the installation was successful run the following command in your terminal.

```powershell
surreal help
```

The result should look similar to the output below, confirming that the SurrealDB command-line tool was installed successfully.

```text title="Output"
.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'


SurrealDB command-line interface and server

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://discord.gg/surrealdb).

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 [SUBCOMMAND]

OPTIONS:
	-h, --help    Print help information

SUBCOMMANDS:
	start      Start the database server
	import     Import a SQL script into an existing database
	export     Export an existing database into a SQL script
	version    Output the command-line tool version information
	sql        Start an SQL REPL in your terminal with pipe support
	help       Print this message or the help of the given subcommand(s)

```

## Installing SurrealDB using Chocolatey

If you use the [Chocolatey](https://chocolatey.org/) package manager, then you can quickly install SurrealDB with one command. This will install a single executable containing both the command-line tool and the SurrealDB server.

```powershell
choco install surreal
```

### Updating SurrealDB

To ensure that you are using the latest stable version (`v3.2.4`), update SurrealDB using the following command.

```powershell
choco update surreal
```

## Installing SurrealDB using Scoop

If you use the [Scoop](https://scoop.sh/) package manager, then you can quickly install SurrealDB with one command. This will install a single executable containing both the command-line tool and the SurrealDB server.

```powershell
scoop install surrealdb
```

### Updating SurrealDB

To ensure that you are using the latest stable version (`v3.2.4`), update SurrealDB using the following command.

```powershell
scoop update surrealdb
```

## Troubleshooting

### DLLs not installed

**Attempting to open the executable at the install location throws errors that the following DLLs are not installed**

If you encounter an error saying that the following DLLs are not installed:
* VCRUNTIME140.dll
* MSVCP140.dll
* VCRUNTIME140_1.dll

Then you may need to install the [Microsoft Visual C++ Redistributable for Visual Studio](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version).

---

Source: https://surrealdb.com/docs/running/multi-node

# Multi-node

Run SurrealDB against distributed storage: horizontally scalable. Highly available clusters.

Multi-node SurrealDB requires **shared distributed storage** - a backend that every query node can reach with transactional consistency. A single RocksDB file on one server (or one Kubernetes pod) is **not** a multi-node cluster; it is the [single-node, on-disk](/docs/running/file-backed.md) model.

<img src="~/assets/img/image/light/storage-cluster.png" darkSrc="~/assets/img/image/dark/storage-cluster.png" alt="Diagram of a multi-node deployment: SurrealDB compute nodes each connect to a shared distributed storage cluster." />

Because compute and storage are separate layers, query nodes hold no data of their own. Every node reads and writes through the same storage cluster, so nodes can join or leave without data being redistributed, and that is what makes horizontal scaling and high availability possible.

## Managed clusters

The [Scale](https://surrealdb.com/pricing/scale) plan on [managed instances](/docs/manage/instances.md) runs multi-node clusters on distributed storage with replication and consensus. SurrealDB operates the storage layer, so a cluster is provisioned and resized from the Cloud dashboard, or from a script with [`surrealctl`](/docs/reference/cli/surrealctl/overview.md).

## Self-hosted clusters

[SurrealDB Enterprise](https://surrealdb.com/enterprise) covers self-hosted multi-node clusters, and ships the Kubernetes operator and runbooks for running the storage layer yourself. See [Managed Kubernetes](/docs/manage/self-hosted/managed-kubernetes.md) for how this maps onto Amazon EKS, Google GKE, and Azure AKS.

## Single-node deployments

Where one node is enough, [file-backed storage](/docs/running/file-backed.md) covers an on-disk server, and [Deploy on Kubernetes](/docs/manage/self-hosted/kubernetes.md) covers a single SurrealDB pod with RocksDB on a persistent volume. [Deployment models](/docs/manage/self-hosted/deployment-models.md) compares the options side by side.

For the flags accepted when starting a server, see the [`surreal start`](/docs/reference/cli/surrealdb-cli/commands/start.md) reference.

---

Source: https://surrealdb.com/docs/running/overview

# Running SurrealDB

Ways to run SurrealDB, from a browser sandbox to managed cloud. Or install it on your own hardware.

You can start with SurrealDB in more than one way. This section orders them from **the least to the most** involved, so you can pick how much you want to set up.

## Try it without installing

1. **[SurrealDB Studio Sandbox](/docs/running/sandbox.md)** (under a minute) - Open SurrealDB Studio in the browser and use the built-in Sandbox. Nothing to install; data is not persistent, which is perfect for quick experiments and learning SurrealQL.

2. **[SurrealDB Cloud](/docs/running/cloud.md)** (about 5 minutes) - Create a free [SurrealDB Cloud](/docs/manage/instances.md) instance (you will need an email to sign in). You keep persistence and a managed database without running a server on your own machine.

3. **[Installation](/docs/running/installation.md)** (about 10 minutes) - Install the `surreal` binary and run SurrealDB locally or on your own infrastructure. This is the path when you want full control, offline work, or production self-hosting.

## How you run the database on your own infrastructure

After you are running SurrealDB (locally or on servers you manage), you can choose storage and topology:

- [Run a single-node, in-memory server](/docs/running/in-memory.md) with optional persistence and versioning (SurrealMX)
- [Run a single-node, on-disk server](/docs/running/file-backed.md) (RocksDB or SurrealKV)
- [Run a multi-node cluster](/docs/running/multi-node.md)
- [Run with Docker](/docs/running/docker.md)

---

Source: https://surrealdb.com/docs/running/sandbox

# SurrealDB Studio Sandbox

Try SurrealDB in the browser with the Studio Sandbox. No install and no account required.

The fastest way to try SurrealDB is to open [SurrealDB Studio](https://studio.surrealdb.com/) in the browser and use the **Sandbox** connection. You can run queries, explore the data model, and use features such as the SurrealQL and GraphQL editors without installing anything on your computer.

> [!NOTE]
> Sandbox data is **not persistent**: it is reset when the session ends or the sandbox is cleared. It is ideal for learning and ad hoc experiments, not for data you need to keep.

## Next steps

When you want **persistent** data but still do not want to install SurrealDB locally, use **[SurrealDB Cloud](/docs/running/cloud.md)** - a free instance only needs an email to get started.

When you are ready to run SurrealDB on your own machine, continue with **[Installation](/docs/running/installation.md)**.

---

Source: https://surrealdb.com/docs/what-is-surrealdb

# What is SurrealDB

SurrealDB is a multi-model database written in Rust. This page covers what it stores, how it runs, and what SurrealDB Agent Memory adds for AI agents.

SurrealDB is a [multi-model database](/blog/what-are-multi-model-databases) written in Rust. One engine stores documents, graphs, vectors, text, time series, geospatial values and relational tables, and one query language reads and writes across all of them inside a single transaction.

The platform has two products, and both run on the same engine:

- **SurrealDB** is the database. You design the schema, and you choose how strictly to define it.
- **[SurrealDB Agent Memory](/docs/agent-memory.md)** is a memory and knowledge layer for AI agents, built on top of that database.

This page covers the main capabilities of both. If you would rather start writing queries, go to [Sample queries](/docs/learn/querying/surrealql/sample-queries.md). If you want to run the database first, go to [Running SurrealDB](/docs/running/overview.md).

## One engine for every data model

Most applications hold more than one shape of data. A product catalogue is document-shaped, its recommendations are graph-shaped, its search spans text and vectors, and its billing is relational. SurrealDB serves all of those shapes from one engine, so a query can cross from a document to a graph edge to a vector index and back.

<img src="~/assets/img/surrealdb/overview/one-engine-many-models-light.png" darkSrc="~/assets/img/surrealdb/overview/one-engine-many-models.png" alt="Diagram of SurrealDB: SurrealQL, GraphQL and REST or RPC interfaces feed into a single SurrealDB engine written in Rust, which holds document, graph, vector, full-text, relational, time-series, geospatial and key-value models inside one ACID transaction." />

| Model | What it gives you |
| --- | --- |
| [Document](/docs/learn/data-models/document/overview.md) | Records with nested objects and arrays, at any depth. |
| [Graph](/docs/learn/data-models/graph/overview.md) | Typed edges between records, recursive traversal, and data stored on the edge itself. |
| [Vector](/docs/learn/data-models/vector-search/overview.md) | HNSW indexes with cosine, Euclidean and Manhattan distance. |
| [Full-text](/docs/learn/data-models/full-text-search/overview.md) | Configurable analysers, BM25 scoring and highlighting. |
| [Time series](/docs/learn/data-models/time-series/overview.md) | Ordered record IDs and range reads over time windows. |
| [Geospatial](/docs/learn/data-models/geospatial/overview.md) | GeoJSON-style points, lines, polygons and collections. |
| [Relational](/docs/explore/tutorials/tutorials/define-a-schema.md) | Defined tables, typed fields, assertions and views. |

Because the models share one storage layer, a write that touches a record, its relations and its embeddings commits together. See [Architecture](/docs/learn/data-models/architecture.md) for how the engine is put together.

## One query language

[SurrealQL](/docs/learn/querying/surrealql/what-is-surrealql.md) keeps the shape of SQL and adds the traversals, similarity functions and nested access the other models need. If you already write `SELECT`, `CREATE`, `UPDATE` and `DELETE`, you can start straight away, then pick up arrow syntax for graph paths and dot notation for nested fields.

Schema strictness is yours to set. A schemaless table accepts any record, which suits early development. A [schemafull](/docs/learn/data-models/document/schema-modes.md) table stores only the fields you define, with types and assertions enforced on write. You can tighten a table later without rewriting the data.

Three other interfaces reach the same data. [GraphQL](/docs/learn/querying/graphql/overview.md) schemas are generated from your tables, the [REST API](/docs/reference/rest-api.md) covers queries and key-value access over HTTP, and [`DEFINE API`](/docs/reference/query-language/statements/define/api.md) publishes your own HTTP endpoints written in SurrealQL.

## Search and retrieval in one query

Retrieval for AI applications usually needs more than one signal. SurrealDB runs those signals together:

- **Vector similarity** over HNSW indexes, for meaning.
- **Full-text search** with BM25 scoring, for wording.
- **Graph traversal** across typed edges, for connection.
- **[Hybrid search](/docs/learn/data-models/vector-search/hybrid-search.md)** that fuses text and vector results with reciprocal rank fusion.

A single statement can find semantically similar documents, walk to the entities they mention, and filter on a structured field, without a second store or a second round trip. This is the basis of the [RAG and Graph RAG patterns](/docs/learn/data-models/vector-search/rag-architecture-patterns.md) documented for SurrealDB.

## Real-time by default

[Live queries](/docs/learn/querying/real-time/live-queries.md) let a client subscribe to a filtered set of records and receive changes as they happen. [Table events](/docs/reference/query-language/statements/define/event.md) fire on create, update and delete, and [`ASYNC` events](/docs/reference/query-language/statements/define/event.md#async-events) run after commit for work that should not hold up the write.

The database is your event source and your source of truth at once, which keeps reactive features close to the data they react to.

## ACID transactions across every model

Each statement runs in its own transaction by default, and [`BEGIN`](/docs/reference/query-language/statements/begin.md) starts a manual transaction spanning as many statements, tables and models as you need. Every transaction runs under [snapshot isolation](/docs/learn/querying/concepts-and-guides/transactions.md#snapshot-isolation), the one isolation level SurrealDB offers, with write conflicts detected at commit.

The guarantee holds on every storage engine and every deployment model, from an embedded in-memory database to a distributed cluster. See [Transactions](/docs/learn/querying/concepts-and-guides/transactions.md) for the isolation semantics and the retry behaviour they imply.

## Runs where your application runs

SurrealDB ships as a single Rust binary and separates compute from storage. The same database therefore runs inside your application, on one server, or across a cluster.

<img src="~/assets/img/surrealdb/overview/deployment-models-light.png" darkSrc="~/assets/img/surrealdb/overview/deployment-models.png" alt="Four deployment models side by side: embedded in an application, a single node with disk persistence, a distributed set of compute nodes on shared storage, and a managed deployment run by SurrealDB Cloud. All four share the same SurrealQL, SDKs and transaction guarantees." />

- **[Embedded](/docs/build/embedding.md)** in a Rust, Go, JavaScript, Python or .NET application, in memory or on disk, and in the browser through WebAssembly and IndexedDB.
- **[Single node](/docs/running/file-backed.md)** on RocksDB, which suits development and smaller production workloads.
- **[Distributed](/docs/manage/self-hosted/deployment-models.md)** across many compute nodes on shared storage, with automatic sharding and read replicas.
- **[Managed](/docs/manage/instances.md)** on SurrealDB Cloud, which runs the infrastructure, the backups and the scaling for you.

Moving between them does not change your queries. See [Deployment models](/docs/manage/self-hosted/deployment-models.md) for the trade-offs of each.

## Secure and multi-tenant by design

SurrealDB can sit behind a backend service or accept connections straight from a frontend, because access control reaches down to individual fields.

- **[Namespaces and databases](/docs/learn/data-models/architecture.md#system-structure)** separate organisations, teams and environments, with no limit on either.
- **[Role-based access](/docs/reference/query-language/statements/define/access.md)** applies at root, namespace and database level.
- **[Record access](/docs/reference/query-language/statements/define/access/record.md)** authenticates your end users against your own tables.
- **[Table and field permissions](/docs/learn/security.md)** decide what each subject may read and write.
- **JWT and third-party authentication** cover the common OAuth providers and signing algorithms.

Encryption in transit and at rest, audit logging, SOC 2 Type 2, ISO 27001, Cyber Essentials Plus and GDPR compliance apply to the managed service. See [Security](/docs/learn/security.md) for the full picture.

## Memory for AI agents

An agent starts each session with nothing. SurrealDB Agent Memory gives it durable memory instead: a layer that turns conversations, documents and connected systems into structured, time-aware facts, then retrieves them on demand.

<img src="~/assets/img/surrealdb/overview/agent-memory-layer-light.png" darkSrc="~/assets/img/surrealdb/overview/agent-memory-layer.png" alt="Diagram of SurrealDB Agent Memory: conversations, documents and systems feed a memory layer offering typed memory, a knowledge graph, hybrid recall and time and provenance tracking. The agent reads from and writes to that layer, and everything is stored in one SurrealDB database." />

The layer is built around six ideas:

- **[Typed memory](/docs/agent-memory/memory-and-knowledge.md)** distinguishes episodes, identity, knowledge, context, instructions and uncertainty.
- **A knowledge graph** stores entities as nodes and typed relationships as edges.
- **Hybrid recall** fuses meaning, wording, connection and recency in one ranker.
- **Tiered queries** keep cheap questions cheap.
- **[Provenance and time](/docs/agent-memory/mental-model/two-layer-architecture.md)** record where each fact came from and when it held, so a superseded belief is end-dated rather than overwritten.
- **Autonomous understanding** improves the memory between conversations through reflection and consolidation.

Every read, decision and write commits inside one SurrealDB transaction, so documents, relations, embeddings and retrieval traces stay consistent with each other. Start with [What is SurrealDB Agent Memory?](/docs/agent-memory/welcome/what-is-surrealdb-agent-memory.md).

## Extend the database itself

Logic that belongs next to the data can live in the database:

- **[Custom functions](/docs/learn/querying/concepts-and-guides/custom-functions.md)** in SurrealQL, for repeated or complicated expressions.
- **[JavaScript functions](/docs/reference/query-language/scripting/overview.md)**, each running in its own isolated context.
- **[WebAssembly modules](/docs/learn/extensions.md)** written in Rust, compiled to WASM and loaded at runtime.
- **[SurrealML](/docs/explore/ml-models.md)** models trained in PyTorch, TensorFlow or Sklearn and executed through an ONNX runtime.
- **[Buckets](/docs/reference/query-language/statements/define/bucket.md)** for files on disk, in memory, or in S3, Google Cloud Storage and Azure Blob Storage.
- **[MCP](/docs/agents.md)** for connecting coding agents such as Claude Code, Cursor and VS Code directly to a database.

## In production

SurrealDB runs at scale in organisations including:

- **Samsung Ads**, for knowledge graphs in advertising analytics.
- **Verizon**, for a generative AI assistant used by field technicians.
- **Tencent**, for infrastructure monitoring, after consolidating nine tools into one.
- **PolyAI**, for low-latency RAG across voice AI experiences.
- **SiteForge**, to shorten its development cycle and reduce backend API usage.

More detail is on the [case studies page](/casestudies), and the complete capability list is on the [features page](/features).

## Where next

- [Sample queries](/docs/learn/querying/surrealql/sample-queries.md) - Run your first SurrealQL queries against a live instance.

- [Running SurrealDB](/docs/running/overview.md) - Start the database in memory, on disk, in Docker, or in the browser.

- [Architecture](/docs/learn/data-models/architecture.md) - How the engine separates compute from storage, and how a database is structured.

- [Agent Memory](/docs/agent-memory.md) - Give your agents durable, queryable memory built from your own data.

---

Source: https://surrealdb.com/docs/labs

# SurrealDB Labs

Talks, videos and experiments from the team and the community. Each entry links out to its own source.

## Source code

- [allographer](https://github.com/itsumura-h/nim-allographer) - Nim ORM and query builder that supports SurrealDB. By Itsumura H.
- [Aspire Integration](https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.SurrealDb) - .NET Aspire hosting integration for local SurrealDB dev stacks. By David Bottiau.
- [AspNetCore.HealthChecks.SurrealDb](https://www.nuget.org/packages/AspNetCore.HealthChecks.SurrealDb) - ASP.NET Core health check package for monitoring SurrealDB connectivity. By David Bottiau.
- [CLI phone book in Python using SurrealDB as database.](https://python.code-maven.com/surrealdb-python-cli-phonebook) - Tutorial building a CLI phonebook in Python with SurrealDB. By Gabor Szabo.
- [Getting started with SurrealDB using Python and Docker.](https://python.code-maven.com/surrealdb-getting-started) - Walkthrough for running SurrealDB with Python and Docker. By Gabor Szabo.
- [GKE using Terraform](https://github.com/dvanmali/terraform-google-surrealdb) - Terraform module for running SurrealDB on Google Kubernetes Engine. By Dylan Vanmali.
- [IoT telemetry example](https://github.com/surrealdb/example-iot-telemetry) - Reference IoT telemetry ingest and queries with SurrealDB. By Martin Schaer.
- [Surreal Transfer](https://github.com/LucyEgan/surreal-transfer) - CLI utilities for exporting and importing SurrealDB data. By Lucy Egan.
- [Surreal-4o Fine-tuned Model Datasets for SurrealQL Queries - Project to create structured datasets for OpenAI.](https://github.com/sFritsch09/surreal-4o) - Structured datasets for fine-tuning models on SurrealQL-style queries. By Sebastian Fritsch.
- [surreal-codegen](https://github.com/siteforge-io/surreal-codegen) - Code generation tooling for SurrealDB-backed TypeScript projects. By Albert Marashi.
- [surreal-ts](https://github.com/horvbalint/surreal-ts) - Generate TypeScript types from SurrealDB schema and queries. By horvbalint.
- [SurrealDB + Go Driver Starter.](https://github.com/sbshah97/surrealdb-go-starter-project) - Starter project wiring the official SurrealDB Go SDK into an application. By Salman Shah.
- [SurrealDB + Vue Blog Starter.](https://github.com/SrWither/surrealdb-vuejs) - Vue.js blog starter template backed by SurrealDB. By Fadel SrWither.
- [SurrealDB AI Assistant](https://surrealdb-ai.jimpex.dev/) - Web AI assistant that queries and explores a SurrealDB database. By Jimpex.
- [SurrealDB AI Docs Retrieval - Project to showcase: How to build a GPT-Based question-answering system on top of SurrealDB Docs.](https://github.com/truskovskiyk/surrealdb-docs-retrieval) - RAG-style demo that answers questions using SurrealDB documentation. By Kyryl Truskovskyi.
- [SurrealDB as a Vector Store for LangChain - A Jupyter notebook demonstrating how to use SurrealDB as a Vector Store.](https://github.com/lalanikarim/notebooks/blob/main/SurrealDB-Langchain.ipynb) - Jupyter notebook showing SurrealDB as a LangChain vector store. By Karim Lalani.
- [SurrealDB GitHub Action](https://github.com/marketplace/actions/surrealdb-in-github-action) - GitHub Action to run SurrealDB in CI pipelines. By SurrealDB.
- [SurrealDB Grafana datasource](https://github.com/grafana/surrealdb-datasource) - Official Grafana data source for SurrealDB queries. By Grafana Labs.
- [SurrealDB MCP Server](https://github.com/nsxdavid/surrealdb-mcp-server) - Model Context Protocol server that connects AI tools to SurrealDB. By David Whatley.
- [SurrealDB ODataV4 Connector](https://github.com/knackstedt/odatav4) - OData v4 tooling and connector work for SurrealDB. By Andrew G. Knackstedt.
- [SurrealDB Presence Demo - Demo project on how to create a realtime presence web application using SurrealDB Live Queries.](https://github.com/Odonno/surrealdb-presence-demo) - Sample realtime presence app built with SurrealDB live queries. By David Bottiau.
- [surrealdb_extra](https://github.com/jakin010/surrealdb_extra) - Rust helper crate extending the official SurrealDB driver. By Jakin010.
- [surrealdb-client-generator](https://github.com/sebastianwessel/surrealdb-client-generator) - Generate typed API clients from your SurrealDB schema and queries. By Sebastian Wessel.
- [surrealdb-flutter](https://github.com/duhanbalci/surrealdb_flutter) - Dart and Flutter client for connecting to SurrealDB. By Duhan Balci.
- [surrealdb-valibot](https://github.com/ShadowWolf308/surrealdb-valibot) - Validate SurrealDB records using Valibot schemas. By Levy van der Valk.
- [surrealdb-zod](https://github.com/ShadowWolf308/surrealdb-zod) - Define and validate SurrealDB-shaped data with Zod. By Levy van der Valk.
- [surrealdb.c](https://github.com/surrealdb/surrealdb.c) - Official C bindings and examples for embedding SurrealDB. By SurrealDB.
- [surrealdb/surrealdb](https://hub.docker.com/r/surrealdb/surrealdb) - Official SurrealDB container image on Docker Hub. By SurrealDB.
- [Surrealist Python tool](https://github.com/kotolex/surrealist) - Python driver and ergonomic query helpers for SurrealDB. By Kotolex.
- [SvelteKit Surreal Database Authentication](https://github.com/jdgamble555/sveltekit-surreal-js) - SvelteKit example with SurrealDB-backed sign-in and sessions. By Jonathan Gamble.
- [UnrealORM: TypeScript ORM built for SurrealDB](https://unreal-orm.jimpex.dev) - TypeScript ORM designed around SurrealDB models and relations. By Jimpex.
- [Use SurrealDB with LangChain](https://github.com/surrealdb/langchain-surrealdb) - Reference integration for using SurrealDB with LangChain. By SurrealDB.
- [VentStream: Real-time CDC sync into SurrealDB](https://ventstream.dev/docs/connectors/sinks/surrealdb) - Stream PostgreSQL, MySQL, MongoDB, and Neo4j into SurrealDB in real time. By Bashiru Bukari.

## Videos

- [10 schema tips for SurrealDB](https://www.youtube.com/watch?v=KNxqL5ZHqFY) - By SurrealDB.
- [10 Tips and Tricks for SurrealDB Studio](https://www.youtube.com/watch?v=lbzoKPBWw3Q) - By SurrealDB.
- [Beyond Surreal? A closer look at NewSQL Relational Data - Beyond Fireship.](https://www.youtube.com/watch?v=LCAIkx1p1k0) - By Fireship.
- [Building an App with Graph Relations, Live Queries and Authentication](https://www.youtube.com/watch?v=m7TwzxEHC-Q) - By SurrealDB.
- [CRUD using SurrealDB in RUST | SurrealDB](https://www.youtube.com/watch?v=3o58KxYOn3E) - By SurrealDB.
- [Designing your schema in SurrealDB Studio](https://www.youtube.com/watch?v=Z1YRY8d7nyg) - By SurrealDB.
- [Different ways to perform a Vector Search in SurrealDB](https://www.youtube.com/watch?v=MqddPmgKSCs) - By SurrealDB.
- [Document-Style Relationships in SurrealDB](https://www.youtube.com/watch?v=TyX45cyZ-WO) - By SurrealDB.
- [Embed SurrealDB Studio in your projects](https://www.youtube.com/watch?v=AzQBvyg9Awc) - By SurrealDB.
- [Getting started with Surreal Cloud](https://www.youtube.com/watch?v=S04qOKkVcmE) - By SurrealDB.
- [Getting started with SurrealDB Studio](https://www.youtube.com/watch?v=VQnHYKNiPso) - By SurrealDB.
- [Getting started with SurrealDB using our JavaScript SDK](https://www.youtube.com/watch?v=fLCO_digirs) - By SurrealDB.
- [Getting started with SurrealDB using our Rust SDK](https://www.youtube.com/watch?v=l1cuddL6A80) - By SurrealDB.
- [Getting started with SurrealDB! Future of cloud databases (maybe)?](https://www.youtube.com/watch?v=D41jb4DDIdA) - By Chris Hay.
- [Graph-Style Relationships in SurrealDB](https://www.youtube.com/watch?v=zwQwKvMa9sU) - By SurrealDB.
- [Graph, Full-Text Search and Vector Search in SurrealDB Studio](https://www.youtube.com/watch?v=_tod9GzWe2E) - By SurrealDB.
- [Hosting Surreal DB in Rust in Less Than 3 Minutes.](https://www.youtube.com/watch?v=VoRoeL1tal4) - By Gui Bibeau.
- [How a luxury fashion retailer scaled personalised recommendations using Surre](https://www.youtube.com/watch?v=yLw9MvNfuY8) - By SurrealDB.
- [How I built a SaaS powered by SurrealDB](https://youtube.com/live/NMcMSqemMo0) - By SurrealDB.
- [How to Build A Full Stack Rust Dashboard App with Leptos, Actix Web and SurrealDB](https://youtu.be/DQB-cJPYChg?si=O9x-6J5BwyUhWXGO) - By SurrealDB.
- [How to Build A Rust Backend with Actix Web and SurrealDB (Full Tutorial)](https://www.youtube.com/watch?v=Rnw-x21kGaA) - By SurrealDB.
- [How to do a Full-Text Search Query in SurrealQL](https://www.youtube.com/watch?v=AEzx1lt0ojY) - By SurrealDB.
- [How to Simplify Your Tech Stack with SurrealDB](https://www.youtube.com/watch?v=kHnn1XTnZCk) - By SurrealDB.
- [Livestream series documenting learning SurrealDB.](https://www.youtube.com/playlist?list=PL5AVzKSngnt_xPGNuYdrbB7NZtJbQ046a) - By Xkonti.
- [Network capabilities in Surreal Cloud](https://www.youtube.com/watch?v=EUp5PxbzvV4) - By SurrealDB.
- [Relational-Style Relationships in SurrealDB](https://www.youtube.com/watch?v=98QhvBFjcQ4) - By SurrealDB.
- [Run SurrealDB in your browser using our WASM engine](https://www.youtube.com/watch?v=D7u5UmrVG3g) - By SurrealDB.
- [Run SurrealDB inside NodeJS using our NodeJS engine](https://www.youtube.com/watch?v=uW_9Hqg7N4Q) - By SurrealDB.
- [Rust Powered Database SurrealDB (It's Pretty Ambitious) - Code to the Moon.](https://www.youtube.com/watch?v=DPQbuW9dQ7w) - By Code to the Moon.
- [Schemaless vs Schemafull Databases](https://www.youtube.com/watch?v=H5PfRJQIKeE) - By SurrealDB.
- [SurrealDB - Rust Embedded Database - Quick Tutorial.](https://www.youtube.com/watch?v=iOyvum0D3LM) - By Jeremy Chone.
- [SurrealDB in 100 seconds.](https://www.youtube.com/watch?v=C7WFwgDRStM) - By Fireship.
- [SurrealDB Studio for Power Users](https://www.youtube.com/watch?v=q5LFNPpH2X0) - By SurrealDB.
- [SurrealDB. The Kitchen Sink Document Store that might dethrone Firebase.](https://www.youtube.com/watch?v=tWpj8Bc_jBQ) - By Ray Villalobos.
- [Understanding User Groups in SurrealDB](https://www.youtube.com/watch?v=cGAxH9FezUY) - By SurrealDB.
- [Using SurrealDB to prove football statistics.](https://www.youtube.com/watch?v=6J1SPMXzOh4&t=5s) - By Joseph McCarthy.

## Blogposts

- [How to Use SurrealDB with the Fresh Framework and Deno.](https://www.freecodecamp.org/news/how-to-use-surrealdb-with-fresh-framework/) - By Rajdeep Singh.
- [Improve database management with SurrealDB](https://blog.logrocket.com/improve-database-management-surrealdb/) - By Alexander Nnakwue.
- [Setting up an invite system](https://mord-blog.vercel.app/articles/surrealdb-invitation-system) - By Mordechai Hadad.
- [Simple API with Gin/Gonic and SurrealDB (GO).](https://atoo.hashnode.dev/simple-api-with-gingonic-and-surrealdb) - By Atharva Deshpande.
- [Unlocking SurrealDB: Building a Real-World Multi-Tenant RBAC System Made Easy (4 Part Series).](https://dev.to/sebastian_wessel/series/24535) - By Sebastian Wessel.

## Documentation

- [Build a realtime presence web application using SurrealDB Live Queries](/docs/tutorials/build-a-realtime-presence-web-application-using-surrealdb-live-queries) - Official tutorial: build a realtime presence app with live queries. By David Bottiau.
- [SurrealDB Vector Store for LangChain](https://python.langchain.com/docs/integrations/vectorstores/surrealdb) - LangChain documentation for using SurrealDB as a vector store. By LangChain.
- [surrealdb-extras](https://docs.rs/surrealdb-extras/latest/surrealdb_extras/) - Rust API reference for the surrealdb-extras helper crate. By Frederik Uni.
