Skip to content
Sign In

Overview

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.

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.

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:

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:

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 makes them otherwise.

Wrapping the same two CREATE statements in BEGIN and COMMIT ties their fates together:

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:

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.

FieldDescription
kindThe error category. The primary thing to branch on.
codeNumeric wire code, kept for backwards compatibility.
messageA human-readable description liable to change. Be sure not to match on it unless you are able to update the match when upgrading versions.
detailsStructured detail for kinds that carry one, itself carrying a nested kind.
causeThe underlying error, where one was attached. Nested errors use this same shape.
KindMeaning
ValidationParse error, invalid request, or invalid parameters
ConfigurationA feature or configuration is not supported
QueryA query timed out, was cancelled, or was not executed
SerializationA value could not be serialised or deserialised
NotAllowedA permission or authorisation check failed
NotFoundA resource does not exist
AlreadyExistsA resource already exists
ConnectionA client-side connection failure
ThrownA THROW statement ran in SurrealQL
InternalAn internal or unexpected failure
ContextA 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.

CodeName
-32700Parse error
-32600Invalid request
-32601Method not found
-32602Method not allowed
-32603Invalid parameters
-32604Live query not supported
-32605Bad live query configuration
-32606Bad GraphQL configuration
-32000Internal error
-32001Client-side error
-32002Invalid authentication
-32003Query not executed
-32004Query timed out
-32005Query cancelled
-32006Thrown
-32007Serialization error
-32008Deserialization error
-32009Query 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.

Each SDK exposes these kinds through its own error type, and documents the two-layer behaviour in its own idiom.

Was this page helpful?