Plugins Quickstart¶
Load a prebuilt plugin and call it from a query in under five minutes — no plugin authoring required.
This page uses the bundled ai.example.geo plugin, which registers a single
scalar function: ai.example.geo.haversine, the great-circle distance (in
kilometres) between two latitude/longitude points. You will read the
prebuilt .wasm artifact, hand its bytes to load_wasm_component, inspect
what was registered, and then call the function from Cypher.
If you want to understand how plugins fit together first, start with Concepts. To build your own, see Authoring.
Prerequisites¶
-
The default
uni-dbwheel already bundles wasmtime. No extra runtime to install — the Component Model loader (load_wasm_component) is built in. In the Rust workspace this is thewasm-pluginsfeature (Extism, viaload_wasm_extism, is the separateextism-pluginsfeature). Both are compiled into the published Python wheel. -
The prebuilt
.wasmfixture must exist on disk. The example plugin is not checked in as a binary; it is compiled on demand. Build it once from the repo root:This produces the Component Model artifact at:
The
wasm32-wasip2target emits a Component Model binary directly — nowasm-toolspost-processing step is needed. For the full build story (and how to compile your own plugin), see WASM Components and Authoring.
Note
If the fixture is missing, the e2e tests skip rather than fail. Make sure the path above exists before running the snippets below.
Load it¶
Read the component bytes and pass them to load_wasm_component. The grants
argument is a list of capability name strings; the geo plugin only
registers a scalar function, so ["ScalarFn"] is sufficient. (Omitting
grants defaults to scalar / aggregate / procedure.)
import uni_db
db = uni_db.Uni.open("./geo.db")
wasm_path = (
"examples/example-wasm-geo/target/wasm32-wasip2/"
"release/example_wasm_geo.wasm"
)
with open(wasm_path, "rb") as f:
outcome = db.load_wasm_component(f.read(), grants=["ScalarFn"])
print(outcome["plugin_id"], outcome["version"])
print(outcome["scalars_registered"])
import uni_db
db = await uni_db.AsyncUni.temporary()
wasm_path = (
"examples/example-wasm-geo/target/wasm32-wasip2/"
"release/example_wasm_geo.wasm"
)
with open(wasm_path, "rb") as f:
# Awaited: wasmtime instantiation runs off the event loop.
outcome = await db.load_wasm_component(f.read(), grants=["ScalarFn"])
print(outcome["plugin_id"], outcome["version"])
print(outcome["scalars_registered"])
use uni_db::Uni;
use uni_plugin::{Capability, CapabilitySet};
use uni_plugin_wasm::WasmLoader;
let db = Uni::open("./geo.db")?;
let bytes = std::fs::read(
"examples/example-wasm-geo/target/wasm32-wasip2/\
release/example_wasm_geo.wasm",
)?;
// The Rust path takes an explicit loader and capability set; the
// Python methods wrap exactly this call.
let loader = WasmLoader::new();
let caps = CapabilitySet::from_iter_of([Capability::ScalarFn]);
let host_grants = vec!["ScalarFn".to_owned()];
let outcome = db.load_wasm_component(&loader, &bytes, &host_grants, &caps)?;
println!("{} {}", outcome.plugin_id, outcome.version);
println!("{:?}", outcome.scalars_registered);
The function is registered the moment the call returns — there is no separate
"activate" step. Loading is instance-scoped: the scalar is available to every
session and query on this Uni handle.
Inspect the LoadOutcome¶
Both Python methods return a dict describing exactly what the loader did.
For the geo plugin you'll see:
{
"plugin_id": "ai.example.geo",
"version": "0.1.0",
"scalars_registered": ["ai.example.geo.haversine"],
"aggregates_registered": [],
"procedures_registered": [],
"effective_capabilities": [],
"denied_capabilities": [],
}
| Key | Meaning |
|---|---|
plugin_id |
The plugin's self-declared identity (from its manifest). |
version |
The plugin's declared semantic version. |
scalars_registered |
Fully-qualified names of scalar functions now callable. |
aggregates_registered |
Aggregate function QNames registered. |
procedures_registered |
Procedure QNames registered. |
effective_capabilities |
Granted ∩ declared — what the plugin actually got. |
denied_capabilities |
Declared-but-not-granted capabilities (diagnostics). |
effective_capabilities is the intersection of what you granted and what the
plugin's manifest declares. The geo plugin declares no capabilities, so its
effective set is empty even though you passed grants=["ScalarFn"] — that grant
is a surface grant that authorizes registering the scalar, distinct from the
host-service capabilities a manifest can declare. If you withhold a capability a
plugin does declare, it shows up under denied_capabilities instead — a quick
way to spot a misconfigured grant set. In Rust, the same fields are typed members
of uni_plugin_wasm::loader::LoadOutcome.
See Reference for the complete field table and the full list of capability names. The grant/deny model is covered in Trust & Capabilities.
Call it from Cypher¶
A registered scalar is callable by its fully-qualified name anywhere a Cypher expression is allowed:
The four f64 arguments are lat1, lon1, lat2, lon2; the result is the
distance in kilometres (~343.557 km for Paris→London). Like any DataFusion
scalar, it is vectorised — pass column expressions over a MATCH and it
evaluates row-by-row across the whole batch.
The name ai.example.geo.haversine is a QName (qualified name). At plan
time the query engine resolves it against the local plugin registry, so a
registered plugin scalar slots in alongside built-in functions with no special
syntax. See Concepts for how QName resolution and the registry
fit into the wider plugin model.
Next steps¶
- Concepts — loaders, registry, QName resolution, and the plugin lifecycle.
- Loaders — the available loaders (WASM Component Model, Extism, Rhai) and when to use each.
- Authoring — write and build your own plugin, including the
wasm32-wasip2toolchain setup. - Trust & Capabilities — the grant model, the full capability list, and host trust policy.