Axum extractors and type destructuring

Quick reference card for extractor rules. For the deep dive on how this works under the hood, see Axum Dependency Injection.

Destructuring syntax

Axum extractors are tuple structs. You destructure them in function params:

async fn handler(
    State(state): State<AppState>,
    Path(id): Path<u64>,
    Query(filter): Query<FilterParams>,
    Json(body): Json<CreateRequest>,
) -> impl IntoResponse { ... }

This is pattern matching — State(state) is a pattern on the left of :, not a function call. Same syntax Rust uses in let and match.

FromRequestParts vs FromRequest

TraitPositionCountConsumes body?Examples
FromRequestPartsAny positionMultiple allowedNoState, Path, Query, Extension, headers
FromRequestLast param onlyOneYesJson, String, Bytes, Request

Body extractor MUST be last. Non-last params must all implement FromRequestParts. If you get the trait Handler<_, _> is not implemented, check this first.

Common extractors cheat sheet

// State — from router's .with_state()
State(state): State<AppState>
 
// Path — from URL segments (Axum 0.8: {id} syntax, not :id)
// .route("/users/{id}", get(handler))
Path(id): Path<u64>
 
// Query — from ?key=value
Query(params): Query<FilterParams>
 
// Json — from request body, expects Content-Type: application/json (MUST be last)
Json(body): Json<CreateRequest>
 
// Other body extractors (also MUST be last, pick ONE):
// String — reads body as UTF-8 text, any content type
// Bytes — reads body as raw bytes
// Form(data): Form<T> — expects Content-Type: application/x-www-form-urlencoded
// Multipart — for file uploads (Content-Type: multipart/form-data)
 
// Headers
TypedHeader(auth): TypedHeader<Authorization<Bearer>>

Axum 0.8 path syntax

// Axum 0.8+
.route("/users/{id}/posts/{post_id}", get(handler))
 
// NOT this (old syntax)
.route("/users/:id/posts/:post_id", get(handler))

See also