Extension<T> is both an extractor and a response type that works with HTTP extensions to share state across handlers and middleware.
Extension allows handlers to extract values that have been added to request extensions by middleware or layers:
use axum::{Router, routing::get, Extension};
use std::sync::Arc;
#[derive(Clone)]
struct State {
count: i32,
}
async fn handler(Extension(state): Extension<Arc<State>>) -> String {
format!("Count: {}", state.count)
}
let state = Arc::new(State { count: 42 });
let app = Router::new()
.route("/", get(handler))
.layer(Extension(state));
Optional extensions
If an extension might not be present, use Option<Extension<T>>:
use axum::{Router, routing::get, Extension};
#[derive(Clone)]
struct Config {
debug: bool,
}
async fn handler(config: Option<Extension<Config>>) -> String {
match config {
Some(Extension(cfg)) => format!("Debug: {}", cfg.debug),
None => "No config available".to_string(),
}
}
let app = Router::new().route("/", get(handler));
If you extract Extension<T> (without Option) and the extension is missing, the request will be rejected with a 500 Internal Server Error.
As a layer
Extension can be used as a layer to add values to all requests:
use axum::{Router, routing::get, Extension};
use std::sync::Arc;
#[derive(Clone)]
struct AppConfig {
api_key: String,
}
let config = Arc::new(AppConfig {
api_key: "secret".to_string(),
});
let app = Router::new()
.route("/", get(|| async { "Hello" }))
.layer(Extension(config));
As a response
Extension can be returned from handlers to add values to response extensions:
use axum::{
Router,
routing::get,
Extension,
response::IntoResponse,
};
#[derive(Clone)]
struct Metadata {
request_id: String,
}
async fn handler() -> impl IntoResponse {
(
Extension(Metadata {
request_id: "abc-123".to_string(),
}),
"Response body"
)
}
let app = Router::new().route("/", get(handler));
Extension vs State
Extension and State are similar but have important differences:
| Feature | Extension | State |
|---|
| Type safety | No compile-time checks | Type checked at compile time |
| Multiple values | Can have many different types | One state type per router |
| Added via | Middleware/layer | with_state() |
| Recommended for | Middleware-injected data | Application state |
use axum::{Router, routing::get, Extension, extract::State};
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db: String,
}
#[derive(Clone)]
struct RequestId(String);
// State: defined when creating the router
async fn with_state(State(state): State<Arc<AppState>>) -> String {
format!("DB: {}", state.db)
}
// Extension: added by middleware
async fn with_extension(Extension(id): Extension<RequestId>) -> String {
format!("Request ID: {}", id.0)
}
let state = Arc::new(AppState { db: "postgres".to_string() });
let app = Router::new()
.route("/state", get(with_state))
.route("/extension", get(with_extension))
.layer(Extension(RequestId("req-123".to_string())))
.with_state(state);
Prefer State for application-level state that’s known at compile time. Use Extension for values added by middleware or for multiple independent shared values.
Type requirements
The type T in Extension<T> must be:
Clone - Values are cloned when extracted
Send + Sync - Required for async handlers
'static - Cannot contain non-static references
use axum::Extension;
use std::sync::Arc;
// ✅ Good: Arc for shared ownership
#[derive(Clone)]
struct Good {
data: Arc<String>,
}
// ❌ Bad: Contains non-static reference
// #[derive(Clone)]
// struct Bad<'a> {
// data: &'a str,
// }
Rejection
Rejects with MissingExtension when:
- The extension type wasn’t added via layer or middleware
- A previous extractor removed it from request extensions
Error message:
Extension of type `your::Type` was not found. Perhaps you forgot to add it? See `axum::Extension`.
Example: Request ID middleware
use axum::{
Router,
routing::get,
Extension,
middleware::{self, Next},
extract::Request,
response::Response,
};
use uuid::Uuid;
#[derive(Clone)]
struct RequestId(String);
async fn add_request_id(mut req: Request, next: Next) -> Response {
let id = RequestId(Uuid::new_v4().to_string());
req.extensions_mut().insert(id);
next.run(req).await
}
async fn handler(Extension(RequestId(id)): Extension<RequestId>) -> String {
format!("Your request ID: {}", id)
}
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(add_request_id));
See also
- State - Recommended way to share application state
- ConnectInfo - Extract connection metadata