---
title: "WASM filters"
url: "/docs/extend/wasm-filters/"
kind: "feature"
summary: "Load a WebAssembly module into the HTTP filter chain: the wasm build feature, the filter config, plugin configuration, and instance lifecycle."
---

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

# WASM filters

Load a WebAssembly module into the HTTP filter chain: the wasm build feature, the filter config, plugin configuration, and instance lifecycle.

{{ <badge label="requires: wasm" kind="requires" /> }}

The `wasm` HTTP filter loads a WebAssembly module into the proxy and calls it on every request and response passing through the filter chain. Use it when you need custom logic in-process — header and body mutation, bespoke auth, callouts — without forking or rebuilding Arion.

## Before you begin

- Arion must be built with the `wasm` Cargo feature (`cargo build -p arion-proxy --features wasm`). A binary without it refuses to start when the config contains a `wasm` filter.
- You need a compiled plugin — a `.wasm` module built with the [Rust SDK](@/docs/extend/rust-sdk.md) for the `wasm32-unknown-unknown` target.

## Enable it

Add the filter to `http_filters` in the HTTP connection manager, before the router:

```yaml,arion=fragment
http_filters:
  - name: envoy.filters.http.wasm
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
      config:
        name: auth_filter
        root_id: auth_filter
        vm_config:
          runtime: envoy.wasm.runtime.v8
          code:
            local:
              filename: /etc/arion/auth_filter.wasm
  - name: envoy.filters.http.router
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
```

`config.name` and `root_id` identify the plugin. `runtime` is accepted for Envoy compatibility but its value is not used — Arion always runs modules on its embedded Wasmtime engine. `vm_config.vm_id` and `allow_precompiled` are optional. `code` takes a local data source: `filename` (a `.wasm` file), `inline_bytes` (base64 module bytes), or `inline_string` (WAT text).

## Variations

### Pass configuration to the plugin

`configuration` carries an arbitrary string (typically JSON) that the plugin reads at startup with `get_plugin_config()`. It must be wrapped as a `google.protobuf.StringValue`:

```yaml,arion=fragment
config:
  name: auth_filter
  root_id: auth_filter
  configuration:
    "@type": type.googleapis.com/google.protobuf.StringValue
    value: '{"header": "x-tenant", "required": true}'
  vm_config:
    runtime: envoy.wasm.runtime.v8
    code:
      local:
        filename: /etc/arion/auth_filter.wasm
```

### Disable on a route

There is no per-route WASM configuration, but any filter can be switched off for a route with a `FilterConfig` wrapper keyed by the filter's chain name:

```yaml,arion=fragment
typed_per_filter_config:
  envoy.filters.http.wasm:
    "@type": type.googleapis.com/envoy.config.route.v3.FilterConfig
    disabled: true
```

## Instance lifecycle

The module is compiled once at configuration load. Instances are created lazily: the first request that needs one instantiates it and runs the plugin's `on_plugin_start` hook; when the transaction completes, the instance is reset and parked in a lock-free pool (up to 1024 per filter) for the next request. Each in-flight request holds its own instance, so plugin state is request-local unless you use [shared state](@/docs/extend/callouts-and-shared-state.md). Which hooks run is discovered from the module's exports: a plugin that only exports `on_request_headers` costs nothing on the response path.

## Verify

With the SDK's `dummy_filter` example module loaded, an unauthenticated request is answered by the plugin itself:

```bash
curl -i http://127.0.0.1:8080/
```

```text
HTTP/1.1 401 Unauthorized
...
401 Unauthorized: missing Authorization header
```

## Limits

- Body hooks fully buffer the request or response body before the plugin sees it; there is no streaming body API.
- `code` must be a local data source. Remote code and the `environment_variable` source are rejected.
- Envoy's `fail_open`, `failure_policy`, `reload_config`, `capability_restriction_config`, and VM `environment_variables` fields are not supported; a config using them refuses to start (see [Envoy compatibility](@/docs/concepts/envoy-compatibility.md)).
- Per-route control is disable-only — no per-route plugin configuration.
- A plugin runtime error fails the request with a 500; there is no fail-open mode.

Field-by-field details are in the [wasm filter reference](@/reference/filters/http/wasm.md); to write the module itself, continue with the [Rust SDK](@/docs/extend/rust-sdk.md).
