Skip to content

Guides

Extension architecture

Surrealism extensions 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 the extension's exports without recompiling the database.

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.

The extension 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. From SurrealDB 4.0, a function reaches the database through a Transaction handle that is valid only for the duration of the call.

At build time, Surrealism also extracts an exports manifest (names, argument types, return type, whether the function takes a writeable transaction, optional comment) and stores it in the archive. That allows metadata lookups without instantiating the extension for every query-planning or introspection operation.

Available since: v4.0.0

Each call to an extension function runs on a fresh instance of the extension, with fresh memory, and the instance is discarded when the call returns. Nothing a call writes to memory is visible to the next call, so an extension cannot carry state between calls in memory. Keep state that has to last in records, through the Transaction handle.

Starting a fresh instance costs little because the build has already done the slow part: surreal ext build runs the extension's init function once, and stores the resulting memory in the archive. Every instance starts from that state, so work done in init, such as loading an attached file, is not repeated on each call.

  • Concurrency: SURREAL_SURREALISM_MAX_CONCURRENT_INVOCATIONS (64 by default) sets how many calls run at once across the datastore. A call that finds no free slot waits within its query's timeout, and calls from inside another extension have slots of their own.

  • Resource limits: SURREAL_SURREALISM_MAX_MEMORY and SURREAL_SURREALISM_MAX_EXECUTION_TIME cap every call. An extension's CAPABILITIES can set lower limits, never higher ones.

  • Timeouts: every call stops at the execution time limit or at the query's timeout. An extension defined with strict_timeout: false runs without that check only on a server started with SURREAL_SURREALISM_ALLOW_UNBOUNDED_EXECUTION.

Before SurrealDB 4.0, a pool of instances was reused between calls, and an instance kept its memory from one call to the next.

The model is close to that of serverless platforms such as AWS Lambda, and closer still to WebAssembly platforms such as Fastly Compute, which also start a fresh instance for each request. The main points of comparison are:

Surrealism extensionsAWS LambdaFastly Compute
State between callsNever kept: every call starts from the build-time snapshotOften kept, because a warm execution environment is reused, but not guaranteedNever kept: every request gets a fresh instance
Setupinit runs once, when the extension is builtRuns on each cold start, or once for a snapshot with SnapStartRuns once at build time with a pre-initialiser such as Wizer
Where it runsInside the database server, in the transaction of the query that called itIn a separate execution environment, reached over the networkIn a separate instance at the edge, reached over the network
Lasting stateRecords, through the Transaction handleAn external store, such as DynamoDB or S3An external store, such as a key-value store
Concurrency limitSURREAL_SURREALISM_MAX_CONCURRENT_INVOCATIONSThe account's or the function's concurrency limitSet by the platform

The difference from Lambda that matters most when porting code is the first row. Code that keeps a cache or a connection in a global variable, and works on Lambda because a warm environment is often reused, finds the variable empty on every call here. Compute such data in init so that it is part of the snapshot, or keep it in records.

Running in the database also changes what a call can do. An extension reads and writes through the transaction of the query that called it, so its reads see that transaction's uncommitted writes, and its own writes commit or roll back with the rest of the query. A function on a serverless platform has to connect to the database as a separate client, in a separate transaction.

For a complete list of server-level variables, see environment variables.

By default, SurrealDB compiles the extensions that are defined when it starts, to reduce the latency of the first call. If you prefer lazy loading, start the server with:

surreal db start --lazy-surrealism

Or set:

SURREAL_LAZY_SURREALISM=true surreal db start

The end-to-end lifecycle is: build your crate with surreal ext build, optionally sign the archive, upload it (typically to a bucket), define the extension with DEFINE EXTENSION or surreal ext install, then invoke functions from queries.

Operational teams often automate upload and definition in CI so test and production databases stay aligned. The overview summarises this flow.

Treat every extension as privileged code that can affect data your database can access. Sandboxing limits host access, but an extension can still implement logic that leaks or corrupts data if you expose it to the wrong scopes. Load only code you trust: a SIGNER checks who built the archive, the DIGEST pins the exact build, and CAPABILITIES grants only the network access, functions and writes that the extension needs. Follow least privilege for namespaces, databases, and credentials that call into Surrealism functions.

Was this page helpful?