> Full SurrealDB documentation index: https://surrealdb.com/docs/llms.txt

# Extension attribute reference

Reference for the SurrealDB extension attribute options, including database access through a transaction, comments, init hooks, and namespaced exports.

The `#[surrealism]` attribute controls which Rust items are exported from your extension and how SurrealDB should treat them. Its options are `default`, `init`, `comment = "..."` and `name = "..."`. This page also covers database access through a `Transaction` and reading the values of a definition's `BINDINGS`.

## 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 EXTENSION`](/docs/reference/query-language/statements/define/extension.md), this function is callable as `ext::name::can_drive()`, where `name` is the name the extension is defined with.

## `default`

Mark one function as the extension's default export. It is called with the extension's name alone, without a function name:

```rust
#[surrealism(default)]
fn hello() -> String {
    "Hello from Surrealism!".to_string()
}
```

```surql
ext::name();
```

The project that [`surreal ext init`](/docs/reference/cli/ext.md#ext-init) creates has a default export.

## `name = "..."`

Export a function or a Rust module under a name other than its own. Each segment of the name uses only ASCII letters, digits and underscores, and segments are separated by `::`:

```rust
#[surrealism(name = "other")]
fn can_drive_bla(age: i64) -> bool {
    age >= 18
}
```

This function is called as `ext::name::other()`.

## Querying the database

_(since v4.0.0)_

A function that takes a `Transaction` as its first parameter can run SurrealQL inside the transaction of the query that called it. A function without one cannot query the database.

```rust
use anyhow::Result;
use surrealism::{surrealism, Transaction};
use surrealdb_types::Value;

#[surrealism]
async fn top_customers(tx: Transaction, limit: i64) -> Result<Value> {
    tx.sql_with_vars("SELECT * FROM customer LIMIT $limit", [("limit", limit)]).await
}
```

The handle has four async methods:

| Method | Use |
| --- | --- |
| `tx.sql(query)` | Run a SurrealQL query and convert the result into the return type. |
| `tx.sql_with_vars(query, vars)` | The same, with parameters given as key and value pairs. |
| `tx.run(fnc, args)` | Call a function, such as `fn::user_exists`, with a tuple of arguments. |
| `tx.run_versioned(fnc, version, args)` | Call a function at a given version. |

Calls awaited together run at the same time when none of them can write. Calls that can write, and every `tx.run` call, run one after the other. Queries are still limited by the `queries` and `functions` capabilities that the extension's [definition](/docs/reference/query-language/statements/define/extension.md#capabilities) grants.

The handle is valid only during the call. A function that stores it and uses it after returning stops with an error.

### Read-only and writeable functions

`Transaction` on its own gives read-only access. To modify data, declare the parameter as `Transaction<ReadWrite>`:

```rust
use anyhow::Result;
use surrealism::{surrealism, ReadWrite, Transaction};
use surrealdb_types::Value;

#[surrealism]
async fn create_user(tx: Transaction<ReadWrite>, name: String) -> Result<String> {
    let exists: bool = tx.run("fn::user_exists", (name.clone(),)).await?;
    if exists {
        return Ok(format!("User {name} already exists"));
    }
    let _: Value = tx.sql_with_vars("CREATE user SET name = $name", [("name", name.clone())]).await?;
    Ok(format!("Created user {name}"))
}
```

The parameter type is recorded in the extension's exports manifest, so SurrealDB knows which functions can write before it calls them. A read-only function that tries to modify data fails with this error:

```text title="Error output"
A read-only extension function cannot modify data; the function must declare that it requires a writeable transaction
```

The check covers every statement that modifies data, and also the functions that have a side effect: `api::invoke`, `eval::surql`, `eval::gql`, the `file::` functions that write, copy, rename or delete, `http::post`, `http::put`, `http::patch`, `http::delete` and `sequence::nextval`. A function name used in method syntax is checked in the same way.

> [!NOTE]
> Before SurrealDB 4.0, write access was declared with `#[surrealism(writeable)]`, and module code queried the database through the free functions `surrealism::sql()`, `surrealism::sql_with_vars()` and `surrealism::run()`. These have been removed. See [Upgrading an extension to 4.0](#upgrading-an-extension-to-40).

### Using your own async runtime

`#[surrealism]` keeps the other attributes on a function, so a function can start its own runtime, for example to use a crate that needs Tokio:

```rust
#[tokio::main(flavor = "current_thread")]
#[surrealism]
async fn fetch_status(url: String) -> Result<String, String> {
    // implementation omitted
    Ok(url)
}
```

`#[tokio::main]` has to come first, above `#[surrealism]`, so that `#[surrealism]` receives the plain `fn` that `#[tokio::main]` produces. Use the `current_thread` flavour, because the default multi-threaded runtime needs threads that an extension does not have. A function with its own runtime cannot take a `Transaction`. Use the `Transaction` methods on their own for database access, as they run on the host's async support and need no runtime in the extension.

## `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 extension metadata outputs such as:

- `surreal ext info`
- the export metadata of `INFO FOR DB STRUCTURE`

## `init`

Register one initialisation hook. From SurrealDB 4.0 it runs once, when the extension is built, and every call starts from the memory it leaves behind:

```rust
#[surrealism(init)]
fn warm_cache() {
    // implementation omitted
}
```

Use this for one-time setup such as preloading attached files or building lookup tables. An `init` function cannot be `async` and cannot take a `Transaction`. Because it runs at build time, it cannot open network connections, and a value it reads from the clock or a random source is fixed into every call. Before SurrealDB 4.0, `init` ran when each instance of the extension started.

## Reading bindings

A function reads a value that the extension's definition gives it by name with `surrealism::binding()`. The name must be declared under `[bindings]` in `package.toml`, and the definition gives its value in [`BINDINGS`](/docs/reference/query-language/statements/define/extension.md#bindings):

```toml title="package.toml"
[bindings]
api_key = { type = "string", description = "Mailgun API key", sensitive = true }
author = "option<string>"
```

```rust
use anyhow::Result;
use surrealism::surrealism;

#[surrealism]
async fn sender() -> Result<String> {
    let key: String = surrealism::binding("api_key").await?;
    let author: Option<String> = surrealism::binding("author").await?;
    // implementation omitted
    Ok(author.unwrap_or_else(|| "noreply@example.com".to_owned()))
}
```

The database evaluates the definition's expression the first time the function reads the name during a call, with permissions off and at no higher a level than the user who defined the extension, and converts the value to the declared type. Later reads in the same call return the same value. A function does not need a `Transaction` parameter to read a binding.

Read a name whose type is `option<...>` as an `Option`, because a definition can leave it out, and the value is then `NONE`. Reading a name that `package.toml` does not declare returns an error. An `init` function cannot read a binding, because it runs when the extension is built, before any definition exists.

An extension that reads bindings or declares `[bindings]` is built for extension ABI 5, and one that does neither for ABI 4. To test a function that reads bindings without a database, pass the values to [`surreal ext run`](/docs/reference/cli/ext.md#ext-run) with `--bind`.

## Namespaced modules

Apply `#[surrealism]` to Rust 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`

## Upgrading an extension to 4.0

_(since v4.0.0)_

Extensions built for SurrealDB 3.3 or earlier have to be rebuilt as extensions with the new version of the `surrealism` crate. SurrealDB 4.0 refuses to load an older `.surli` file with the error `Unsupported ABI version 2: this server runs ABI 4 to 5. Rebuild the extension with an SDK for one of them`. Rename `surrealism.toml` to `package.toml`, or scaffold a new project with [`surreal ext init`](/docs/reference/cli/ext.md#ext-init) and move the code across. The definition then has to be made again with [`DEFINE EXTENSION`](/docs/reference/query-language/statements/define/extension.md).

| Before 4.0 | From 4.0 |
| --- | --- |
| `#[surrealism(writeable)]` | A `Transaction<ReadWrite>` parameter |
| `surrealism::sql(query)` | `tx.sql(query).await` |
| `surrealism::sql_with_vars(query, vars)` | `tx.sql_with_vars(query, vars).await` |
| `surrealism::run(fnc, version, args)` | `tx.run(fnc, args).await` or `tx.run_versioned(fnc, version, args).await` |
| `surrealism::kv::get()`, `set()` and the other `kv` functions | Records, through `Transaction<ReadWrite>`. Memory does not last from one call to the next, so a `static` keeps only what `init` set |
| `surrealism::tokio` | A `tokio` dependency of your own, with `#[tokio::main(flavor = "current_thread")]` |

The extension key-value store is removed. The `max_kv_entries` and `max_kv_value_bytes` capabilities in `surrealism.toml` and the `SURREAL_SURREALISM_MAX_KV_ENTRIES` and `SURREAL_SURREALISM_MAX_KV_VALUE_BYTES` environment variables no longer have any effect.

A function that wrote data without declaring it now fails with the read-only error shown in [Read-only and writeable functions](#read-only-and-writeable-functions).

## Related docs

- [Creating custom extensions](/docs/learn/extensions/guides/creating-custom-extensions.md)
- [Extension architecture](/docs/learn/extensions/guides/extension-architecture.md)
- [`surreal ext` command reference](/docs/reference/cli/ext.md)
