---
title: "Rust SDK"
url: "/docs/extend/rust-sdk/"
kind: "feature"
summary: "Write a WASM filter in Rust: the arion_plugin entry point, lifecycle hooks, typestate handles, and building the module."
---

> Arion site index: [/llms.txt](/llms.txt)

# Rust SDK

Write a WASM filter in Rust: the arion_plugin entry point, lifecycle hooks, typestate handles, and building the module.

The Arion WebAssembly SDK is a Rust crate for writing plugins that run inside the proxy's [wasm filter](@/docs/extend/wasm-filters.md). 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.

```rust
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

```toml,name=Cargo.toml
[package]
name = "auth_filter"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
arion_wasm_sdk = { path = "../arion/arion-wasm-sdk" }
```

```bash
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](@/docs/extend/callouts-and-shared-state.md).
- 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](@/guides/extend/first-wasm-filter.md).
