---
title: "External processing"
url: "/reference/filters/http/ext-proc/"
kind: "reference"
summary: "HTTP filter that streams headers and bodies to an external gRPC processor, overridable per route."
---

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

# External processing

HTTP filter that streams headers and bodies to an external gRPC processor, overridable per route.

| Attachment | Type URL (`@type`) | Per-route override | Build feature |
|---|---|---|---|
| HTTP filter chain (`http_filters`) | `type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor` | Yes — `envoy.extensions.filters.http.ext_proc.v3.ExtProcPerRoute`, or `envoy.config.route.v3.FilterConfig` | None |

## What it does

External processing (ext_proc) hands each request and response to a gRPC service you run, which can mutate headers and bodies or answer immediately on Arion's behalf. `processing_mode` decides what is sent — headers, bodies, trailers, in streamed or buffered form — so the processor sees only what it needs. It is the extension point for logic that lives outside the proxy; for in-process extension see [Wasm filters](@/reference/filters/http/wasm.md).

## Minimal configuration

From `arion-runtime-ext_proc.yaml`; the fragment is an entry in `http_filters`, before the router. The target cluster must speak HTTP/2:

```yaml,arion=fragment
- name: ext_proc
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor
    failure_mode_allow: false
    message_timeout: 5s
    processing_mode:
      request_header_mode: "SEND"
      response_header_mode: "SKIP"
    grpc_service:
      envoy_grpc:
        cluster_name: ext_proc_server
```

## Fields

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `grpc_service` | object | Yes | — | Processor target: `envoy_grpc` (`cluster_name`, optional `max_receive_message_length`) or `google_grpc` (`target_uri`), plus an optional `timeout`. |
| `failure_mode_allow` | bool | No | `false` | Continue when the processor is unreachable or errors. When `false`, processor message timeouts return 504 and processor or stream errors return 500. |
| `processing_mode` | object | No | headers only | Per-phase modes, below. |
| `message_timeout` | duration | No | `200ms` | How long to wait for the processor's reply to each message. |
| `max_message_timeout` | duration | No | unset | Upper bound the processor may raise the timeout to. |
| `mutation_rules` | object | No | unset | Restrict header mutations with `allow_all_routing`, `allow_envoy`, `disallow_system`, `disallow_all`, `allow_expression`, `disallow_expression` and `disallow_is_error`. Omitting the object applies no mutation-rule restrictions. |
| `forward_rules` | object | No | all | `allowed_headers` / `disallowed_headers` matcher lists limiting which headers are sent to the processor. |
| `allow_mode_override` | bool | No | `false` | Let the processor change the processing mode mid-stream. |
| `allowed_override_modes` | list | No | empty | Processing modes the processor may switch to. |
| `route_cache_action` | enum | No | `DEFAULT` | `DEFAULT`, `CLEAR` or `RETAIN` the route after header mutations. |
| `send_body_without_waiting_for_header_response` | bool | No | `false` | Stream body chunks without waiting for the header reply. |
| `observability_mode` | bool | No | `false` | Fire-and-forget: send messages but never wait for answers. |
| `disable_immediate_response` | bool | No | `false` | Ignore `immediate_response` messages from the processor. |
| `deferred_close_timeout` | duration | No | unset | Grace period before closing the processor stream. |
| `metadata_options` | object | No | unset | Limited support for `forwarding_namespaces.untyped`; include `envoy.lb` to report the served endpoint with response headers. |

`http_service`, `filter_metadata`, `request_attributes`, `response_attributes` and `status_on_error` are unsupported and refused at startup.

### Header mutations

Omitting `mutation_rules` allows the processor to modify supported headers without mutation-rule restrictions. With `mutation_rules: {}`, the default rules block changes to `host`, `:authority`, `:scheme`, `:method` and `x-envoy-` headers. Other supported pseudo-headers remain allowed unless `disallow_system` is enabled; `allow_all_routing`, `allow_envoy` and the expression fields adjust these restrictions.

For `set_headers`, leaving both `append` and `append_action` unset adds or replaces the header. `append: true` appends instead; an explicit `append` takes precedence over `append_action`.

### Processing modes

`request_header_mode` / `response_header_mode` and the trailer modes take `DEFAULT`, `SEND` or `SKIP`. `request_body_mode` / `response_body_mode` take `NONE`, `STREAMED`, `BUFFERED`, `BUFFERED_PARTIAL` or `FULL_DUPLEX_STREAMED`.

For a processor that waits for the complete request before answering its headers, use `FULL_DUPLEX_STREAMED` with `send_body_without_waiting_for_header_response: true`. This lets request body messages reach the processor without waiting for its header reply.

### Metadata forwarding

`metadata_options.forwarding_namespaces.untyped: [envoy.lb]` enables served-endpoint feedback. With response-header processing enabled, a cluster using the `OverrideHost` load-balancing policy reports the actual served endpoint in `ProcessingRequest.metadata_context.filter_metadata["envoy.lb"]["x-gateway-destination-endpoint-served"]`, including fallback selections. This is processor metadata, not an HTTP response header.

Other untyped namespace names are accepted but do not enable arbitrary metadata forwarding. Nonempty `forwarding_namespaces.typed`, `receiving_namespaces` and `cluster_metadata_forwarding_namespaces` are rejected; empty receiving or cluster namespace objects are accepted. `metadata_options` cannot be set in per-route overrides.

## Per-route override

Routes adjust the filter through `typed_per_filter_config` keyed by its chain name. The override type is `ExtProcPerRoute`: either `disabled: true`, or `overrides` carrying a different `processing_mode`, `grpc_service` or `failure_mode_allow` for that route. These settings work with both inline `route_config` and routes delivered over RDS. Per-route settings retain the filter's metadata forwarding configuration.

## Ordering and interactions

The processor sees requests as earlier filters left them and can still short-circuit with an immediate response; place ext_proc before the router and after authentication unless the processor itself does the authenticating.

See the [AI Inference Gateway overview](@/docs/ai-inference-gateway/overview.md) and [inference routing guide](@/guides/inference/inference-routing.md) for endpoint selection through ext_proc.
