The Arion WebAssembly SDK is a Rust crate for writing plugins that run inside the proxy's wasm filter. You implement one trait, annotate it with a macro, and the SDK handles the FFI, memory, and serialization between your module and the host.
Before you begin
The SDK lives in the Arion repository as three crates, referenced by path from your plugin: arion_wasm_sdk (the API you use), arion-wasm-sdk-macros (the #[arion_plugin] macro, re-exported by the SDK), and arion-wasm-types (ABI types shared with the host; the common ones are re-exported from the SDK root). Install the build target with rustup target add wasm32-unknown-unknown.
Write a filter
A plugin is a struct implementing Default and the Plugin trait. The #[arion_plugin] attribute inspects your impl block and generates extern "C" entry points for exactly the hooks you override — unimplemented hooks are not exported and cost nothing.
use arion_wasm_sdk::{arion_plugin, bytes, http, init_tracing, FilterAction, HttpHeaders, Plugin, RequestHandle};
#[derive(Default)]
struct AuthFilter;
#[arion_plugin]
impl Plugin for AuthFilter {
fn on_plugin_start(&mut self) {
let _ = init_tracing();
}
fn on_request_headers(&mut self, ctx: &RequestHandle<HttpHeaders>) -> FilterAction {
match ctx.get_header("authorization") {
Ok(Some(v)) if v.as_bytes() == b"Bearer secret-token" => FilterAction::Continue,
_ => ctx.direct_response(
http::Response::builder()
.status(401)
.body(bytes::Bytes::from_static(b"unauthorized"))
.unwrap(),
),
}
}
}Lifecycle hooks
The Plugin trait has two hook groups. Module lifecycle: on_plugin_start (once per instance — parse config, open shared state, call init_tracing) and on_plugin_destroy. Transaction lifecycle: on_transaction_start, on_request_headers, on_request_body, on_response_headers, on_response_body, and on_transaction_complete.
The four header/body hooks return a FilterAction:
| Action | Effect |
|---|---|
Continue | Pass the message to the next filter. |
PauseAndBufferBody | Buffer the full body, then call the matching body hook. |
DirectResponse | Short-circuit with the response staged via direct_response. |
A plugin that exports a body hook without the matching headers hook gets the body buffered implicitly.
Typestate handles
Hooks receive RequestHandle<T> or ResponseHandle<T>, where T is HttpHeaders or HttpBody. The type parameter enforces phase safety at compile time: get_body() exists only on HttpBody handles, which only body hooks receive — calling it from a headers hook does not compile.
On any handle: get_header, set_header, add_header, remove_header, replace_header, plus map-at-once variants (get_headers_map, set_headers_map) and batched apply_header_mutations. Request handles add get_uri / set_uri; response handles add get_status_code / set_status_code. Body handles add get_body / set_body, trailer access, and full materialization (take_request / replace_request, take_response / replace_response) for rewriting the whole message as standard http crate types. When set_body changes the length, the host updates an existing Content-Length header but never inserts one.
Build the module
[package]
name = "auth_filter"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
arion_wasm_sdk = { path = "../arion/arion-wasm-sdk" }cargo build --target wasm32-unknown-unknown --release
The module lands at target/wasm32-unknown-unknown/release/auth_filter.wasm, ready for the filter's code.local.filename. Building for wasm32-wasip1 also works.
Limits
- The plugin struct must implement
Default; the macro instantiates a single instance per module instance. - Hooks are synchronous Rust. Async work — HTTP and gRPC callouts, timers — goes through host calls; see callouts and shared state.
- Body hooks see the complete buffered body only; there is no streaming chunk API.
- The guest/host ABI is versioned by the SDK: rebuild plugins when you upgrade Arion.
Nineteen runnable example plugins live in the repository under arion-wasm-sdk/examples/, one per SDK feature. For an end-to-end walkthrough, follow your first WASM filter.