Extractor and response type for URL encoded form data.
Type signature
pub struct Form<T>(pub T);
The Form extractor deserializes form data from the request. The behavior depends on the request method:
- GET or HEAD requests: Form data is read from the query string (same as
Query)
- Other methods: Form data is read from the request body with
Content-Type: application/x-www-form-urlencoded
This matches how HTML forms are sent by browsers by default.
Since parsing form data might require consuming the request body, the Form extractor must be last if there are multiple extractors in a handler. See the order of extractors.
Basic usage
use axum::{
Form,
routing::post,
Router,
};
use serde::Deserialize;
#[derive(Deserialize)]
struct SignUp {
username: String,
password: String,
}
async fn accept_form(Form(sign_up): Form<SignUp>) {
let username = sign_up.username;
let password = sign_up.password;
// ...
}
let app = Router::new().route("/signup", post(accept_form));
Optional fields
Use Option for optional form fields:
use serde::Deserialize;
#[derive(Deserialize)]
struct FormData {
username: String,
email: Option<String>,
age: Option<u32>,
}
Multiple values
Form fields with the same name can be collected into a Vec:
use serde::Deserialize;
#[derive(Deserialize)]
struct Preferences {
interests: Vec<String>,
}
// Form data: interests=reading&interests=coding&interests=travel
As a response
Form can encode any type that implements serde::Serialize as application/x-www-form-urlencoded:
use axum::Form;
use serde::Serialize;
#[derive(Serialize)]
struct Payload {
value: String,
}
async fn handler() -> Form<Payload> {
Form(Payload { value: "foo".to_owned() })
}
Error handling
The request method is not GET/HEAD and the content-type is not application/x-www-form-urlencoded.
Failed to deserialize form data from query string (GET/HEAD requests).
FailedToDeserializeFormBody
Failed to deserialize form data from request body (other methods).
Example errors
// GET request with invalid query string:
// Status: 400 Bad Request
// Body: "Failed to deserialize form: age: invalid digit found in string"
// POST request with invalid form body:
// Status: 422 Unprocessable Entity
// Body: "Failed to deserialize form body: age: invalid digit found in string"
// POST request without correct content-type:
// Status: 415 Unsupported Media Type
GET vs POST behavior
The Form extractor automatically handles both query strings and request bodies:
use axum::{
Form,
routing::{get, post},
Router,
};
use serde::Deserialize;
#[derive(Deserialize)]
struct SearchForm {
query: String,
}
async fn search(Form(form): Form<SearchForm>) {
// Works with both:
// - GET /search?query=rust
// - POST /search with body: query=rust
}
let app = Router::new()
.route("/search", get(search).post(search));
For multipart/form-data (file uploads), use the Multipart extractor instead:
use axum::extract::Multipart;
async fn upload(mut multipart: Multipart) {
// Handle multipart form data
}
URL encoding
Form data uses URL encoding where special characters are percent-encoded:
// Form data: message=hello%20world&emoji=%F0%9F%8E%89
#[derive(Deserialize)]
struct Message {
message: String, // "hello world"
emoji: String, // "🎉"
}